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

Issue:
Support constant string values as arguments to functions
For QIR output recording, code like the following needs to be emitted:

```llvm
@0 = internal constant [5 x i8] c"0_t0\00"
@1 = internal constant [5 x i8] c"0_t1\00"
...
call void @__quantum__rt__result_record_output(%Result* null, i8* getelementptr inbounds ([5 x i8], [5 x i8]* @0, i32 0, i32 0))
call void @__quantum__rt__result_record_output(%Result* nonnull inttoptr (i64 1 to %Result*), i8* getelementptr inbounds ([5 x i8], [5 x i8]* @1, i32 0, i32 0))
...
declare void @__quantum__rt__result_record_output(%Result*, i8*)
```

This requires:

1. Creating a global constant string value in the module.
2. Passing it as a pointer as an argument to a record output function.


Discussion / hints:

Fix patch:
diff --git a/Cargo.lock b/Cargo.lock
index 0ab9d4ed..00e19873 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -373,7 +373,7 @@ dependencies = [
  "indoc",
  "libc",
  "memoffset",
- "parking_lot 0.11.2",
+ "parking_lot 0.12.1",
  "pyo3-build-config",
  "pyo3-ffi",
  "pyo3-macros",
diff --git a/pyqir/pyqir/__init__.py b/pyqir/pyqir/__init__.py
index 912f7c3d..206c145c 100644
--- a/pyqir/pyqir/__init__.py
+++ b/pyqir/pyqir/__init__.py
@@ -30,6 +30,7 @@
     TypeFactory,
     Value,
     const,
+    const_getelementptr,
     constant_bytes,
     is_entry_point,
     is_interop_friendly,
@@ -70,6 +71,7 @@
     "TypeFactory",
     "Value",
     "const",
+    "const_getelementptr",
     "constant_bytes",
     "is_entry_point",
     "is_interop_friendly",
diff --git a/pyqir/pyqir/_native.pyi b/pyqir/pyqir/_native.pyi
index 1b0fa4a4..156ba22f 100644
--- a/pyqir/pyqir/_native.pyi
+++ b/pyqir/pyqir/_native.pyi
@@ -18,6 +18,8 @@ class TypeFactory:
     @property
     def result(self) -> Type: ...
     @staticmethod
+    def pointer_to(ty: Type) -> Type: ...
+    @staticmethod
     def function(ret: Type, params: Sequence[Type]) -> Type: ...
 
 class Builder:
@@ -79,6 +81,7 @@ class SimpleModule:
     def ir(self) -> str: ...
     def bitcode(self) -> bytes: ...
     def add_external_function(self, name: str, ty: Type) -> Function: ...
+    def add_global_string(self, value: bytes) -> Value: ...
 
 class Type:
     @property
@@ -157,6 +160,7 @@ class Attribute:
     def value(self) -> str: ...
 
 def const(ty: Type, value: Union[int, float]) -> Value: ...
+def const_getelementptr(value: Value, indices: Sequence[Value]) -> Value: ...
 def qubit_id(value: Value) -> Optional[int]: ...
 def result_id(value: Value) -> Optional[int]: ...
 def is_entry_point(function: Function) -> bool: ...
diff --git a/pyqir/src/python.rs b/pyqir/src/python.rs
index c8730b6f..8dc3d450 100644
--- a/pyqir/src/python.rs
+++ b/pyqir/src/python.rs
@@ -12,9 +12,9 @@ use crate::{
     simple::{SimpleModule, TypeFactory},
     types::{is_qubit, is_result, ArrayType, FunctionType, IntType, PointerType, StructType, Type},
     values::{
-        constant_bytes, is_entry_point, is_interop_friendly, qubit_id, r#const,
-        required_num_qubits, required_num_results, result_id, BasicBlock, Constant, FloatConstant,
-        Function, IntConstant, Value,
+        const_getelementptr, constant_bytes, is_entry_point, is_interop_friendly, qubit_id,
+        r#const, required_num_qubits, required_num_results, result_id, BasicBlock, Constant,
+        FloatConstant, Function, IntConstant, Value,
     },
 };
 use pyo3::prelude::*;
@@ -49,6 +49,7 @@ fn _native(_py: Python, m: &PyModule) -> PyResult<()> {
     m.add_class::<Type>()?;
     m.add_class::<TypeFactory>()?;
     m.add_class::<Value>()?;
+    m.add_function(wrap_pyfunction!(const_getelementptr, m)?)?;
     m.add_function(wrap_pyfunction!(constant_bytes, m)?)?;
     m.add_function(wrap_pyfunction!(is_entry_point, m)?)?;
     m.add_function(wrap_pyfunction!(is_interop_friendly, m)?)?;
diff --git a/pyqir/src/simple.rs b/pyqir/src/simple.rs
index bc1795bb..eccbe4b6 100644
--- a/pyqir/src/simple.rs
+++ b/pyqir/src/simple.rs
@@ -10,7 +10,9 @@ use crate::{
 };
 use inkwell::{
     context::ContextRef,
+    module::Linkage,
     types::{AnyType, AnyTypeEnum, BasicType, BasicTypeEnum, FunctionType},
+    AddressSpace,
 };
 use pyo3::{
     exceptions::{PyOSError, PyUnicodeDecodeError, PyValueError},
@@ -140,6 +142,27 @@ impl SimpleModule {
         let function = unsafe { module.get() }.add_function(name, ty, None);
         unsafe { Value::from_any(py, context, function) }
     }
+
+    /// Adds a global null-terminated string constant to the module.
+    ///
+    /// :param bytes Value: The string value without the null terminator.
+    /// :returns: The global value.
+    /// :rtype: Value
+    #[pyo3(text_signature = "(value)")]
+    fn add_global_string(&self, py: Python, value: &[u8]) -> PyResult<PyObject> {
+        let module = self.module.borrow(py);
+        let context = unsafe { module.get().get_context() };
+        let value = context.const_string(value, true);
+        let global = unsafe { module.get() }.add_global(
+            context.i8_type().array_type(value.get_type().get_size()),
+            None,
+            "",
+        );
+        global.set_linkage(Linkage::Internal);
+        global.set_constant(true);
+        global.set_initializer(&value);
+        unsafe { Value::from_any(py, module.context().clone(), global) }
+    }
 }
 
 impl SimpleModule {
@@ -213,6 +236,21 @@ impl TypeFactory {
         self.new_type(py, |context| types::result(context).into())
     }
 
+    /// A pointer type.
+    ///
+    /// :param ty: The type pointed to.
+    /// :returns: The pointer type.
+    /// :rtype: Type
+    #[staticmethod]
+    #[pyo3(text_signature = "(ty)")]
+    #[allow(clippy::similar_names)]
+    fn pointer_to(py: Python, ty: &Type) -> PyResult<PyObject> {
+        let pointee = BasicTypeEnum::try_from(unsafe { ty.get() })
+            .map_err(|()| PyValueError::new_err("Type can't be pointed to."))?;
+        let pointer = pointee.ptr_type(AddressSpace::Generic);
+        unsafe { Type::from_any(py, ty.context().clone(), pointer.into()) }
+    }
+
     /// A function type.
     ///
     /// :param Type ret: The return type.
diff --git a/pyqir/src/values.rs b/pyqir/src/values.rs
index 93954e4a..2660325e 100644
--- a/pyqir/src/values.rs
+++ b/pyqir/src/values.rs
@@ -9,7 +9,7 @@ use inkwell::{
     types::{AnyType, AnyTypeEnum},
     values::{
         AnyValueEnum, BasicMetadataValueEnum, BasicValueEnum, FloatValue, FunctionValue,
-        InstructionValue, IntValue, PointerValue,
+        GlobalValue, InstructionValue, IntValue, PointerValue,
     },
     LLVMReference,
 };
@@ -370,6 +370,12 @@ impl<'ctx> From<PointerValue<'ctx>> for AnyValue<'ctx> {
     }
 }
 
+impl<'ctx> From<GlobalValue<'ctx>> for AnyValue<'ctx> {
+    fn from(global: GlobalValue<'ctx>) -> Self {
+        Self::Any(global.as_pointer_value().into())
+    }
+}
+
 impl<'ctx> From<InstructionValue<'ctx>> for AnyValue<'ctx> {
     fn from(instruction: InstructionValue<'ctx>) -> Self {
         Self::Any(instruction.into())
@@ -527,6 +533,28 @@ pub(crate) fn r#const(py: Python, ty: &Type, value: &PyAny) -> PyResult<PyObject
     unsafe { Value::from_any(py, context, value) }
 }
 
