From 2d6c613c16facc854a0aea5e7dd8218a48dd18b9 Mon Sep 17 00:00:00 2001 From: Ian Davis Date: Fri, 13 Sep 2024 12:27:39 -0700 Subject: [PATCH 01/13] Add link function to Module --- pyqir/pyqir/_native.pyi | 10 ++++++ pyqir/src/module.rs | 34 ++++++++++++++---- pyqir/tests/5_bit_random_number.ll | 44 +++++++++++++++++++++++ pyqir/tests/combined_module.ll | 56 ++++++++++++++++++++++++++++++ pyqir/tests/random_bit.ll | 34 ++++++++++++++++++ pyqir/tests/test_module_linking.py | 31 +++++++++++++++++ 6 files changed, 203 insertions(+), 6 deletions(-) create mode 100644 pyqir/tests/5_bit_random_number.ll create mode 100644 pyqir/tests/combined_module.ll create mode 100644 pyqir/tests/random_bit.ll create mode 100644 pyqir/tests/test_module_linking.py diff --git a/pyqir/pyqir/_native.pyi b/pyqir/pyqir/_native.pyi index 0723dd50..de8357d8 100644 --- a/pyqir/pyqir/_native.pyi +++ b/pyqir/pyqir/_native.pyi @@ -681,6 +681,16 @@ class Module: """Converts this module into an LLVM IR string.""" ... + def link(self, other: Module) -> Optional[str]: + """ + Link the supplied module into the current module. + Destroys the supplied module. + + :returns: An error message if linking failed or `None` if linking succeeded. + :rtype: typing.Optional[str] + """ + ... + class ModuleFlagBehavior(Enum): """Module flag behavior choices""" diff --git a/pyqir/src/module.rs b/pyqir/src/module.rs index 8ee46652..ccd53887 100644 --- a/pyqir/src/module.rs +++ b/pyqir/src/module.rs @@ -17,17 +17,14 @@ use llvm_sys::{ bit_writer::LLVMWriteBitcodeToMemoryBuffer, core::*, ir_reader::LLVMParseIRInContext, + linker::LLVMLinkModules2, LLVMLinkage, LLVMModule, }; use pyo3::{exceptions::PyValueError, prelude::*, pyclass::CompareOp, types::PyBytes}; use qirlib::module::FlagBehavior; +use core::mem::forget; use std::{ - collections::hash_map::DefaultHasher, - ffi::CString, - hash::{Hash, Hasher}, - ops::Deref, - ptr::{self, NonNull}, - str, + collections::hash_map::DefaultHasher, ffi::CString, hash::{Hash, Hasher}, ops::Deref, ptr::{self, NonNull}, str }; /// A module is a collection of global values. @@ -263,6 +260,31 @@ impl Module { .to_string() } } + + /// Link the supplied module into the current module. + /// Destroys the supplied module. + /// + /// :returns: An error message if linking failed or `None` if linking succeeded. + /// :rtype: typing.Optional[str] + pub fn link(&self, other: Py, py: Python) -> Option { + if self.context.borrow(py).as_ptr() != other.borrow(py).context.borrow(py).as_ptr() { + return Some("Cannot link modules from different contexts. Modules are untouched.".to_string()); + } + unsafe { + let result = LLVMLinkModules2(self.module.as_ptr(), other.borrow(py).module.as_ptr()); + // `forget` the other module. LLVM has destroyed it + // and we'll get a segfault if we drop it. + forget(other); + if result == 0 { + return None; + } else { + // in the future we need to return a proper error message + // using `LLVMContextSetDiagnosticHandler`. This is a lot of work + // to get right, so we'll leave it for now. + Some("Failed to link modules".to_string()) + } + } + } } impl Deref for Module { diff --git a/pyqir/tests/5_bit_random_number.ll b/pyqir/tests/5_bit_random_number.ll new file mode 100644 index 00000000..9c0c7b8f --- /dev/null +++ b/pyqir/tests/5_bit_random_number.ll @@ -0,0 +1,44 @@ +; ModuleID = '5_bit_random_number' +%Result = type opaque +%Qubit = type opaque + +define void @five_bit_random_number() #0 { +block_0: + call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 0 to %Qubit*)) + call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 1 to %Qubit*)) + call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 2 to %Qubit*)) + call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 3 to %Qubit*)) + call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 4 to %Qubit*)) + call void @__quantum__qis__m__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*)) + call void @__quantum__qis__m__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 1 to %Result*)) + call void @__quantum__qis__m__body(%Qubit* inttoptr (i64 2 to %Qubit*), %Result* inttoptr (i64 2 to %Result*)) + call void @__quantum__qis__m__body(%Qubit* inttoptr (i64 3 to %Qubit*), %Result* inttoptr (i64 3 to %Result*)) + call void @__quantum__qis__m__body(%Qubit* inttoptr (i64 4 to %Qubit*), %Result* inttoptr (i64 4 to %Result*)) + call void @__quantum__rt__array_record_output(i64 5, i8* null) + call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null) + call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 1 to %Result*), i8* null) + call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 2 to %Result*), i8* null) + call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 3 to %Result*), i8* null) + call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 4 to %Result*), i8* null) + ret void +} + +declare void @__quantum__qis__h__body(%Qubit*) + +declare void @__quantum__rt__array_record_output(i64, i8*) + +declare void @__quantum__rt__result_record_output(%Result*, i8*) + +declare void @__quantum__qis__m__body(%Qubit*, %Result*) #1 + +attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="base_profile" "required_num_qubits"="5" "required_num_results"="5" } +attributes #1 = { "irreversible" } + +; module flags + +!llvm.module.flags = !{!0, !1, !2, !3} + +!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} \ No newline at end of file diff --git a/pyqir/tests/combined_module.ll b/pyqir/tests/combined_module.ll new file mode 100644 index 00000000..ebadbb78 --- /dev/null +++ b/pyqir/tests/combined_module.ll @@ -0,0 +1,56 @@ + +%Qubit = type opaque +%Result = type opaque + +define void @random_bit() #0 { +block_0: + call void @__quantum__qis__h__body(%Qubit* null) + call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 1 to %Qubit*)) + call void @__quantum__qis__cz__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Qubit* null) + call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 1 to %Qubit*)) + call void @__quantum__qis__m__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* null) + call void @__quantum__rt__result_record_output(%Result* null, i8* null) + ret void +} + +declare void @__quantum__qis__h__body(%Qubit*) + +declare void @__quantum__qis__cz__body(%Qubit*, %Qubit*) + +declare void @__quantum__rt__result_record_output(%Result*, i8*) + +declare void @__quantum__qis__m__body(%Qubit*, %Result*) #1 + +define void @five_bit_random_number() #2 { +block_0: + call void @__quantum__qis__h__body(%Qubit* null) + call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 1 to %Qubit*)) + call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 2 to %Qubit*)) + call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 3 to %Qubit*)) + call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 4 to %Qubit*)) + call void @__quantum__qis__m__body(%Qubit* null, %Result* null) + call void @__quantum__qis__m__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 1 to %Result*)) + call void @__quantum__qis__m__body(%Qubit* inttoptr (i64 2 to %Qubit*), %Result* inttoptr (i64 2 to %Result*)) + call void @__quantum__qis__m__body(%Qubit* inttoptr (i64 3 to %Qubit*), %Result* inttoptr (i64 3 to %Result*)) + call void @__quantum__qis__m__body(%Qubit* inttoptr (i64 4 to %Qubit*), %Result* inttoptr (i64 4 to %Result*)) + call void @__quantum__rt__array_record_output(i64 5, i8* null) + call void @__quantum__rt__result_record_output(%Result* null, i8* null) + call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 1 to %Result*), i8* null) + call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 2 to %Result*), i8* null) + call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 3 to %Result*), i8* null) + call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 4 to %Result*), i8* null) + ret void +} + +declare void @__quantum__rt__array_record_output(i64, i8*) + +attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="base_profile" "required_num_qubits"="2" "required_num_results"="1" } +attributes #1 = { "irreversible" } +attributes #2 = { "entry_point" "output_labeling_schema" "qir_profiles"="base_profile" "required_num_qubits"="5" "required_num_results"="5" } + +!llvm.module.flags = !{!0, !1, !2, !3} + +!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} diff --git a/pyqir/tests/random_bit.ll b/pyqir/tests/random_bit.ll new file mode 100644 index 00000000..7c1cf384 --- /dev/null +++ b/pyqir/tests/random_bit.ll @@ -0,0 +1,34 @@ +; ModuleID = 'random_bit' +%Result = type opaque +%Qubit = type opaque + +define void @random_bit() #0 { +block_0: + call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 0 to %Qubit*)) + call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 1 to %Qubit*)) + call void @__quantum__qis__cz__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Qubit* inttoptr (i64 0 to %Qubit*)) + call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 1 to %Qubit*)) + call void @__quantum__qis__m__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 0 to %Result*)) + call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null) + ret void +} + +declare void @__quantum__qis__h__body(%Qubit*) + +declare void @__quantum__qis__cz__body(%Qubit*, %Qubit*) + +declare void @__quantum__rt__result_record_output(%Result*, i8*) + +declare void @__quantum__qis__m__body(%Qubit*, %Result*) #1 + +attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="base_profile" "required_num_qubits"="2" "required_num_results"="1" } +attributes #1 = { "irreversible" } + +; module flags + +!llvm.module.flags = !{!0, !1, !2, !3} + +!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} \ No newline at end of file diff --git a/pyqir/tests/test_module_linking.py b/pyqir/tests/test_module_linking.py new file mode 100644 index 00000000..bae3a809 --- /dev/null +++ b/pyqir/tests/test_module_linking.py @@ -0,0 +1,31 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +from pathlib import Path + +from pyqir import ( + Context, + Module, +) + + +def test_link_modules_with_same_context() -> None: + context = Context() + ir = Path("tests/random_bit.ll").read_text() + dest = Module.from_ir(context, ir) + ir = Path("tests/5_bit_random_number.ll").read_text() + src = Module.from_ir(context, ir) + assert dest.link(src) is None + assert dest.verify() is None + actual_ir = str(dest) + expected_ir = str(Path("tests/combined_module.ll").read_text()) + assert actual_ir == expected_ir + +def test_link_modules_with_different_contexts() -> None: + ir = Path("tests/random_bit.ll").read_text() + dest = Module.from_ir(Context(), ir) + ir = Path("tests/5_bit_random_number.ll").read_text() + src = Module.from_ir(Context(), ir) + message = dest.link(src) + assert message == "Cannot link modules from different contexts. Modules are untouched." + From bb1e5ca3d02db813f53cb2e73a0ef9094ba8e531 Mon Sep 17 00:00:00 2001 From: Ian Davis Date: Fri, 13 Sep 2024 13:25:04 -0700 Subject: [PATCH 02/13] Formatting --- pyqir/src/module.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/pyqir/src/module.rs b/pyqir/src/module.rs index ccd53887..fcdfa2cc 100644 --- a/pyqir/src/module.rs +++ b/pyqir/src/module.rs @@ -9,6 +9,7 @@ use crate::{ metadata::Metadata, values::{Constant, Owner, Value}, }; +use core::mem::forget; use core::slice; #[allow(clippy::wildcard_imports, deprecated)] use llvm_sys::{ @@ -22,9 +23,13 @@ use llvm_sys::{ }; use pyo3::{exceptions::PyValueError, prelude::*, pyclass::CompareOp, types::PyBytes}; use qirlib::module::FlagBehavior; -use core::mem::forget; use std::{ - collections::hash_map::DefaultHasher, ffi::CString, hash::{Hash, Hasher}, ops::Deref, ptr::{self, NonNull}, str + collections::hash_map::DefaultHasher, + ffi::CString, + hash::{Hash, Hasher}, + ops::Deref, + ptr::{self, NonNull}, + str, }; /// A module is a collection of global values. @@ -268,7 +273,9 @@ impl Module { /// :rtype: typing.Optional[str] pub fn link(&self, other: Py, py: Python) -> Option { if self.context.borrow(py).as_ptr() != other.borrow(py).context.borrow(py).as_ptr() { - return Some("Cannot link modules from different contexts. Modules are untouched.".to_string()); + return Some( + "Cannot link modules from different contexts. Modules are untouched.".to_string(), + ); } unsafe { let result = LLVMLinkModules2(self.module.as_ptr(), other.borrow(py).module.as_ptr()); From 5943769717f909adbc5d40fb7e49cba71629fa92 Mon Sep 17 00:00:00 2001 From: Ian Davis Date: Fri, 13 Sep 2024 15:29:00 -0700 Subject: [PATCH 03/13] Lint --- pyqir/src/module.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyqir/src/module.rs b/pyqir/src/module.rs index fcdfa2cc..504d5982 100644 --- a/pyqir/src/module.rs +++ b/pyqir/src/module.rs @@ -283,7 +283,7 @@ impl Module { // and we'll get a segfault if we drop it. forget(other); if result == 0 { - return None; + None } else { // in the future we need to return a proper error message // using `LLVMContextSetDiagnosticHandler`. This is a lot of work From 3c1bc8388a2647b26f745f88c36f23f2e728f914 Mon Sep 17 00:00:00 2001 From: Ian Davis Date: Mon, 16 Sep 2024 09:01:52 -0700 Subject: [PATCH 04/13] Python formatting --- pyqir/tests/test_module_linking.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pyqir/tests/test_module_linking.py b/pyqir/tests/test_module_linking.py index bae3a809..50b25a8b 100644 --- a/pyqir/tests/test_module_linking.py +++ b/pyqir/tests/test_module_linking.py @@ -21,11 +21,13 @@ def test_link_modules_with_same_context() -> None: expected_ir = str(Path("tests/combined_module.ll").read_text()) assert actual_ir == expected_ir + def test_link_modules_with_different_contexts() -> None: ir = Path("tests/random_bit.ll").read_text() dest = Module.from_ir(Context(), ir) ir = Path("tests/5_bit_random_number.ll").read_text() src = Module.from_ir(Context(), ir) message = dest.link(src) - assert message == "Cannot link modules from different contexts. Modules are untouched." - + assert ( + message == "Cannot link modules from different contexts. Modules are untouched." + ) From 730ee5aa8b580d51985e30987963a7c1cc90c45a Mon Sep 17 00:00:00 2001 From: Ian Davis Date: Mon, 16 Sep 2024 10:37:17 -0700 Subject: [PATCH 05/13] Add diagnostic handler --- pyqir/pyqir/_native.pyi | 5 +- pyqir/src/module.rs | 28 ++++++----- pyqir/tests/profile_v1.0_compat.ll | 34 +++++++++++++ pyqir/tests/profile_v1.1_compat.ll | 34 +++++++++++++ pyqir/tests/profile_v2.0_compat.ll | 34 +++++++++++++ pyqir/tests/test_module_linking.py | 78 +++++++++++++++++++++++++++--- qirlib/src/context.rs | 26 ++++++++++ qirlib/src/lib.rs | 2 + 8 files changed, 218 insertions(+), 23 deletions(-) create mode 100644 pyqir/tests/profile_v1.0_compat.ll create mode 100644 pyqir/tests/profile_v1.1_compat.ll create mode 100644 pyqir/tests/profile_v2.0_compat.ll create mode 100644 qirlib/src/context.rs diff --git a/pyqir/pyqir/_native.pyi b/pyqir/pyqir/_native.pyi index de8357d8..4b151057 100644 --- a/pyqir/pyqir/_native.pyi +++ b/pyqir/pyqir/_native.pyi @@ -681,13 +681,12 @@ class Module: """Converts this module into an LLVM IR string.""" ... - def link(self, other: Module) -> Optional[str]: + def link(self, other: Module) -> None: """ Link the supplied module into the current module. Destroys the supplied module. - :returns: An error message if linking failed or `None` if linking succeeded. - :rtype: typing.Optional[str] + :raises: An error if linking failed. """ ... diff --git a/pyqir/src/module.rs b/pyqir/src/module.rs index 504d5982..d2d868a9 100644 --- a/pyqir/src/module.rs +++ b/pyqir/src/module.rs @@ -22,7 +22,7 @@ use llvm_sys::{ LLVMLinkage, LLVMModule, }; use pyo3::{exceptions::PyValueError, prelude::*, pyclass::CompareOp, types::PyBytes}; -use qirlib::module::FlagBehavior; +use qirlib::{context::set_diagnostic_handler, module::FlagBehavior}; use std::{ collections::hash_map::DefaultHasher, ffi::CString, @@ -269,26 +269,30 @@ impl Module { /// Link the supplied module into the current module. /// Destroys the supplied module. /// - /// :returns: An error message if linking failed or `None` if linking succeeded. - /// :rtype: typing.Optional[str] - pub fn link(&self, other: Py, py: Python) -> Option { - if self.context.borrow(py).as_ptr() != other.borrow(py).context.borrow(py).as_ptr() { - return Some( + /// :raises: An error if linking failed. + pub fn link(&self, other: Py, py: Python) -> PyResult<()> { + let context = self.context.borrow(py).as_ptr(); + if context != other.borrow(py).context.borrow(py).as_ptr() { + return Err(PyValueError::new_err( "Cannot link modules from different contexts. Modules are untouched.".to_string(), - ); + )); } unsafe { + let mut char_ptr: *mut ::core::ffi::c_char = ptr::null_mut(); + let char_ptr_ptr = &mut char_ptr as *mut *mut ::core::ffi::c_char + as *mut *mut ::core::ffi::c_void + as *mut ::core::ffi::c_void; + + set_diagnostic_handler(context, char_ptr_ptr); let result = LLVMLinkModules2(self.module.as_ptr(), other.borrow(py).module.as_ptr()); // `forget` the other module. LLVM has destroyed it // and we'll get a segfault if we drop it. forget(other); if result == 0 { - None + Ok(()) } else { - // in the future we need to return a proper error message - // using `LLVMContextSetDiagnosticHandler`. This is a lot of work - // to get right, so we'll leave it for now. - Some("Failed to link modules".to_string()) + let error = Message::from_raw(char_ptr); + return Err(PyValueError::new_err(error.to_str().unwrap().to_string())); } } } diff --git a/pyqir/tests/profile_v1.0_compat.ll b/pyqir/tests/profile_v1.0_compat.ll new file mode 100644 index 00000000..9a1ef1c2 --- /dev/null +++ b/pyqir/tests/profile_v1.0_compat.ll @@ -0,0 +1,34 @@ +; ModuleID = 'OneDotZero' +%Result = type opaque +%Qubit = type opaque + +define void @OneDotZero() #0 { +block_0: + call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 0 to %Qubit*)) + call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 1 to %Qubit*)) + call void @__quantum__qis__cz__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Qubit* inttoptr (i64 0 to %Qubit*)) + call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 1 to %Qubit*)) + call void @__quantum__qis__m__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 0 to %Result*)) + call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null) + ret void +} + +declare void @__quantum__qis__h__body(%Qubit*) + +declare void @__quantum__qis__cz__body(%Qubit*, %Qubit*) + +declare void @__quantum__rt__result_record_output(%Result*, i8*) + +declare void @__quantum__qis__m__body(%Qubit*, %Result*) #1 + +attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="base_profile" "required_num_qubits"="2" "required_num_results"="1" } +attributes #1 = { "irreversible" } + +; module flags + +!llvm.module.flags = !{!0, !1, !2, !3} + +!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} \ No newline at end of file diff --git a/pyqir/tests/profile_v1.1_compat.ll b/pyqir/tests/profile_v1.1_compat.ll new file mode 100644 index 00000000..1bf726d1 --- /dev/null +++ b/pyqir/tests/profile_v1.1_compat.ll @@ -0,0 +1,34 @@ +; ModuleID = 'OneDotOne' +%Result = type opaque +%Qubit = type opaque + +define void @OneDotOne() #0 { +block_0: + call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 0 to %Qubit*)) + call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 1 to %Qubit*)) + call void @__quantum__qis__cz__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Qubit* inttoptr (i64 0 to %Qubit*)) + call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 1 to %Qubit*)) + call void @__quantum__qis__m__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 0 to %Result*)) + call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null) + ret void +} + +declare void @__quantum__qis__h__body(%Qubit*) + +declare void @__quantum__qis__cz__body(%Qubit*, %Qubit*) + +declare void @__quantum__rt__result_record_output(%Result*, i8*) + +declare void @__quantum__qis__m__body(%Qubit*, %Result*) #1 + +attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="base_profile" "required_num_qubits"="2" "required_num_results"="1" } +attributes #1 = { "irreversible" } + +; module flags + +!llvm.module.flags = !{!0, !1, !2, !3} + +!0 = !{i32 1, !"qir_major_version", i32 1} +!1 = !{i32 7, !"qir_minor_version", i32 1} +!2 = !{i32 1, !"dynamic_qubit_management", i1 false} +!3 = !{i32 1, !"dynamic_result_management", i1 false} \ No newline at end of file diff --git a/pyqir/tests/profile_v2.0_compat.ll b/pyqir/tests/profile_v2.0_compat.ll new file mode 100644 index 00000000..c51531ca --- /dev/null +++ b/pyqir/tests/profile_v2.0_compat.ll @@ -0,0 +1,34 @@ +; ModuleID = 'TwoDotZero' +%Result = type opaque +%Qubit = type opaque + +define void @TwoDotZero() #0 { +block_0: + call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 0 to %Qubit*)) + call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 1 to %Qubit*)) + call void @__quantum__qis__cz__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Qubit* inttoptr (i64 0 to %Qubit*)) + call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 1 to %Qubit*)) + call void @__quantum__qis__m__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 0 to %Result*)) + call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null) + ret void +} + +declare void @__quantum__qis__h__body(%Qubit*) + +declare void @__quantum__qis__cz__body(%Qubit*, %Qubit*) + +declare void @__quantum__rt__result_record_output(%Result*, i8*) + +declare void @__quantum__qis__m__body(%Qubit*, %Result*) #1 + +attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="base_profile" "required_num_qubits"="2" "required_num_results"="1" } +attributes #1 = { "irreversible" } + +; module flags + +!llvm.module.flags = !{!0, !1, !2, !3} + +!0 = !{i32 1, !"qir_major_version", i32 2} +!1 = !{i32 7, !"qir_minor_version", i32 0} +!2 = !{i32 1, !"dynamic_qubit_management", i1 false} +!3 = !{i32 1, !"dynamic_result_management", i1 false} \ No newline at end of file diff --git a/pyqir/tests/test_module_linking.py b/pyqir/tests/test_module_linking.py index 50b25a8b..02e191f7 100644 --- a/pyqir/tests/test_module_linking.py +++ b/pyqir/tests/test_module_linking.py @@ -3,31 +3,93 @@ from pathlib import Path +import pytest + +current_file_path = Path(__file__) +# Get the directory of the current file +current_dir = current_file_path.parent + from pyqir import ( Context, Module, ) +def read_file(file_name: str) -> str: + return Path(current_dir / file_name).read_text(encoding="utf-8") + + def test_link_modules_with_same_context() -> None: context = Context() - ir = Path("tests/random_bit.ll").read_text() + ir = read_file("random_bit.ll") dest = Module.from_ir(context, ir) - ir = Path("tests/5_bit_random_number.ll").read_text() + ir = read_file("5_bit_random_number.ll") src = Module.from_ir(context, ir) - assert dest.link(src) is None + dest.link(src) assert dest.verify() is None actual_ir = str(dest) - expected_ir = str(Path("tests/combined_module.ll").read_text()) + expected_ir = str(read_file("combined_module.ll")) assert actual_ir == expected_ir def test_link_modules_with_different_contexts() -> None: - ir = Path("tests/random_bit.ll").read_text() + ir = read_file("random_bit.ll") dest = Module.from_ir(Context(), ir) - ir = Path("tests/5_bit_random_number.ll").read_text() + ir = read_file("5_bit_random_number.ll") src = Module.from_ir(Context(), ir) - message = dest.link(src) + with pytest.raises(ValueError) as ex: + dest.link(src) + assert ( + str(ex.value) + == "Cannot link modules from different contexts. Modules are untouched." + ) + + +def test_link_module_with_src_minor_version_less() -> None: + context = Context() + ir = read_file("profile_v1.0_compat.ll") + dest = Module.from_ir(context, ir) + ir = read_file("profile_v1.1_compat.ll") + src = Module.from_ir(context, ir) + dest.link(src) + assert dest.get_flag("qir_minor_version").value.value == 1 + + +def test_link_module_with_src_minor_version_greater() -> None: + context = Context() + ir = read_file("profile_v1.1_compat.ll") + dest = Module.from_ir(context, ir) + ir = read_file("profile_v1.0_compat.ll") + src = Module.from_ir(context, ir) + dest.link(src) + assert dest.get_flag("qir_minor_version").value.value == 1 + + +def test_link_module_with_src_major_version_less() -> None: + context = Context() + ir = read_file("profile_v2.0_compat.ll") + dest = Module.from_ir(context, ir) + ir = read_file("profile_v1.0_compat.ll") + src = Module.from_ir(context, ir) + with pytest.raises(ValueError) as ex: + dest.link(src) + + assert ( + "linking module flags 'qir_major_version': IDs have conflicting values" + in str(ex) + ) + + +def test_link_module_with_src_major_version_greater() -> None: + context = Context() + ir = read_file("profile_v1.0_compat.ll") + dest = Module.from_ir(context, ir) + ir = read_file("profile_v2.0_compat.ll") + src = Module.from_ir(context, ir) + with pytest.raises(ValueError) as ex: + dest.link(src) + assert ( - message == "Cannot link modules from different contexts. Modules are untouched." + "linking module flags 'qir_major_version': IDs have conflicting values" + in str(ex) ) diff --git a/qirlib/src/context.rs b/qirlib/src/context.rs new file mode 100644 index 00000000..b30d3136 --- /dev/null +++ b/qirlib/src/context.rs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use llvm_sys::{ + core::{LLVMContextSetDiagnosticHandler, LLVMGetDiagInfoDescription, LLVMGetDiagInfoSeverity}, + prelude::{LLVMContextRef, LLVMDiagnosticInfoRef}, + LLVMDiagnosticSeverity, +}; + +pub fn set_diagnostic_handler(context: LLVMContextRef, output_ptr: *mut core::ffi::c_void) { + unsafe { LLVMContextSetDiagnosticHandler(context, Some(diagnostic_handler), output_ptr) }; +} + +pub(crate) extern "C" fn diagnostic_handler( + diagnostic_info: LLVMDiagnosticInfoRef, + output: *mut ::core::ffi::c_void, +) { + unsafe { + let severity = LLVMGetDiagInfoSeverity(diagnostic_info); + if severity == LLVMDiagnosticSeverity::LLVMDSError { + let c_char_output = + output as *mut *mut ::core::ffi::c_void as *mut *mut ::core::ffi::c_char; + *c_char_output = LLVMGetDiagInfoDescription(diagnostic_info) + } + } +} diff --git a/qirlib/src/lib.rs b/qirlib/src/lib.rs index 5711e787..038670f9 100644 --- a/qirlib/src/lib.rs +++ b/qirlib/src/lib.rs @@ -20,6 +20,8 @@ extern crate llvm_sys_140 as llvm_sys; #[cfg(not(feature = "no-llvm-linking"))] pub mod builder; #[cfg(not(feature = "no-llvm-linking"))] +pub mod context; +#[cfg(not(feature = "no-llvm-linking"))] pub(crate) mod llvm_wrapper; #[cfg(not(feature = "no-llvm-linking"))] pub mod metadata; From 2814f55c7a0fcaba57e56e1798c2b9b8ed77c848 Mon Sep 17 00:00:00 2001 From: Ian Davis Date: Mon, 16 Sep 2024 10:44:54 -0700 Subject: [PATCH 06/13] Lints --- qirlib/src/context.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qirlib/src/context.rs b/qirlib/src/context.rs index b30d3136..3ff5bc5d 100644 --- a/qirlib/src/context.rs +++ b/qirlib/src/context.rs @@ -7,7 +7,7 @@ use llvm_sys::{ LLVMDiagnosticSeverity, }; -pub fn set_diagnostic_handler(context: LLVMContextRef, output_ptr: *mut core::ffi::c_void) { +pub unsafe fn set_diagnostic_handler(context: LLVMContextRef, output_ptr: *mut core::ffi::c_void) { unsafe { LLVMContextSetDiagnosticHandler(context, Some(diagnostic_handler), output_ptr) }; } @@ -19,7 +19,7 @@ pub(crate) extern "C" fn diagnostic_handler( let severity = LLVMGetDiagInfoSeverity(diagnostic_info); if severity == LLVMDiagnosticSeverity::LLVMDSError { let c_char_output = - output as *mut *mut ::core::ffi::c_void as *mut *mut ::core::ffi::c_char; + (output as *mut *mut ::core::ffi::c_void).cast::<*mut ::core::ffi::c_char>(); *c_char_output = LLVMGetDiagInfoDescription(diagnostic_info) } } From 01a467f51e7ec98f98205d622e4317892012a2db Mon Sep 17 00:00:00 2001 From: Ian Davis Date: Mon, 16 Sep 2024 10:49:22 -0700 Subject: [PATCH 07/13] More lint --- qirlib/src/context.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qirlib/src/context.rs b/qirlib/src/context.rs index 3ff5bc5d..985c6e19 100644 --- a/qirlib/src/context.rs +++ b/qirlib/src/context.rs @@ -19,8 +19,8 @@ pub(crate) extern "C" fn diagnostic_handler( let severity = LLVMGetDiagInfoSeverity(diagnostic_info); if severity == LLVMDiagnosticSeverity::LLVMDSError { let c_char_output = - (output as *mut *mut ::core::ffi::c_void).cast::<*mut ::core::ffi::c_char>(); - *c_char_output = LLVMGetDiagInfoDescription(diagnostic_info) + output.cast::<*mut ::core::ffi::c_void>().cast::<*mut ::core::ffi::c_char>(); + *c_char_output = LLVMGetDiagInfoDescription(diagnostic_info); } } } From ebade3621267fb773d82e78b709c85ea9b507c54 Mon Sep 17 00:00:00 2001 From: Ian Davis Date: Mon, 16 Sep 2024 10:50:33 -0700 Subject: [PATCH 08/13] Formatting --- qirlib/src/context.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/qirlib/src/context.rs b/qirlib/src/context.rs index 985c6e19..be633d20 100644 --- a/qirlib/src/context.rs +++ b/qirlib/src/context.rs @@ -18,8 +18,9 @@ pub(crate) extern "C" fn diagnostic_handler( unsafe { let severity = LLVMGetDiagInfoSeverity(diagnostic_info); if severity == LLVMDiagnosticSeverity::LLVMDSError { - let c_char_output = - output.cast::<*mut ::core::ffi::c_void>().cast::<*mut ::core::ffi::c_char>(); + let c_char_output = output + .cast::<*mut ::core::ffi::c_void>() + .cast::<*mut ::core::ffi::c_char>(); *c_char_output = LLVMGetDiagInfoDescription(diagnostic_info); } } From 80e9fc28fe11b6c0f884b363d436dc9035fbf2f5 Mon Sep 17 00:00:00 2001 From: Ian Davis Date: Mon, 16 Sep 2024 10:55:52 -0700 Subject: [PATCH 09/13] Linting --- pyqir/src/module.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pyqir/src/module.rs b/pyqir/src/module.rs index d2d868a9..1d670bae 100644 --- a/pyqir/src/module.rs +++ b/pyqir/src/module.rs @@ -278,12 +278,12 @@ impl Module { )); } unsafe { - let mut char_ptr: *mut ::core::ffi::c_char = ptr::null_mut(); - let char_ptr_ptr = &mut char_ptr as *mut *mut ::core::ffi::c_char - as *mut *mut ::core::ffi::c_void - as *mut ::core::ffi::c_void; + let mut c_char_output: *mut ::core::ffi::c_char = ptr::null_mut(); + let output = (&mut char_ptr as *mut *mut ::core::ffi::c_char + as *mut *mut ::core::ffi::c_void) + .cast::<::core::ffi::c_void>(); - set_diagnostic_handler(context, char_ptr_ptr); + set_diagnostic_handler(context, output); let result = LLVMLinkModules2(self.module.as_ptr(), other.borrow(py).module.as_ptr()); // `forget` the other module. LLVM has destroyed it // and we'll get a segfault if we drop it. From 0b873d2f2ffbff8e5e1a18d61a4b7cbbbd489f2d Mon Sep 17 00:00:00 2001 From: Ian Davis Date: Mon, 16 Sep 2024 10:58:10 -0700 Subject: [PATCH 10/13] Bad rename --- pyqir/src/module.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyqir/src/module.rs b/pyqir/src/module.rs index 1d670bae..a09da051 100644 --- a/pyqir/src/module.rs +++ b/pyqir/src/module.rs @@ -279,7 +279,7 @@ impl Module { } unsafe { let mut c_char_output: *mut ::core::ffi::c_char = ptr::null_mut(); - let output = (&mut char_ptr as *mut *mut ::core::ffi::c_char + let output = (&mut c_char_output as *mut *mut ::core::ffi::c_char as *mut *mut ::core::ffi::c_void) .cast::<::core::ffi::c_void>(); @@ -291,7 +291,7 @@ impl Module { if result == 0 { Ok(()) } else { - let error = Message::from_raw(char_ptr); + let error = Message::from_raw(c_char_output); return Err(PyValueError::new_err(error.to_str().unwrap().to_string())); } } From f068a1ba12981eb4b406144e0f66d84417113d25 Mon Sep 17 00:00:00 2001 From: Ian Davis Date: Mon, 16 Sep 2024 10:59:57 -0700 Subject: [PATCH 11/13] Linting --- pyqir/src/module.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyqir/src/module.rs b/pyqir/src/module.rs index a09da051..4752f4a1 100644 --- a/pyqir/src/module.rs +++ b/pyqir/src/module.rs @@ -279,8 +279,8 @@ impl Module { } unsafe { let mut c_char_output: *mut ::core::ffi::c_char = ptr::null_mut(); - let output = (&mut c_char_output as *mut *mut ::core::ffi::c_char - as *mut *mut ::core::ffi::c_void) + let output = (&mut c_char_output as *mut *mut ::core::ffi::c_char) + .cast::<*mut ::core::ffi::c_void>() .cast::<::core::ffi::c_void>(); set_diagnostic_handler(context, output); From b186f5bb002dd7a3b5645675763d891a477045e1 Mon Sep 17 00:00:00 2001 From: Ian Davis Date: Mon, 16 Sep 2024 11:01:52 -0700 Subject: [PATCH 12/13] Lint --- pyqir/src/module.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyqir/src/module.rs b/pyqir/src/module.rs index 4752f4a1..9a4c2796 100644 --- a/pyqir/src/module.rs +++ b/pyqir/src/module.rs @@ -279,7 +279,7 @@ impl Module { } unsafe { let mut c_char_output: *mut ::core::ffi::c_char = ptr::null_mut(); - let output = (&mut c_char_output as *mut *mut ::core::ffi::c_char) + let output = std::ptr::from_mut::<*mut ::core::ffi::c_char>(&mut c_char_output) .cast::<*mut ::core::ffi::c_void>() .cast::<::core::ffi::c_void>(); From 3cef7c74d4301282d39584658b8f9ced3d1e8790 Mon Sep 17 00:00:00 2001 From: Ian Davis Date: Mon, 16 Sep 2024 11:06:54 -0700 Subject: [PATCH 13/13] Linting --- pyqir/src/module.rs | 2 +- pyqir/tests/test_module_linking.py | 13 +++++++++++-- qirlib/src/context.rs | 5 ++++- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/pyqir/src/module.rs b/pyqir/src/module.rs index 9a4c2796..1d6d2c53 100644 --- a/pyqir/src/module.rs +++ b/pyqir/src/module.rs @@ -279,7 +279,7 @@ impl Module { } unsafe { let mut c_char_output: *mut ::core::ffi::c_char = ptr::null_mut(); - let output = std::ptr::from_mut::<*mut ::core::ffi::c_char>(&mut c_char_output) + let output = ::core::ptr::from_mut::<*mut ::core::ffi::c_char>(&mut c_char_output) .cast::<*mut ::core::ffi::c_void>() .cast::<::core::ffi::c_void>(); diff --git a/pyqir/tests/test_module_linking.py b/pyqir/tests/test_module_linking.py index 02e191f7..10c5d94c 100644 --- a/pyqir/tests/test_module_linking.py +++ b/pyqir/tests/test_module_linking.py @@ -3,6 +3,7 @@ from pathlib import Path +import pyqir import pytest current_file_path = Path(__file__) @@ -19,6 +20,14 @@ def read_file(file_name: str) -> str: return Path(current_dir / file_name).read_text(encoding="utf-8") +def get_int_flag_value(module: Module, flag_name: str) -> int: + flag = module.get_flag(flag_name) + assert flag is not None + assert isinstance(flag, pyqir.ConstantAsMetadata) + assert isinstance(flag.value, pyqir.IntConstant) + return flag.value.value + + def test_link_modules_with_same_context() -> None: context = Context() ir = read_file("random_bit.ll") @@ -52,7 +61,7 @@ def test_link_module_with_src_minor_version_less() -> None: ir = read_file("profile_v1.1_compat.ll") src = Module.from_ir(context, ir) dest.link(src) - assert dest.get_flag("qir_minor_version").value.value == 1 + assert get_int_flag_value(dest, "qir_minor_version") == 1 def test_link_module_with_src_minor_version_greater() -> None: @@ -62,7 +71,7 @@ def test_link_module_with_src_minor_version_greater() -> None: ir = read_file("profile_v1.0_compat.ll") src = Module.from_ir(context, ir) dest.link(src) - assert dest.get_flag("qir_minor_version").value.value == 1 + assert get_int_flag_value(dest, "qir_minor_version") == 1 def test_link_module_with_src_major_version_less() -> None: diff --git a/qirlib/src/context.rs b/qirlib/src/context.rs index be633d20..96b915c6 100644 --- a/qirlib/src/context.rs +++ b/qirlib/src/context.rs @@ -7,7 +7,10 @@ use llvm_sys::{ LLVMDiagnosticSeverity, }; -pub unsafe fn set_diagnostic_handler(context: LLVMContextRef, output_ptr: *mut core::ffi::c_void) { +pub unsafe fn set_diagnostic_handler( + context: LLVMContextRef, + output_ptr: *mut ::core::ffi::c_void, +) { unsafe { LLVMContextSetDiagnosticHandler(context, Some(diagnostic_handler), output_ptr) }; }