Skip to content

Commit

Permalink
Add link function to Module (#293)
Browse files Browse the repository at this point in the history
  • Loading branch information
idavis authored Sep 16, 2024
1 parent 3ace643 commit feeef97
Show file tree
Hide file tree
Showing 11 changed files with 415 additions and 1 deletion.
9 changes: 9 additions & 0 deletions pyqir/pyqir/_native.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -681,6 +681,15 @@ class Module:
"""Converts this module into an LLVM IR string."""
...

def link(self, other: Module) -> None:
"""
Link the supplied module into the current module.
Destroys the supplied module.
:raises: An error if linking failed.
"""
...

class ModuleFlagBehavior(Enum):
"""Module flag behavior choices"""

Expand Down
35 changes: 34 additions & 1 deletion pyqir/src/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand All @@ -17,10 +18,11 @@ 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 qirlib::{context::set_diagnostic_handler, module::FlagBehavior};
use std::{
collections::hash_map::DefaultHasher,
ffi::CString,
Expand Down Expand Up @@ -263,6 +265,37 @@ impl Module {
.to_string()
}
}

/// Link the supplied module into the current module.
/// Destroys the supplied module.
///
/// :raises: An error if linking failed.
pub fn link(&self, other: Py<Module>, 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 c_char_output: *mut ::core::ffi::c_char = ptr::null_mut();
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>();

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.
forget(other);
if result == 0 {
Ok(())
} else {
let error = Message::from_raw(c_char_output);
return Err(PyValueError::new_err(error.to_str().unwrap().to_string()));
}
}
}
}

impl Deref for Module {
Expand Down
44 changes: 44 additions & 0 deletions pyqir/tests/5_bit_random_number.ll
Original file line number Diff line number Diff line change
@@ -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}
56 changes: 56 additions & 0 deletions pyqir/tests/combined_module.ll
Original file line number Diff line number Diff line change
@@ -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}
34 changes: 34 additions & 0 deletions pyqir/tests/profile_v1.0_compat.ll
Original file line number Diff line number Diff line change
@@ -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}
34 changes: 34 additions & 0 deletions pyqir/tests/profile_v1.1_compat.ll
Original file line number Diff line number Diff line change
@@ -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}
34 changes: 34 additions & 0 deletions pyqir/tests/profile_v2.0_compat.ll
Original file line number Diff line number Diff line change
@@ -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}
34 changes: 34 additions & 0 deletions pyqir/tests/random_bit.ll
Original file line number Diff line number Diff line change
@@ -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}
Loading

0 comments on commit feeef97

Please sign in to comment.