+/// Creates a `getelementptr` constant expression.
+///
+/// :param Value value: The aggregate value.
+/// :param Sequence[Value] indices: The indices of the element.
+/// :returns: A pointer to the element.
+/// :rtype: Value
+#[pyfunction]
+#[pyo3(text_signature = "(value, indices)")]
+#[allow(clippy::needless_pass_by_value)]
+pub(crate) fn const_getelementptr(
+    py: Python,
+    value: &Value,
+    indices: Vec<Value>,
+) -> PyResult<PyObject> {
+    let indices = indices
+        .iter()
+        .map(|i| IntValue::try_from(i.value).map_err(Into::into))
+        .collect::<PyResult<Vec<_>>>()?;
+    let gep = unsafe { PointerValue::try_from(value.value)?.const_in_bounds_gep(&indices) };
+    unsafe { Value::from_any(py, value.context.clone(), gep) }
+}
+
 /// If the value is a static qubit ID, extracts it.
 ///
 /// :param Value value: The value.


Test patch:
diff --git a/pyqir/tests/test_external_functions.py b/pyqir/tests/test_external_functions.py
index 1cd0296e..3180564b 100644
--- a/pyqir/tests/test_external_functions.py
+++ b/pyqir/tests/test_external_functions.py
@@ -8,6 +8,7 @@
     TypeFactory,
     Value,
     const,
+    const_getelementptr,
 )
 import re
 import pytest
@@ -365,3 +366,26 @@ def test_computed_rotation() -> None:
     assert "call void @__quantum__qis__rx__body(double %0, %Qubit* null)" in ir
     assert "call void @__quantum__qis__ry__body(double %0, %Qubit* null)" in ir
     assert "call void @__quantum__qis__rz__body(double %0, %Qubit* null)" in ir
+
+
+def test_record_output() -> None:
+    m = SimpleModule("test", 0, 1)
+    result_record_output = m.add_external_function(
+        "__quantum__rt__result_record_output",
+        m.types.function(
+            m.types.void, [m.types.result, m.types.pointer_to(m.types.int(8))]
+        ),
+    )
+    tag = m.add_global_string(b"foo")
+    i32 = m.types.int(32)
+    m.builder.call(
+        result_record_output,
+        [m.results[0], const_getelementptr(tag, [const(i32, 0), const(i32, 0)])],
+    )
+
+    ir = m.ir()
+    assert r'@0 = internal constant [4 x i8] c"foo\00"' in ir
+    assert (
+        "call void @__quantum__rt__result_record_output(%Result* null, i8* getelementptr inbounds ([4 x i8], [4 x i8]* @0, i32 0, i32 0))"
+        in ir
+    )