Skip to content

Commit

Permalink
Metadata (#223)
Browse files Browse the repository at this point in the history
* Adding param, module, and function metadata
Co-authored-by: Sarah Marshall <[email protected]>
  • Loading branch information
idavis authored Dec 27, 2022
1 parent 117c9cf commit 431959b
Show file tree
Hide file tree
Showing 69 changed files with 1,082 additions and 127 deletions.
111 changes: 88 additions & 23 deletions examples/dynamic_allocation.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,53 +2,118 @@
# Licensed under the MIT License.

import pyqir
from pyqir import (
BasicBlock,
Builder,
Context,
Function,
Linkage,
Module,
ModuleFlagBehavior,
)

context = Context()
mod = Module(context, "dynamic_allocation")
builder = Builder(context)

mod = pyqir.SimpleModule("dynamic_allocation", num_qubits=0, num_results=0)
qubit_type = pyqir.qubit_type(mod.context)
result_type = pyqir.result_type(mod.context)
# Define module flags
i1 = pyqir.IntType(context, 1)
i32 = pyqir.IntType(context, 32)

mod.add_flag(
ModuleFlagBehavior.ERROR,
"qir_major_version",
pyqir.const(i32, 1),
)

mod.add_flag(
ModuleFlagBehavior.MAX,
"qir_minor_version",
pyqir.const(i32, 0),
)

mod.add_flag(
ModuleFlagBehavior.ERROR,
"dynamic_qubit_management",
pyqir.const(i1, True),
)

mod.add_flag(
ModuleFlagBehavior.ERROR,
"dynamic_result_management",
pyqir.const(i1, True),
)

# define external calls and type definitions
qubit_type = pyqir.qubit_type(context)
result_type = pyqir.result_type(context)

# PyQIR assumes you want to use static allocation for qubits and results, but
# you can still use dynamic allocation by manually calling the appropriate
# runtime functions.
qubit_allocate = mod.add_external_function(
"__quantum__rt__qubit_allocate", pyqir.FunctionType(qubit_type, [])
qubit_allocate = Function(
pyqir.FunctionType(qubit_type, []),
Linkage.EXTERNAL,
"__quantum__rt__qubit_allocate",
mod,
)
qubit_release = mod.add_external_function(

qubit_release = Function(
pyqir.FunctionType(pyqir.Type.void(context), [qubit_type]),
Linkage.EXTERNAL,
"__quantum__rt__qubit_release",
pyqir.FunctionType(pyqir.Type.void(mod.context), [qubit_type]),
mod,
)
result_get_one = mod.add_external_function(
"__quantum__rt__result_get_one", pyqir.FunctionType(result_type, [])

result_get_one = Function(
pyqir.FunctionType(result_type, []),
Linkage.EXTERNAL,
"__quantum__rt__result_get_one",
mod,
)
result_equal = mod.add_external_function(

result_equal = Function(
pyqir.FunctionType(pyqir.IntType(context, 1), [result_type, result_type]),
Linkage.EXTERNAL,
"__quantum__rt__result_equal",
pyqir.FunctionType(pyqir.IntType(mod.context, 1), [result_type, result_type]),
mod,
)
m = mod.add_external_function(
"__quantum__qis__m__body", pyqir.FunctionType(result_type, [qubit_type])

m = Function(
pyqir.FunctionType(result_type, [qubit_type]),
Linkage.EXTERNAL,
"__quantum__qis__m__body",
mod,
)

# Instead of mod.qubits[i], use __quantum__rt__qubit_allocate.
qubit_return = mod.builder.call(qubit_allocate, [])
# Create entry point
num_qubits = 1
num_results = 1
entry_point = pyqir.entry_point(mod, "main", num_qubits, num_results)
builder.insert_at_end(BasicBlock(context, "entry", entry_point))

# Define entry point body
qubit_return = builder.call(qubit_allocate, [])

assert qubit_return is not None
qubit = qubit_return

qis = pyqir.BasicQisBuilder(mod.builder)
qis = pyqir.BasicQisBuilder(builder)
qis.h(qubit)

# Instead of qis.mz, use __quantum__qis__m__body.
result = mod.builder.call(m, [qubit])
result = builder.call(m, [qubit])
assert result is not None

# Instead of mod.if_result, use __quantum__rt__result_equal and mod.if_.
one = mod.builder.call(result_get_one, [])
# Instead of if_result, use __quantum__rt__result_equal and mod.if_.
one = builder.call(result_get_one, [])
assert one is not None
result_is_one = mod.builder.call(result_equal, [result, one])
result_is_one = builder.call(result_equal, [result, one])
assert result_is_one is not None
mod.builder.if_(result_is_one, lambda: qis.reset(qubit))
builder.if_(result_is_one, lambda: qis.reset(qubit))

# Be sure to release any allocated qubits when you're done with them.
mod.builder.call(qubit_release, [qubit])
builder.call(qubit_release, [qubit])

if __name__ == "__main__":
print(mod.ir())
print(str(mod))
2 changes: 1 addition & 1 deletion pyqir/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ declare void @__quantum__qis__h__body(%Qubit*)
declare void @__quantum__qis__cnot__body(%Qubit*, %Qubit*)
declare void @__quantum__qis__mz__body(%Qubit*, %Result*)
declare void @__quantum__qis__mz__body(%Qubit*, %Result* writeonly)
attributes #0 = { "EntryPoint" "requiredQubits"="2" "requiredResults"="2" }
```
Expand Down
8 changes: 8 additions & 0 deletions pyqir/pyqir/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
Builder,
Call,
Constant,
ConstantAsMetadata,
Context,
FCmp,
FloatConstant,
Expand All @@ -22,7 +23,10 @@
IntPredicate,
IntType,
Linkage,
Metadata,
MetadataString,
Module,
ModuleFlagBehavior,
Opcode,
Phi,
PointerType,
Expand Down Expand Up @@ -60,6 +64,7 @@
"Builder",
"Call",
"Constant",
"ConstantAsMetadata",
"Context",
"FCmp",
"FloatConstant",
Expand All @@ -72,7 +77,10 @@
"IntPredicate",
"IntType",
"Linkage",
"Metadata",
"MetadataString",
"Module",
"ModuleFlagBehavior",
"Opcode",
"Phi",
"PointerType",
Expand Down
80 changes: 75 additions & 5 deletions pyqir/pyqir/_native.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,29 @@ class Module:
def context(self) -> Context:
"""The LLVM context."""
...
def add_flag(
self, behavior: ModuleFlagBehavior, id: str, flag: Union[Metadata, Constant]
) -> None:
"""
Adds a flag to the llvm.module.flags metadata
See https://llvm.org/docs/LangRef.html#module-flags-metadata
:param ModuleFlagBehavior behavior: flag specifying the behavior when two (or more) modules are merged together
:param str id: string that is a unique ID for the metadata.
:param Union[Metadata, Constant] flag: value of the flag
"""
...
def get_flag(self, id: str) -> Optional[Metadata]:
"""
Gets the flag value from the llvm.module.flags metadata for a given id
See https://llvm.org/docs/LangRef.html#module-flags-metadata
:param id: metadata string that is a unique ID for the metadata.
:returns: value of the flag if found, otherwise None
"""
...
def verify(self) -> Optional[str]:
"""
Verifies that this module is valid.
Expand All @@ -516,6 +539,17 @@ class Module:
"""Converts this module into an LLVM IR string."""
...

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

ERROR: ModuleFlagBehavior
WARNING: ModuleFlagBehavior
REQUIRE: ModuleFlagBehavior
OVERRIDE: ModuleFlagBehavior
APPEND: ModuleFlagBehavior
APPEND_UNIQUE: ModuleFlagBehavior
MAX: ModuleFlagBehavior

class Opcode(Enum):
"""An instruction opcode."""

Expand Down Expand Up @@ -672,6 +706,35 @@ class Type:
"""Whether this type is the bool type."""
...

class Metadata:
"""A metadata value."""

...

class MetadataString(Metadata):
"""A metadata string"""

def __init__(self, context: Context, string: str) -> None:
"""
Creates a metadata string
:param context: The LLVM context.
:param string: the value of the metadata string to create
"""
...
@property
def value(self) -> str:
"""The underlying metadata string value."""
...

class ConstantAsMetadata(Metadata):
"""A metadata constant value."""

@property
def value(self) -> Constant:
"""The underlying metadata constant value."""
...

class Value:
"""A value."""

Expand All @@ -695,15 +758,22 @@ def const(ty: Type, value: Union[bool, int, float]) -> Constant:
...

def entry_point(
module: Module, name: str, required_num_qubits: int, required_num_results: int
module: Module,
name: str,
required_num_qubits: int,
required_num_results: int,
qir_profiles: Optional[str] = "custom",
output_labeling_schema: Optional[str] = "",
) -> Function:
"""
Creates an entry point.
:param module: The parent module.
:param name: The entry point name.
:param required_num_qubits: The number of qubits required by the entry point.
:param required_num_results: The number of results required by the entry point.
:param Module module: The parent module.
:param str name: The entry point name.
:param int required_num_qubits: The number of qubits required by the entry point.
:param int required_num_results: The number of results required by the entry point.
:param Optional[str] qir_profiles: Value identifying the profile the entry point has been compiled for. Use base_profile when QIR is compliant.
:param Optional[str] output_labeling_schema: An arbitrary string value that identifies the schema used by a compiler frontend that produced the IR to label the recorded output
:returns: An entry point.
"""
...
Expand Down
36 changes: 32 additions & 4 deletions pyqir/pyqir/_simple.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
FunctionType,
Linkage,
Module,
ModuleFlagBehavior,
Value,
)

Expand All @@ -35,10 +36,10 @@ def __init__(
"""
Initializes a simple module.
:param name: The name of the module.
:param num_qubits: The number of statically allocated qubits.
:param num_results: The number of statically allocated results.
:param context: The LLVM context.
:param str name: The name of the module.
:param str num_qubits: The number of statically allocated qubits.
:param int num_results: The number of statically allocated results.
:param Optional[Context] context: The LLVM context.
"""

if context is None:
Expand All @@ -52,6 +53,33 @@ def __init__(
entry_point = pyqir.entry_point(self._module, "main", num_qubits, num_results)
self._builder.insert_at_end(BasicBlock(context, "entry", entry_point))

i1 = pyqir.IntType(context, 1)
i32 = pyqir.IntType(context, 32)

self._module.add_flag(
ModuleFlagBehavior.ERROR,
"qir_major_version",
pyqir.const(i32, 1),
)

self._module.add_flag(
ModuleFlagBehavior.MAX,
"qir_minor_version",
pyqir.const(i32, 0),
)

self._module.add_flag(
ModuleFlagBehavior.ERROR,
"dynamic_qubit_management",
pyqir.const(i1, False),
)

self._module.add_flag(
ModuleFlagBehavior.ERROR,
"dynamic_result_management",
pyqir.const(i1, False),
)

@property
def context(self) -> Context:
"""The LLVM context."""
Expand Down
6 changes: 5 additions & 1 deletion pyqir/src/core.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

#![allow(clippy::used_underscore_binding)]

use llvm_sys::core::LLVMContextCreate;
#[allow(deprecated)]
use llvm_sys::{
core::{LLVMContextCreate, LLVMContextDispose, LLVMDisposeMemoryBuffer, LLVMDisposeMessage},
core::{LLVMContextDispose, LLVMDisposeMemoryBuffer, LLVMDisposeMessage},
prelude::*,
LLVMContext, LLVMMemoryBuffer,
};
Expand Down
1 change: 1 addition & 0 deletions pyqir/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ extern crate llvm_sys_140 as llvm_sys;
mod builder;
mod core;
mod instructions;
mod metadata;
mod module;
mod python;
mod qis;
Expand Down
Loading

0 comments on commit 431959b

Please sign in to comment.