From 7a7f9427ba65215d4b0de2a9de2c818cdf18fe45 Mon Sep 17 00:00:00 2001 From: Ian Davis Date: Thu, 19 Oct 2023 14:15:23 -0700 Subject: [PATCH 1/7] Adding ability to access and iterate over function attributes. --- pyqir/pyqir/_native.pyi | 17 ++++++++++++ pyqir/src/values.rs | 60 ++++++++++++++++++++++++++++++++--------- qirlib/src/values.rs | 54 ++++++++++++++++++++++++++++++++++++- 3 files changed, 118 insertions(+), 13 deletions(-) diff --git a/pyqir/pyqir/_native.pyi b/pyqir/pyqir/_native.pyi index 42be7609..2656bf41 100644 --- a/pyqir/pyqir/_native.pyi +++ b/pyqir/pyqir/_native.pyi @@ -19,6 +19,11 @@ class ArrayType(Type): class Attribute: """An attribute.""" + @property + def string_kind(self) -> str: + """The kind of this attribute as a string.""" + ... + @property def string_value(self) -> Optional[str]: """The value of this attribute as a string, or `None` if this is not a string attribute.""" @@ -44,6 +49,15 @@ class AttributeList: """The attributes for the function itself.""" ... +class AttributeIterator: + """An iterator of attributes for a specific part of a function.""" + + def __iter__(self) -> object: + ... + + def __next__(self) -> Optional[Attribute]: + ... + class AttributeSet: """A set of attributes for a specific part of a function.""" @@ -64,6 +78,9 @@ class AttributeSet: """ ... + def __iter__(self) -> AttributeIterator: + ... + class BasicBlock(Value): """A basic block.""" diff --git a/pyqir/src/values.rs b/pyqir/src/values.rs index 39e6c3cd..090b2256 100644 --- a/pyqir/src/values.rs +++ b/pyqir/src/values.rs @@ -23,7 +23,7 @@ use pyo3::{ types::{PyBytes, PyLong, PyString}, PyRef, }; -use qirlib::values; +use qirlib::values::{self, get_string_attribute_kind, get_string_attribute_value}; use std::{ borrow::Borrow, collections::hash_map::DefaultHasher, @@ -33,6 +33,7 @@ use std::{ ops::Deref, ptr::NonNull, slice, str, + vec::IntoIter, }; /// A value. @@ -522,21 +523,20 @@ pub(crate) struct Attribute(LLVMAttributeRef); #[pymethods] impl Attribute { + /// The id of this attribute as a string. + /// + /// :type: str + #[getter] + fn string_kind(&self) -> String { + unsafe { get_string_attribute_kind(self.0) } + } + /// The value of this attribute as a string, or `None` if this is not a string attribute. /// /// :type: typing.Optional[str] #[getter] - fn string_value(&self) -> Option<&str> { - unsafe { - if LLVMIsStringAttribute(self.0) == 0 { - None - } else { - let mut len = 0; - let value = LLVMGetStringAttributeValue(self.0, &mut len).cast(); - let value = slice::from_raw_parts(value, len.try_into().unwrap()); - Some(str::from_utf8(value).unwrap()) - } - } + fn string_value(&self) -> Option { + unsafe { get_string_attribute_value(self.0) } } } @@ -588,6 +588,24 @@ pub(crate) struct AttributeSet { index: LLVMAttributeIndex, } +/// An iterator of attributes for a specific part of a function. +#[pyclass] +struct AttributeIterator { + iter: IntoIter>, +} + +#[pymethods] +impl AttributeIterator { + fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> { + slf + } + // Returning `None` from `__next__` indicates that that there are no further items. + // and maps to StopIteration + fn __next__(mut slf: PyRefMut<'_, Self>) -> Option> { + slf.iter.next() + } +} + #[pymethods] impl AttributeSet { /// Tests if an attribute is a member of the set. @@ -622,6 +640,24 @@ impl AttributeSet { Ok(Attribute(attr)) } } + + fn __iter__(slf: PyRef<'_, Self>) -> PyResult> { + let function = slf.function.borrow(slf.py()).into_super().into_super(); + + unsafe { + let attrs = qirlib::values::get_attributes(function.as_ptr(), slf.index); + let items: Vec> = attrs + .into_iter() + .map(|a| Py::new(slf.py(), Attribute(a)).expect("msg")) + .collect(); + Py::new( + slf.py(), + AttributeIterator { + iter: items.clone().into_iter(), + }, + ) + } + } } #[derive(FromPyObject)] diff --git a/qirlib/src/values.rs b/qirlib/src/values.rs index 36be3bb8..f5774dbd 100644 --- a/qirlib/src/values.rs +++ b/qirlib/src/values.rs @@ -9,7 +9,13 @@ use llvm_sys::{ core::*, prelude::*, LLVMAttributeFunctionIndex, LLVMAttributeIndex, LLVMLinkage, LLVMOpaqueAttributeRef, LLVMOpcode, LLVMTypeKind, LLVMValueKind, }; -use std::{convert::TryFrom, ffi::CStr, ptr::NonNull, str}; +use std::{ + convert::TryFrom, + ffi::CStr, + mem::{ManuallyDrop, MaybeUninit}, + ptr::NonNull, + str, +}; pub unsafe fn qubit(context: LLVMContextRef, id: u64) -> LLVMValueRef { let i64 = LLVMInt64TypeInContext(context); @@ -225,6 +231,52 @@ unsafe fn get_string_attribute( )) } +pub unsafe fn get_attribute_count(function: LLVMValueRef, index: LLVMAttributeIndex) -> usize { + LLVMGetAttributeCountAtIndex(function, index) + .try_into() + .expect("Attribute count larger than usize.") +} + +pub unsafe fn get_string_attribute_kind(attr: *mut LLVMOpaqueAttributeRef) -> String { + let mut len = 0; + let value = LLVMGetStringAttributeKind(attr, &mut len).cast(); + let value = slice::from_raw_parts(value, len.try_into().unwrap()); + str::from_utf8(value) + .expect("Attribute kind is not valid UTF-8.") + .to_string() +} + +pub unsafe fn get_string_attribute_value(attr: *mut LLVMOpaqueAttributeRef) -> Option { + if LLVMIsStringAttribute(attr) == 0 { + None + } else { + let mut len = 0; + let value = LLVMGetStringAttributeValue(attr, &mut len).cast(); + let value = slice::from_raw_parts(value, len.try_into().unwrap()); + Some( + str::from_utf8(value) + .expect("Attribute kind is not valid UTF-8.") + .to_string(), + ) + } +} + +pub unsafe fn get_attributes( + function: LLVMValueRef, + index: LLVMAttributeIndex, +) -> Vec<*mut LLVMOpaqueAttributeRef> { + let count = get_attribute_count(function, index); + let attrs: Vec> = Vec::with_capacity(count); + let mut attrs = ManuallyDrop::new(attrs); + for _ in 0..count { + attrs.push(MaybeUninit::uninit()); + } + + LLVMGetAttributesAtIndex(function, index, attrs.as_mut_ptr() as *mut _); + + Vec::from_raw_parts(attrs.as_mut_ptr() as *mut _, attrs.len(), attrs.capacity()) +} + unsafe fn pointer_to_int(value: LLVMValueRef) -> Option { let ty = LLVMTypeOf(value); if LLVMGetTypeKind(ty) == LLVMTypeKind::LLVMPointerTypeKind && LLVMIsConstant(value) != 0 { From a63bfadfc42f7873dec45b3fc117a8955089c31d Mon Sep 17 00:00:00 2001 From: Ian Davis Date: Thu, 19 Oct 2023 14:54:24 -0700 Subject: [PATCH 2/7] linting --- pyqir/src/values.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pyqir/src/values.rs b/pyqir/src/values.rs index 090b2256..3d084081 100644 --- a/pyqir/src/values.rs +++ b/pyqir/src/values.rs @@ -646,14 +646,13 @@ impl AttributeSet { unsafe { let attrs = qirlib::values::get_attributes(function.as_ptr(), slf.index); - let items: Vec> = attrs + let items = attrs .into_iter() - .map(|a| Py::new(slf.py(), Attribute(a)).expect("msg")) - .collect(); + .map(|a| Py::new(slf.py(), Attribute(a)).expect("msg")); Py::new( slf.py(), AttributeIterator { - iter: items.clone().into_iter(), + iter: items.collect::>>().into_iter(), }, ) } From 71f34f49092223c2658d29f1c4c3aaaa42d4b40a Mon Sep 17 00:00:00 2001 From: Ian Davis Date: Thu, 19 Oct 2023 15:46:31 -0700 Subject: [PATCH 3/7] Adding tests and linting updates. --- qirlib/src/values.rs | 140 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 138 insertions(+), 2 deletions(-) diff --git a/qirlib/src/values.rs b/qirlib/src/values.rs index f5774dbd..1ea91399 100644 --- a/qirlib/src/values.rs +++ b/qirlib/src/values.rs @@ -266,15 +266,18 @@ pub unsafe fn get_attributes( index: LLVMAttributeIndex, ) -> Vec<*mut LLVMOpaqueAttributeRef> { let count = get_attribute_count(function, index); + if count == 0 { + return Vec::new(); + } let attrs: Vec> = Vec::with_capacity(count); let mut attrs = ManuallyDrop::new(attrs); for _ in 0..count { attrs.push(MaybeUninit::uninit()); } - LLVMGetAttributesAtIndex(function, index, attrs.as_mut_ptr() as *mut _); + LLVMGetAttributesAtIndex(function, index, attrs.as_mut_ptr().cast()); - Vec::from_raw_parts(attrs.as_mut_ptr() as *mut _, attrs.len(), attrs.capacity()) + Vec::from_raw_parts(attrs.as_mut_ptr().cast(), attrs.len(), attrs.capacity()) } unsafe fn pointer_to_int(value: LLVMValueRef) -> Option { @@ -322,3 +325,136 @@ mod tests { assert_reference_ir("module/many_required_qubits_results", 5, 7, |_| ()); } } + +#[cfg(test)] +mod string_attribute_tests { + use std::ffi::CString; + + use llvm_sys::{ + core::{ + LLVMAddFunction, LLVMBuildRetVoid, LLVMContextCreate, LLVMCreateBuilderInContext, + LLVMModuleCreateWithNameInContext, LLVMVoidTypeInContext, + LLVMDisposeModule, LLVMContextDispose + }, + LLVMAttributeFunctionIndex, LLVMContext, LLVMModule, LLVMValue, LLVMAttributeReturnIndex, LLVMAttributeIndex, + }; + + use crate::values::get_attributes; + + use super::{add_string_attribute, get_attribute_count}; + + fn with_function_attributes(f: impl Fn(*mut LLVMContext, *mut LLVMModule, *mut LLVMValue)) { + unsafe { + // setup + let context = LLVMContextCreate(); + let module_name = CString::new("test_module").unwrap(); + let module = LLVMModuleCreateWithNameInContext(module_name.as_ptr(), context); + let function_name = CString::new("test_func").unwrap(); + let function = LLVMAddFunction( + module, + function_name.as_ptr(), + LLVMVoidTypeInContext(context), + ); + add_string_attribute(function, b"entry_point", b""); + add_string_attribute(function, b"required_num_qubits", b"1"); + add_string_attribute(function, b"required_num_results", b"2"); + add_string_attribute(function, b"qir_profiles", b"test"); + let builder = LLVMCreateBuilderInContext(context); + LLVMBuildRetVoid(builder); + + // assert + f(context, module, function); + + // teardown + LLVMDisposeModule(module); + LLVMContextDispose(context); + } + } + fn with_no_function_attributes(f: impl Fn(*mut LLVMContext, *mut LLVMModule, *mut LLVMValue)) { + unsafe { + let context = LLVMContextCreate(); + let module_name = CString::new("test_module").unwrap(); + let module = LLVMModuleCreateWithNameInContext(module_name.as_ptr(), context); + let function_name = CString::new("test_func").unwrap(); + let function = LLVMAddFunction( + module, + function_name.as_ptr(), + LLVMVoidTypeInContext(context), + ); + let builder = LLVMCreateBuilderInContext(context); + LLVMBuildRetVoid(builder); + f(context, module, function); + } + } + #[test] + fn get_attribute_count_works_when_function_attrs_exist() { + unsafe { + with_function_attributes(|_, _, f| { + let count = get_attribute_count(f, LLVMAttributeFunctionIndex); + assert!(count == 4); + }); + } + } + #[test] + fn get_attribute_count_works_when_function_attrs_dont_exist() { + unsafe { + with_no_function_attributes(|_, _, f| { + let count = get_attribute_count(f, LLVMAttributeFunctionIndex); + assert!(count == 0); + }); + } + } + #[test] + fn get_attribute_count_works_when_return_attrs_dont_exist() { + unsafe { + with_no_function_attributes(|_, _, f| { + let count = get_attribute_count(f, LLVMAttributeReturnIndex); + assert!(count == 0); + }); + } + } + #[test] + fn get_attribute_count_works_when_param_attrs_dont_exist() { + unsafe { + with_no_function_attributes(|_, _, f| { + const INVALID_PARAM_ID: LLVMAttributeIndex = 1; + let count = get_attribute_count(f, INVALID_PARAM_ID); + assert!(count == 0); + }); + } + } + #[test] + fn iteration_works_when_function_attrs_dont_exist() { + unsafe { + with_no_function_attributes(|_, _, f| { + let attrs = get_attributes(f, LLVMAttributeFunctionIndex); + for _ in attrs { + panic!("Should not have any attributes") + } + }); + } + } + #[test] + fn iteration_works_when_return_attrs_dont_exist() { + unsafe { + with_no_function_attributes(|_, _, f| { + let attrs = get_attributes(f, LLVMAttributeReturnIndex); + for _ in attrs { + panic!("Should not have any attributes") + } + }); + } + } + #[test] + fn iteration_works_when_param_attrs_dont_exist() { + unsafe { + with_no_function_attributes(|_, _, f| { + const INVALID_PARAM_ID: LLVMAttributeIndex = 1; + let attrs = get_attributes(f, INVALID_PARAM_ID); + for _ in attrs { + panic!("Should not have any attributes") + } + }); + } + } +} From fb5d4298f109a3a485b80bfa94bffbcb18d57eea Mon Sep 17 00:00:00 2001 From: Ian Davis Date: Thu, 19 Oct 2023 16:05:42 -0700 Subject: [PATCH 4/7] Cleaning up tests. --- qirlib/src/values.rs | 194 +++++++++++++++++++++++++++++-------------- 1 file changed, 130 insertions(+), 64 deletions(-) diff --git a/qirlib/src/values.rs b/qirlib/src/values.rs index 1ea91399..fb47a5b2 100644 --- a/qirlib/src/values.rs +++ b/qirlib/src/values.rs @@ -332,20 +332,23 @@ mod string_attribute_tests { use llvm_sys::{ core::{ - LLVMAddFunction, LLVMBuildRetVoid, LLVMContextCreate, LLVMCreateBuilderInContext, - LLVMModuleCreateWithNameInContext, LLVMVoidTypeInContext, - LLVMDisposeModule, LLVMContextDispose + LLVMAddFunction, LLVMBuildRetVoid, LLVMContextCreate, LLVMContextDispose, + LLVMCreateBuilderInContext, LLVMDisposeModule, LLVMModuleCreateWithNameInContext, + LLVMVoidTypeInContext, }, - LLVMAttributeFunctionIndex, LLVMContext, LLVMModule, LLVMValue, LLVMAttributeReturnIndex, LLVMAttributeIndex, + LLVMAttributeFunctionIndex, LLVMAttributeIndex, LLVMAttributeReturnIndex, LLVMContext, + LLVMModule, LLVMValue, }; use crate::values::get_attributes; use super::{add_string_attribute, get_attribute_count}; - fn with_function_attributes(f: impl Fn(*mut LLVMContext, *mut LLVMModule, *mut LLVMValue)) { + fn setup_expect( + setup: impl Fn(*mut LLVMContext, *mut LLVMModule, *mut LLVMValue), + expect: impl Fn(*mut LLVMValue), + ) { unsafe { - // setup let context = LLVMContextCreate(); let module_name = CString::new("test_module").unwrap(); let module = LLVMModuleCreateWithNameInContext(module_name.as_ptr(), context); @@ -355,106 +358,169 @@ mod string_attribute_tests { function_name.as_ptr(), LLVMVoidTypeInContext(context), ); - add_string_attribute(function, b"entry_point", b""); - add_string_attribute(function, b"required_num_qubits", b"1"); - add_string_attribute(function, b"required_num_results", b"2"); - add_string_attribute(function, b"qir_profiles", b"test"); let builder = LLVMCreateBuilderInContext(context); LLVMBuildRetVoid(builder); - - // assert - f(context, module, function); - - // teardown + setup(context, module, function); + expect(function); LLVMDisposeModule(module); LLVMContextDispose(context); } } - fn with_no_function_attributes(f: impl Fn(*mut LLVMContext, *mut LLVMModule, *mut LLVMValue)) { + #[test] + fn get_attribute_count_works_when_function_attrs_exist() { unsafe { - let context = LLVMContextCreate(); - let module_name = CString::new("test_module").unwrap(); - let module = LLVMModuleCreateWithNameInContext(module_name.as_ptr(), context); - let function_name = CString::new("test_func").unwrap(); - let function = LLVMAddFunction( - module, - function_name.as_ptr(), - LLVMVoidTypeInContext(context), + setup_expect( + |_, _, function| { + add_string_attribute(function, b"entry_point", b""); + add_string_attribute(function, b"required_num_qubits", b"1"); + add_string_attribute(function, b"required_num_results", b"2"); + add_string_attribute(function, b"qir_profiles", b"test"); + }, + |f| { + let count = get_attribute_count(f, LLVMAttributeFunctionIndex); + assert!(count == 4); + }, ); - let builder = LLVMCreateBuilderInContext(context); - LLVMBuildRetVoid(builder); - f(context, module, function); } } #[test] - fn get_attribute_count_works_when_function_attrs_exist() { + fn attributes_with_kind_only_have_empty_string_values() { + unsafe { + setup_expect( + |_, _, function| { + add_string_attribute(function, b"entry_point", b""); + }, + |f| { + let count = get_attribute_count(f, LLVMAttributeFunctionIndex); + assert!(count == 1); + let attrs = get_attributes(f, LLVMAttributeFunctionIndex); + for attr in attrs { + if let Some(value) = super::get_string_attribute_value(attr) { + assert_eq!(value, ""); + } else { + panic!("Should have a value"); + } + } + }, + ); + } + } + #[test] + fn attributes_with_kind_only_have_key_matching_kind() { + unsafe { + setup_expect( + |_, _, function| { + add_string_attribute(function, b"entry_point", b""); + }, + |f| { + let count = get_attribute_count(f, LLVMAttributeFunctionIndex); + assert!(count == 1); + let attrs = get_attributes(f, LLVMAttributeFunctionIndex); + for attr in attrs { + assert_eq!(super::get_string_attribute_kind(attr), "entry_point"); + } + }, + ); + } + } + #[test] + fn attributes_with_key_and_value_have_matching_kind_and_value() { unsafe { - with_function_attributes(|_, _, f| { - let count = get_attribute_count(f, LLVMAttributeFunctionIndex); - assert!(count == 4); - }); + setup_expect( + |_, _, function| { + add_string_attribute(function, b"qir_profiles", b"test"); + }, + |f| { + let count = get_attribute_count(f, LLVMAttributeFunctionIndex); + assert!(count == 1); + let attrs = get_attributes(f, LLVMAttributeFunctionIndex); + for attr in attrs { + assert_eq!(super::get_string_attribute_kind(attr), "qir_profiles"); + assert!(super::get_string_attribute_value(attr).is_some()); + assert_eq!(super::get_string_attribute_value(attr).unwrap(), "test"); + } + }, + ); } } #[test] fn get_attribute_count_works_when_function_attrs_dont_exist() { unsafe { - with_no_function_attributes(|_, _, f| { - let count = get_attribute_count(f, LLVMAttributeFunctionIndex); - assert!(count == 0); - }); + setup_expect( + |_, _, _| {}, + |f| { + let count = get_attribute_count(f, LLVMAttributeFunctionIndex); + assert!(count == 0); + }, + ); } } #[test] fn get_attribute_count_works_when_return_attrs_dont_exist() { unsafe { - with_no_function_attributes(|_, _, f| { - let count = get_attribute_count(f, LLVMAttributeReturnIndex); - assert!(count == 0); - }); + setup_expect( + |_, _, _| {}, + |f| { + let count = get_attribute_count(f, LLVMAttributeReturnIndex); + assert!(count == 0); + }, + ); } } #[test] fn get_attribute_count_works_when_param_attrs_dont_exist() { unsafe { - with_no_function_attributes(|_, _, f| { - const INVALID_PARAM_ID: LLVMAttributeIndex = 1; - let count = get_attribute_count(f, INVALID_PARAM_ID); - assert!(count == 0); - }); + setup_expect( + |_, _, _| {}, + |f| { + const INVALID_PARAM_ID: LLVMAttributeIndex = 1; + let count = get_attribute_count(f, INVALID_PARAM_ID); + assert!(count == 0); + }, + ); } } #[test] fn iteration_works_when_function_attrs_dont_exist() { unsafe { - with_no_function_attributes(|_, _, f| { - let attrs = get_attributes(f, LLVMAttributeFunctionIndex); - for _ in attrs { - panic!("Should not have any attributes") - } - }); + setup_expect( + |_, _, _| {}, + |f| { + let attrs = get_attributes(f, LLVMAttributeFunctionIndex); + for _ in attrs { + panic!("Should not have any attributes") + } + }, + ); } } #[test] fn iteration_works_when_return_attrs_dont_exist() { unsafe { - with_no_function_attributes(|_, _, f| { - let attrs = get_attributes(f, LLVMAttributeReturnIndex); - for _ in attrs { - panic!("Should not have any attributes") - } - }); + setup_expect( + |_, _, _| {}, + |f| { + let attrs = get_attributes(f, LLVMAttributeReturnIndex); + for _ in attrs { + panic!("Should not have any attributes") + } + }, + ); } } #[test] fn iteration_works_when_param_attrs_dont_exist() { unsafe { - with_no_function_attributes(|_, _, f| { - const INVALID_PARAM_ID: LLVMAttributeIndex = 1; - let attrs = get_attributes(f, INVALID_PARAM_ID); - for _ in attrs { - panic!("Should not have any attributes") - } - }); + setup_expect( + |_, _, _| {}, + |f| { + const INVALID_PARAM_ID: LLVMAttributeIndex = 1; + let attrs = get_attributes(f, INVALID_PARAM_ID); + for _ in attrs { + panic!("Should not have any attributes") + } + }, + ); } } } From bd9a6dfba1399fdd15f110bab72aa7f023a4433a Mon Sep 17 00:00:00 2001 From: Ian Davis Date: Fri, 20 Oct 2023 10:53:09 -0700 Subject: [PATCH 5/7] Adding tests, fixing python typing, adding ability to get/set ret and param attrs --- eng/psakefile.ps1 | 2 +- eng/utils.ps1 | 16 ++++- pyqir/pyqir/__init__.py | 3 + pyqir/pyqir/_constants.py | 5 ++ pyqir/pyqir/_native.pyi | 33 ++++++---- pyqir/src/values.rs | 5 +- pyqir/tests/test_string_attributes.py | 87 ++++++++++++++++++++++++++- qirlib/src/values.rs | 55 +++++++++++++---- 8 files changed, 176 insertions(+), 30 deletions(-) create mode 100644 pyqir/pyqir/_constants.py diff --git a/eng/psakefile.ps1 b/eng/psakefile.ps1 index 62a67c06..5b410868 100644 --- a/eng/psakefile.ps1 +++ b/eng/psakefile.ps1 @@ -109,7 +109,7 @@ task check-environment { } Assert ((Test-InVirtualEnvironment) -eq $true) ($env_message -Join ' ') - exec { & $Python -m pip install pip~=23.1 } + exec { & $Python -m pip install pip~=23.3 } } task init -depends check-environment { diff --git a/eng/utils.ps1 b/eng/utils.ps1 index 7cd10775..baf26169 100644 --- a/eng/utils.ps1 +++ b/eng/utils.ps1 @@ -174,11 +174,21 @@ function Test-AllowedToDownloadLlvm { } function Test-InCondaEnvironment { - (Test-Path env:\CONDA_PREFIX) + $found = (Test-Path env:\CONDA_PREFIX) + if ($found) { + $condaPrefix = $env:CONDA_PREFIX + Write-BuildLog "Found conda environment: $condaPrefix" + } + $found } function Test-InVenvEnvironment { - (Test-Path env:\VIRTUAL_ENV) + $found = (Test-Path env:\VIRTUAL_ENV) + if ($found) { + $venv = $env:VIRTUAL_ENV + Write-BuildLog "Found venv environment: $venv" + } + $found } function Test-InVirtualEnvironment { @@ -301,5 +311,5 @@ function install-llvm { if ($clear_cache_var) { Remove-Item -Path Env:QIRLIB_CACHE_DIR } - } + } } diff --git a/pyqir/pyqir/__init__.py b/pyqir/pyqir/__init__.py index 22e3d471..098c9e9b 100644 --- a/pyqir/pyqir/__init__.py +++ b/pyqir/pyqir/__init__.py @@ -59,6 +59,7 @@ from pyqir._simple import SimpleModule from pyqir._entry_point import entry_point from pyqir._basicqis import BasicQisBuilder +from pyqir._constants import AttributeFunctionIndex, AttributeReturnIndex __all__ = [ "ArrayType", @@ -117,4 +118,6 @@ "result_id", "result_type", "result", + "AttributeFunctionIndex", + "AttributeReturnIndex", ] diff --git a/pyqir/pyqir/_constants.py b/pyqir/pyqir/_constants.py new file mode 100644 index 00000000..de3bc5ca --- /dev/null +++ b/pyqir/pyqir/_constants.py @@ -0,0 +1,5 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +AttributeReturnIndex = 0 +AttributeFunctionIndex = 4294967295 # -1 u32 diff --git a/pyqir/pyqir/_native.pyi b/pyqir/pyqir/_native.pyi index 2656bf41..f091e954 100644 --- a/pyqir/pyqir/_native.pyi +++ b/pyqir/pyqir/_native.pyi @@ -2,7 +2,16 @@ # Licensed under the MIT License. from enum import Enum -from typing import Callable, List, Optional, Sequence, Tuple, Union +from typing import ( + Callable, + Iterable, + Iterator, + List, + Optional, + Sequence, + Tuple, + Union, +) class ArrayType(Type): """An array type.""" @@ -23,7 +32,6 @@ class Attribute: def string_kind(self) -> str: """The kind of this attribute as a string.""" ... - @property def string_value(self) -> Optional[str]: """The value of this attribute as a string, or `None` if this is not a string attribute.""" @@ -49,16 +57,13 @@ class AttributeList: """The attributes for the function itself.""" ... -class AttributeIterator: +class AttributeIterator(Iterator[Attribute]): """An iterator of attributes for a specific part of a function.""" - def __iter__(self) -> object: - ... - - def __next__(self) -> Optional[Attribute]: - ... + def __iter__(self) -> Iterator[Attribute]: ... + def __next__(self) -> Attribute: ... -class AttributeSet: +class AttributeSet(Iterable[Attribute]): """A set of attributes for a specific part of a function.""" def __contains__(self, item: str) -> bool: @@ -77,9 +82,7 @@ class AttributeSet: :returns: The attribute. """ ... - - def __iter__(self) -> AttributeIterator: - ... + def __iter__(self) -> Iterator[Attribute]: ... class BasicBlock(Value): """A basic block.""" @@ -1240,7 +1243,10 @@ def if_result( ... def add_string_attribute( - function: Function, kind: str, value: Optional[str] = None + function: Function, + kind: str, + value: Optional[str] = None, + index: Optional[int] = None, ) -> bool: """ Adds a string attribute to the given function. @@ -1248,5 +1254,6 @@ def add_string_attribute( :param function: The function. :param key: The attribute key. :param value: The attribute value. + :param index: The optional attribute index, defaults to the function index. """ ... diff --git a/pyqir/src/values.rs b/pyqir/src/values.rs index 3d084081..8fcc05f3 100644 --- a/pyqir/src/values.rs +++ b/pyqir/src/values.rs @@ -898,12 +898,14 @@ pub(crate) fn extract_byte_string<'py>(py: Python<'py>, value: &Value) -> Option // :param function: The function. // :param kind: The attribute kind. // :param value: The attribute value. +// :param index: The optional attribute index, defaults to the function index. #[pyfunction] -#[pyo3(text_signature = "(function, key, value)")] +#[pyo3(text_signature = "(function, key, value, index)")] pub(crate) fn add_string_attribute<'py>( function: PyRef, key: &'py PyString, value: Option<&'py PyString>, + index: Option, ) { let function = function.into_super().into_super().as_ptr(); let key = key.to_string_lossy(); @@ -916,6 +918,7 @@ pub(crate) fn add_string_attribute<'py>( Some(ref x) => x.as_bytes(), None => &[], }, + index.unwrap_or(LLVMAttributeFunctionIndex), ); } } diff --git a/pyqir/tests/test_string_attributes.py b/pyqir/tests/test_string_attributes.py index a2f68926..0498ec3f 100644 --- a/pyqir/tests/test_string_attributes.py +++ b/pyqir/tests/test_string_attributes.py @@ -1,10 +1,14 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. +from typing import List import pyqir from pyqir import ( + Attribute, + AttributeFunctionIndex, + AttributeReturnIndex, + Builder, IntType, - ModuleFlagBehavior, Module, Context, add_string_attribute, @@ -77,3 +81,84 @@ def test_attribute_alphabetical_sorting() -> None: # Tests that attributes are sorted alphabetically by key, # irrespective of their value assert 'attributes #0 = { "1" "A"="123" "a"="a" "b"="A" "c" }' in ir + + +def test_function_attributes_can_be_iterated_in_alphabetical_order() -> None: + mod = pyqir.Module(pyqir.Context(), "test") + void = pyqir.Type.void(mod.context) + function = Function(FunctionType(void, []), Linkage.EXTERNAL, "test_function", mod) + # add them out of order, they will be sorted automatically + add_string_attribute(function, "required_num_results", "1") + add_string_attribute(function, "entry_point", "") + add_string_attribute(function, "required_num_qubits", "2") + attrs: List[Attribute] = list(function.attributes.func) + assert len(attrs) == 3 + # Tests that attributes are sorted alphabetically by indexing into the list + assert attrs[0].string_kind == "entry_point" + assert attrs[0].string_value == "" + assert attrs[1].string_kind == "required_num_qubits" + assert attrs[1].string_value == "2" + assert attrs[2].string_kind == "required_num_results" + assert attrs[2].string_value == "1" + + +def test_parameter_attrs() -> None: + mod = pyqir.Module(pyqir.Context(), "test") + void = pyqir.Type.void(mod.context) + i8 = IntType(mod.context, 8) + function = Function( + FunctionType(void, [i8]), Linkage.EXTERNAL, "test_function", mod + ) + # add them out of order, they will be sorted automatically + add_string_attribute(function, "zeroext", "", 1) + add_string_attribute(function, "mycustom", "myvalue", 1) + + # params have their own AttributeSet + attrs = list(function.attributes.param(0)) + + attr = attrs[0] + assert attr.string_kind == "mycustom" + assert attr.string_value == "myvalue" + + attr = attrs[1] + assert attr.string_kind == "zeroext" + assert attr.string_value == "" + + +def test_return_attrs_can_be_added_and_read() -> None: + mod = pyqir.Module(pyqir.Context(), "test") + void = pyqir.Type.void(mod.context) + i8 = IntType(mod.context, 8) + function = Function( + FunctionType(void, [i8]), Linkage.EXTERNAL, "test_function", mod + ) + builder = Builder(mod.context) + builder.ret(None) + + add_string_attribute(function, "mycustom", "myvalue", AttributeReturnIndex) + + # params have their own AttributeSet + attrs = list(function.attributes.ret) + + attr = attrs[0] + assert attr.string_kind == "mycustom" + assert attr.string_value == "myvalue" + + +def test_explicit_function_index_attrs_can_be_added_and_read() -> None: + mod = pyqir.Module(pyqir.Context(), "test") + void = pyqir.Type.void(mod.context) + i8 = IntType(mod.context, 8) + function = Function( + FunctionType(void, [i8]), Linkage.EXTERNAL, "test_function", mod + ) + builder = Builder(mod.context) + builder.ret(None) + + add_string_attribute(function, "mycustom", "myvalue", AttributeFunctionIndex) + + attrs = list(function.attributes.func) + + attr = attrs[0] + assert attr.string_kind == "mycustom" + assert attr.string_value == "myvalue" diff --git a/qirlib/src/values.rs b/qirlib/src/values.rs index fb47a5b2..561f7f35 100644 --- a/qirlib/src/values.rs +++ b/qirlib/src/values.rs @@ -57,24 +57,32 @@ pub unsafe fn entry_point( let ty = LLVMFunctionType(void, [].as_mut_ptr(), 0, 0); let function = LLVMAddFunction(module, name.as_ptr(), ty); - add_string_attribute(function, b"entry_point", b""); + add_string_attribute(function, b"entry_point", b"", LLVMAttributeFunctionIndex); add_string_attribute( function, b"required_num_qubits", required_num_qubits.to_string().as_bytes(), + LLVMAttributeFunctionIndex, ); add_string_attribute( function, b"required_num_results", required_num_results.to_string().as_bytes(), + LLVMAttributeFunctionIndex, ); - add_string_attribute(function, b"qir_profiles", qir_profiles.as_bytes()); + add_string_attribute( + function, + b"qir_profiles", + qir_profiles.as_bytes(), + LLVMAttributeFunctionIndex, + ); add_string_attribute( function, b"output_labeling_schema", output_labeling_schema.as_bytes(), + LLVMAttributeFunctionIndex, ); function @@ -206,7 +214,12 @@ pub unsafe fn extract_string(value: LLVMValueRef) -> Option> { Some(data[offset..].to_vec()) } -pub unsafe fn add_string_attribute(function: LLVMValueRef, key: &[u8], value: &[u8]) { +pub unsafe fn add_string_attribute( + function: LLVMValueRef, + key: &[u8], + value: &[u8], + index: LLVMAttributeIndex, +) { let context = LLVMGetTypeContext(LLVMTypeOf(function)); let attr = LLVMCreateStringAttribute( context, @@ -215,7 +228,7 @@ pub unsafe fn add_string_attribute(function: LLVMValueRef, key: &[u8], value: &[ value.as_ptr().cast(), value.len().try_into().unwrap(), ); - LLVMAddAttributeAtIndex(function, LLVMAttributeFunctionIndex, attr); + LLVMAddAttributeAtIndex(function, index, attr); } unsafe fn get_string_attribute( @@ -371,10 +384,25 @@ mod string_attribute_tests { unsafe { setup_expect( |_, _, function| { - add_string_attribute(function, b"entry_point", b""); - add_string_attribute(function, b"required_num_qubits", b"1"); - add_string_attribute(function, b"required_num_results", b"2"); - add_string_attribute(function, b"qir_profiles", b"test"); + add_string_attribute(function, b"entry_point", b"", LLVMAttributeFunctionIndex); + add_string_attribute( + function, + b"required_num_qubits", + b"1", + LLVMAttributeFunctionIndex, + ); + add_string_attribute( + function, + b"required_num_results", + b"2", + LLVMAttributeFunctionIndex, + ); + add_string_attribute( + function, + b"qir_profiles", + b"test", + LLVMAttributeFunctionIndex, + ); }, |f| { let count = get_attribute_count(f, LLVMAttributeFunctionIndex); @@ -388,7 +416,7 @@ mod string_attribute_tests { unsafe { setup_expect( |_, _, function| { - add_string_attribute(function, b"entry_point", b""); + add_string_attribute(function, b"entry_point", b"", LLVMAttributeFunctionIndex); }, |f| { let count = get_attribute_count(f, LLVMAttributeFunctionIndex); @@ -410,7 +438,7 @@ mod string_attribute_tests { unsafe { setup_expect( |_, _, function| { - add_string_attribute(function, b"entry_point", b""); + add_string_attribute(function, b"entry_point", b"", LLVMAttributeFunctionIndex); }, |f| { let count = get_attribute_count(f, LLVMAttributeFunctionIndex); @@ -428,7 +456,12 @@ mod string_attribute_tests { unsafe { setup_expect( |_, _, function| { - add_string_attribute(function, b"qir_profiles", b"test"); + add_string_attribute( + function, + b"qir_profiles", + b"test", + LLVMAttributeFunctionIndex, + ); }, |f| { let count = get_attribute_count(f, LLVMAttributeFunctionIndex); From 00bbac2a419fa9f338a4959648615f9a41ffcfc5 Mon Sep 17 00:00:00 2001 From: Ian Davis Date: Fri, 20 Oct 2023 11:17:16 -0700 Subject: [PATCH 6/7] Renaming constants --- pyqir/pyqir/__init__.py | 6 +++--- pyqir/pyqir/_constants.py | 4 ++-- pyqir/tests/test_string_attributes.py | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pyqir/pyqir/__init__.py b/pyqir/pyqir/__init__.py index 098c9e9b..1906128b 100644 --- a/pyqir/pyqir/__init__.py +++ b/pyqir/pyqir/__init__.py @@ -59,7 +59,7 @@ from pyqir._simple import SimpleModule from pyqir._entry_point import entry_point from pyqir._basicqis import BasicQisBuilder -from pyqir._constants import AttributeFunctionIndex, AttributeReturnIndex +from pyqir._constants import ATTR_FUNCTION_INDEX, ATTR_RETURN_INDEX __all__ = [ "ArrayType", @@ -118,6 +118,6 @@ "result_id", "result_type", "result", - "AttributeFunctionIndex", - "AttributeReturnIndex", + "ATTR_FUNCTION_INDEX", + "ATTR_RETURN_INDEX", ] diff --git a/pyqir/pyqir/_constants.py b/pyqir/pyqir/_constants.py index de3bc5ca..8fd17226 100644 --- a/pyqir/pyqir/_constants.py +++ b/pyqir/pyqir/_constants.py @@ -1,5 +1,5 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. -AttributeReturnIndex = 0 -AttributeFunctionIndex = 4294967295 # -1 u32 +ATTR_RETURN_INDEX = 0 +ATTR_FUNCTION_INDEX = 4294967295 # -1 u32 diff --git a/pyqir/tests/test_string_attributes.py b/pyqir/tests/test_string_attributes.py index 0498ec3f..55d115e7 100644 --- a/pyqir/tests/test_string_attributes.py +++ b/pyqir/tests/test_string_attributes.py @@ -5,8 +5,8 @@ import pyqir from pyqir import ( Attribute, - AttributeFunctionIndex, - AttributeReturnIndex, + ATTR_FUNCTION_INDEX, + ATTR_RETURN_INDEX, Builder, IntType, Module, @@ -135,7 +135,7 @@ def test_return_attrs_can_be_added_and_read() -> None: builder = Builder(mod.context) builder.ret(None) - add_string_attribute(function, "mycustom", "myvalue", AttributeReturnIndex) + add_string_attribute(function, "mycustom", "myvalue", ATTR_RETURN_INDEX) # params have their own AttributeSet attrs = list(function.attributes.ret) @@ -155,7 +155,7 @@ def test_explicit_function_index_attrs_can_be_added_and_read() -> None: builder = Builder(mod.context) builder.ret(None) - add_string_attribute(function, "mycustom", "myvalue", AttributeFunctionIndex) + add_string_attribute(function, "mycustom", "myvalue", ATTR_FUNCTION_INDEX) attrs = list(function.attributes.func) From c44ef6095c2f68f4de66b379226acbfe062369cf Mon Sep 17 00:00:00 2001 From: Ian Davis Date: Fri, 20 Oct 2023 11:56:32 -0700 Subject: [PATCH 7/7] Cleaning up API usage --- pyqir/tests/test_string_attributes.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyqir/tests/test_string_attributes.py b/pyqir/tests/test_string_attributes.py index 55d115e7..c38a71b3 100644 --- a/pyqir/tests/test_string_attributes.py +++ b/pyqir/tests/test_string_attributes.py @@ -45,9 +45,9 @@ def test_round_trip_serialize_parse() -> None: function = Function(FunctionType(void, []), Linkage.EXTERNAL, "test_function", mod) add_string_attribute(function, "foo", "bar") # also test for non-value attributes - add_string_attribute(function, "entry_point", "") + add_string_attribute(function, "entry_point") # test behavior of empty attribute - add_string_attribute(function, "", "") + add_string_attribute(function, "") ir = str(mod) parsed_mod = Module.from_ir(Context(), ir, "test") assert str(parsed_mod) == str(mod) @@ -58,7 +58,7 @@ def test_duplicate_attr_key_replaces_previous() -> None: void = pyqir.Type.void(mod.context) function = Function(FunctionType(void, []), Linkage.EXTERNAL, "test_function", mod) add_string_attribute(function, "foo", "bar") - add_string_attribute(function, "foo", "") + add_string_attribute(function, "foo") ir = str(mod) # Tests that subsequently added attributes with the same key # replace previously added ones @@ -89,7 +89,7 @@ def test_function_attributes_can_be_iterated_in_alphabetical_order() -> None: function = Function(FunctionType(void, []), Linkage.EXTERNAL, "test_function", mod) # add them out of order, they will be sorted automatically add_string_attribute(function, "required_num_results", "1") - add_string_attribute(function, "entry_point", "") + add_string_attribute(function, "entry_point") add_string_attribute(function, "required_num_qubits", "2") attrs: List[Attribute] = list(function.attributes.func) assert len(attrs) == 3