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

Issue:
Support generating modules with entry points without using SimpleModule
To support multiple entry points, PyQIR should allow you to manually create modules that have any number of entry points, without using SimpleModule (which was designed for only one entry point).


Discussion / hints:

Fix patch:
diff --git a/pyqir/pyqir/__init__.py b/pyqir/pyqir/__init__.py
index 206c145c..f74d9ad8 100644
--- a/pyqir/pyqir/__init__.py
+++ b/pyqir/pyqir/__init__.py
@@ -9,6 +9,7 @@
     Builder,
     Call,
     Constant,
+    Context,
     FCmp,
     FloatConstant,
     FloatPredicate,
@@ -19,6 +20,7 @@
     IntConstant,
     IntPredicate,
     IntType,
+    Linkage,
     Module,
     Opcode,
     Phi,
@@ -29,17 +31,23 @@
     Type,
     TypeFactory,
     Value,
-    const,
     const_getelementptr,
+    const,
     constant_bytes,
+    entry_point,
     is_entry_point,
     is_interop_friendly,
-    is_qubit,
-    is_result,
+    is_qubit_type,
+    is_result_type,
     qubit_id,
+    qubit_type,
+    qubit,
     required_num_qubits,
     required_num_results,
     result_id,
+    result_type,
+    result,
+    verify_module,
 )
 
 __all__ = [
@@ -50,6 +58,7 @@
     "Builder",
     "Call",
     "Constant",
+    "Context",
     "FCmp",
     "FloatConstant",
     "FloatPredicate",
@@ -60,6 +69,7 @@
     "IntConstant",
     "IntPredicate",
     "IntType",
+    "Linkage",
     "Module",
     "Opcode",
     "Phi",
@@ -70,15 +80,21 @@
     "Type",
     "TypeFactory",
     "Value",
-    "const",
     "const_getelementptr",
+    "const",
     "constant_bytes",
+    "entry_point",
     "is_entry_point",
     "is_interop_friendly",
-    "is_qubit",
-    "is_result",
+    "is_qubit_type",
+    "is_result_type",
     "qubit_id",
+    "qubit_type",
+    "qubit",
     "required_num_qubits",
     "required_num_results",
     "result_id",
+    "result_type",
+    "result",
+    "verify_module",
 ]
diff --git a/pyqir/pyqir/_native.pyi b/pyqir/pyqir/_native.pyi
index 156ba22f..e68de9a0 100644
--- a/pyqir/pyqir/_native.pyi
+++ b/pyqir/pyqir/_native.pyi
@@ -5,6 +5,9 @@ from enum import Enum
 from pyqir.evaluator import GateSet
 from typing import Callable, List, Optional, Sequence, Tuple, Union
 
+class Context:
+    def __init__(self) -> None: ...
+
 class TypeFactory:
     @property
     def void(self) -> Type: ...
@@ -23,6 +26,8 @@ class TypeFactory:
     def function(ret: Type, params: Sequence[Type]) -> Type: ...
 
 class Builder:
+    def __init__(self, context: Context) -> None: ...
+    def insert_at_end(self, block: BasicBlock) -> None: ...
     def and_(self, lhs: Value, rhs: Value) -> Value: ...
     def or_(self, lhs: Value, rhs: Value) -> Value: ...
     def xor(self, lhs: Value, rhs: Value) -> Value: ...
@@ -43,6 +48,7 @@ class Builder:
         true: Callable[[], None] = ...,
         false: Callable[[], None] = ...,
     ) -> None: ...
+    def ret(self, value: Optional[Value]) -> Instruction: ...
 
 class BasicQisBuilder:
     def __init__(self, builder: Builder) -> None: ...
@@ -84,16 +90,22 @@ class SimpleModule:
     def add_global_string(self, value: bytes) -> Value: ...
 
 class Type:
+    @staticmethod
+    def void(context: Context) -> Type: ...
+    @staticmethod
+    def double(context: Context) -> Type: ...
     @property
     def is_void(self) -> bool: ...
     @property
     def is_double(self) -> bool: ...
 
 class IntType(Type):
+    def __init__(self, context: Context, width: int) -> None: ...
     @property
     def width(self) -> int: ...
 
 class FunctionType(Type):
+    def __init__(self, ret: Type, params: Sequence[Type]) -> None: ...
     @property
     def ret(self) -> Type: ...
     @property
@@ -112,13 +124,16 @@ class ArrayType(Type):
     def count(self) -> int: ...
 
 class PointerType(Type):
+    def __init__(self, pointee: Type) -> None: ...
     @property
     def pointee(self) -> Type: ...
     @property
     def address_space(self) -> int: ...
 
-def is_qubit(ty: Type) -> bool: ...
-def is_result(ty: Type) -> bool: ...
+def qubit_type(context: Context) -> Type: ...
+def is_qubit_type(ty: Type) -> bool: ...
+def result_type(context: Context) -> Type: ...
+def is_result_type(ty: Type) -> bool: ...
 
 class Value:
     @property
@@ -127,6 +142,9 @@ class Value:
     def name(self) -> Optional[str]: ...
 
 class BasicBlock(Value):
+    def __init__(
+        self, context: Context, name: str, insertion: Union[Function, BasicBlock]
+    ) -> None: ...
     @property
     def instructions(self) -> List[Instruction]: ...
     @property
@@ -147,6 +165,9 @@ class FloatConstant(Constant):
     def value(self) -> float: ...
 
 class Function(Constant):
+    def __init__(
+        self, ty: FunctionType, linkage: Linkage, name: str, module: Module
+    ) -> None: ...
     @property
     def type(self) -> FunctionType: ...
     @property
@@ -155,19 +176,37 @@ class Function(Constant):
     def basic_blocks(self) -> List[BasicBlock]: ...
     def attribute(self, name: str) -> Optional[Attribute]: ...
 
+class Linkage(Enum):
+    APPENDING: Linkage
+    AVAILABLE_EXTERNALLY: Linkage
+    COMMON: Linkage
+    EXTERNAL: Linkage
+    EXTERNAL_WEAK: Linkage
+    INTERNAL: Linkage
+    LINK_ONCE_ANY: Linkage
+    LINK_ONCE_ODR: Linkage
+    PRIVATE: Linkage
+    WEAK_ANY: Linkage
+    WEAK_ODR: Linkage
+
 class Attribute:
     @property
     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 const(ty: Type, value: Union[int, float]) -> Value: ...
+def constant_bytes(value: Value) -> Optional[bytes]: ...
+def entry_point(
+    module: Module, name: str, required_num_qubits: int, required_num_results: int
+) -> Function: ...
 def is_entry_point(function: Function) -> bool: ...
 def is_interop_friendly(function: Function) -> bool: ...
+def qubit_id(value: Value) -> Optional[int]: ...
+def qubit(context: Context, id: int) -> Value: ...
 def required_num_qubits(function: Function) -> Optional[int]: ...
 def required_num_results(function: Function) -> Optional[int]: ...
-def constant_bytes(value: Value) -> Optional[bytes]: ...
+def result_id(value: Value) -> Optional[int]: ...
+def result(context: Context, id: int) -> Value: ...
 
 class Opcode(Enum):
     ADD: Opcode
@@ -303,6 +342,7 @@ class Phi(Instruction):
     def incoming(self) -> List[Tuple[Value, BasicBlock]]: ...
 
 class Module:
+    def __init__(self, context: Context, name: str) -> None: ...
     @staticmethod
     def from_ir(ir: str, name: str = "") -> Module: ...
     @staticmethod
@@ -315,8 +355,12 @@ class Module:
     def functions(self) -> List[Function]: ...
     @property
     def bitcode(self) -> bytes: ...
+    @property
+    def context(self) -> Context: ...
     def __str__(self) -> str: ...
 
+def verify_module(module: Module) -> Optional[str]: ...
+
 class PyNonadaptiveJit:
     def eval(
         self,
diff --git a/pyqir/src/builder.rs b/pyqir/src/builder.rs
index d454a2fc..48bc73ec 100644
--- a/pyqir/src/builder.rs
+++ b/pyqir/src/builder.rs
@@ -4,11 +4,11 @@
 use crate::{
     context::{self, Context},
     instructions::IntPredicate,
-    values::{self, AnyValue, Value},
+    values::{self, AnyValue, BasicBlock, Value},
 };
 use inkwell::{
     types::{AnyTypeEnum, FunctionType},
-    values::{AnyValueEnum, CallableValue, IntValue},
+    values::{AnyValueEnum, BasicValueEnum, CallableValue, IntValue},
 };
 use pyo3::{exceptions::PyValueError, prelude::*, types::PySequence};
 use qirlib::builder::Ext;
@@ -19,6 +19,8 @@ use std::{
 };
 
 /// An instruction builder.
+///
+/// :param Context context: The global context.
 #[pyclass(unsendable)]
 pub(crate) struct Builder {
     builder: inkwell::builder::Builder<'static>,
@@ -27,6 +29,28 @@ pub(crate) struct Builder {
 
 #[pymethods]
 impl Builder {
+    #[new]
+    pub(crate) fn new(py: Python, context: Py<Context>) -> Self {
+        let builder = {
+            let context = context.borrow(py);
+            let builder = context.create_builder();
+            unsafe {
+                transmute::<inkwell::builder::Builder<'_>, inkwell::builder::Builder<'static>>(
+                    builder,
+                )
+            }
+        };
+
+        Self { builder, context }
+    }
+
+    /// Tells this builder to insert subsequent instructions at the end of the block.
+    ///
+    /// :param BasicBlock block: The block to insert into.
+    fn insert_at_end(&self, block: &BasicBlock) {
+        self.builder.position_at_end(unsafe { block.get() });
+    }
+
     /// Inserts a bitwise logical and instruction.
     ///
     /// :param Value lhs: The left-hand side.
@@ -251,23 +275,26 @@ impl Builder {
             || r#false.iter().try_for_each(|f| f.call0().map(|_| ())),
         )
     }
-}
 
-impl Builder {
-    pub(crate) fn new(py: Python, context: Py<Context>) -> Self {
-        let builder = {
-            let context = context.borrow(py);
-            let builder = context.create_builder();
-            unsafe {
-                transmute::<inkwell::builder::Builder<'_>, inkwell::builder::Builder<'static>>(
-                    builder,
-                )
+    /// Inserts a return instruction.
+    ///
+    /// :param Value value: The value to return. If `None`, returns void.
+    /// :returns: The return instruction.
+    /// :rtype: Instruction
+    fn ret(&self, py: Python, value: Option<&Value>) -> PyResult<PyObject> {
+        let inst = match value {
+            None => self.builder.build_return(None),
+            Some(value) => {
+                context::require_same(py, [&self.context, value.context()])?;
+                let value = BasicValueEnum::try_from(unsafe { value.get() })?;
+                self.builder.build_return(Some(&value))
             }
         };
-
-        Self { builder, context }
+        unsafe { Value::from_any(py, self.context.clone(), inst) }
     }
+}
 
+impl Builder {
     pub(crate) unsafe fn get(&self) -> &inkwell::builder::Builder<'static> {
         &self.builder
     }
diff --git a/pyqir/src/context.rs b/pyqir/src/context.rs
index dd305d6e..e84496f7 100644
--- a/pyqir/src/context.rs
+++ b/pyqir/src/context.rs
@@ -8,7 +8,10 @@ use std::{borrow::Borrow, ops::Deref};
 #[derive(Eq, PartialEq)]
 pub(crate) struct Context(inkwell::context::Context);
 
+/// The context owns global state needed by most LLVM objects.
+#[pymethods]
 impl Context {
+    #[new]
     pub(crate) fn new() -> Self {
         Self(inkwell::context::Context::create())
     }
diff --git a/pyqir/src/module.rs b/pyqir/src/module.rs
index c56153c6..ad3196cc 100644
--- a/pyqir/src/module.rs
+++ b/pyqir/src/module.rs
@@ -6,7 +6,10 @@ use inkwell::memory_buffer::MemoryBuffer;
 use pyo3::{exceptions::PyValueError, prelude::*, types::PyBytes};
 use std::mem::transmute;
 
-/// A module.
+/// A module is a collection of functions.
+///
+/// :param Context context: The global context.
+/// :param str name: The module name.
 #[pyclass(unsendable)]
 pub(crate) struct Module {
     module: inkwell::module::Module<'static>,
@@ -15,6 +18,18 @@ pub(crate) struct Module {
 
 #[pymethods]
 impl Module {
+    #[new]
+    pub(crate) fn new(py: Python, context: Py<Context>, name: &str) -> Self {
+        let module = {
+            let context = context.borrow(py);
+            let module = context.create_module(name);
+            unsafe {
+                transmute::<inkwell::module::Module<'_>, inkwell::module::Module<'static>>(module)
+            }
+        };
+        Self { module, context }
+    }
+
     /// Creates a module from LLVM IR.
     ///
     /// :param str ir: The LLVM IR for a module.
@@ -94,30 +109,23 @@ impl Module {
         PyBytes::new(py, self.module.write_bitcode_to_memory().as_slice())
     }
 
+    /// The global context.
+    ///
+    /// :type: Context
+    #[getter]
+    pub(crate) fn context(&self) -> &Py<Context> {
+        &self.context
+    }
+
     fn __str__(&self) -> String {
         self.module.to_string()
     }
 }
 
 impl Module {
-    pub(crate) fn new(py: Python, context: Py<Context>, name: &str) -> Self {
-        let module = {
-            let context = context.borrow(py);
-            let module = context.create_module(name);
-            unsafe {
-                transmute::<inkwell::module::Module<'_>, inkwell::module::Module<'static>>(module)
-            }
-        };
-        Self { module, context }
-    }
-
     pub(crate) unsafe fn get(&self) -> &inkwell::module::Module<'static> {
         &self.module
     }
-
-    pub(crate) fn context(&self) -> &Py<Context> {
-        &self.context
-    }
 }
 
 /// An attribute.
@@ -135,3 +143,12 @@ impl Attribute {
             .expect("Value is not valid UTF-8.")
     }
 }
+
+/// Verifies that a module is valid.
+///
+/// :returns: An error description if the module is invalid or `None` if the module is valid.
+/// :rtype: Optional[str]
+#[pyfunction]
+pub(crate) fn verify_module(module: &Module) -> Option<String> {
+    module.module.verify().map_err(|e| e.to_string()).err()
+}
diff --git a/pyqir/src/python.rs b/pyqir/src/python.rs
index 8dc3d450..8aa287af 100644
--- a/pyqir/src/python.rs
+++ b/pyqir/src/python.rs
@@ -3,18 +3,22 @@
 
 use crate::{
     builder::Builder,
+    context::Context,
     evaluator::PyNonadaptiveJit,
     instructions::{
         Call, FCmp, FloatPredicate, ICmp, Instruction, IntPredicate, Opcode, Phi, Switch,
     },
-    module::{Attribute, Module},
+    module::{verify_module, Attribute, Module},
     qis::BasicQisBuilder,
     simple::{SimpleModule, TypeFactory},
-    types::{is_qubit, is_result, ArrayType, FunctionType, IntType, PointerType, StructType, Type},
+    types::{
+        is_qubit_type, is_result_type, qubit_type, result_type, ArrayType, FunctionType, IntType,
+        PointerType, StructType, Type,
+    },
     values::{
-        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,
+        const_getelementptr, constant_bytes, entry_point, is_entry_point, is_interop_friendly,
+        qubit, qubit_id, r#const, required_num_qubits, required_num_results, result, result_id,
+        BasicBlock, Constant, FloatConstant, Function, IntConstant, Linkage, Value,
     },
 };
 use pyo3::prelude::*;
@@ -28,6 +32,7 @@ fn _native(_py: Python, m: &PyModule) -> PyResult<()> {
     m.add_class::<Builder>()?;
     m.add_class::<Call>()?;
     m.add_class::<Constant>()?;
+    m.add_class::<Context>()?;
     m.add_class::<FCmp>()?;
     m.add_class::<FloatConstant>()?;
     m.add_class::<FloatPredicate>()?;
@@ -38,6 +43,7 @@ fn _native(_py: Python, m: &PyModule) -> PyResult<()> {
     m.add_class::<IntConstant>()?;
     m.add_class::<IntPredicate>()?;
     m.add_class::<IntType>()?;
+    m.add_class::<Linkage>()?;
     m.add_class::<Module>()?;
     m.add_class::<Opcode>()?;
     m.add_class::<Phi>()?;
@@ -51,14 +57,20 @@ fn _native(_py: Python, m: &PyModule) -> PyResult<()> {
     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!(entry_point, m)?)?;
     m.add_function(wrap_pyfunction!(is_entry_point, m)?)?;
     m.add_function(wrap_pyfunction!(is_interop_friendly, m)?)?;
-    m.add_function(wrap_pyfunction!(is_qubit, m)?)?;
-    m.add_function(wrap_pyfunction!(is_result, m)?)?;
+    m.add_function(wrap_pyfunction!(is_qubit_type, m)?)?;
+    m.add_function(wrap_pyfunction!(is_result_type, m)?)?;
     m.add_function(wrap_pyfunction!(qubit_id, m)?)?;
+    m.add_function(wrap_pyfunction!(qubit_type, m)?)?;
+    m.add_function(wrap_pyfunction!(qubit, m)?)?;
     m.add_function(wrap_pyfunction!(r#const, m)?)?;
     m.add_function(wrap_pyfunction!(required_num_qubits, m)?)?;
     m.add_function(wrap_pyfunction!(required_num_results, m)?)?;
     m.add_function(wrap_pyfunction!(result_id, m)?)?;
+    m.add_function(wrap_pyfunction!(result_type, m)?)?;
+    m.add_function(wrap_pyfunction!(result, m)?)?;
+    m.add_function(wrap_pyfunction!(verify_module, m)?)?;
     Ok(())
 }
diff --git a/pyqir/src/simple.rs b/pyqir/src/simple.rs
index 77ca3674..6e222d6f 100644
--- a/pyqir/src/simple.rs
+++ b/pyqir/src/simple.rs
@@ -11,7 +11,7 @@ use crate::{
 use inkwell::{
     context::ContextRef,
     module::Linkage,
-    types::{AnyType, AnyTypeEnum, BasicType, BasicTypeEnum, FunctionType},
+    types::{AnyTypeEnum, BasicType, BasicTypeEnum},
     AddressSpace,
 };
 use pyo3::{
@@ -19,14 +19,8 @@ use pyo3::{
     prelude::*,
     types::PyBytes,
 };
-use qirlib::{
-    module, types,
-    values::{qubit, result},
-};
-use std::{
-    convert::{Into, TryFrom},
-    mem::transmute,
-};
+use qirlib::{passes, types, values};
+use std::{convert::Into, mem::transmute};
 
 /// A simple module represents an executable program with these restrictions:
 ///
@@ -56,9 +50,13 @@ impl SimpleModule {
         let builder = Py::new(py, Builder::new(py, context.clone()))?;
 
         {
-            let builder = builder.borrow(py);
+            let context = context.borrow(py);
             let module = module.borrow(py);
-            unsafe { module::simple_init(module.get(), builder.get(), num_qubits, num_results) };
+            let builder = builder.borrow(py);
+            let entry_point =
+                values::entry_point(unsafe { module.get() }, "main", num_qubits, num_results);
+            unsafe { builder.get() }
+                .position_at_end(context.append_basic_block(entry_point, "entry"));
         }
 
         Ok(SimpleModule {
@@ -84,7 +82,9 @@ impl SimpleModule {
         let context = module.context();
         let context_ref = unsafe { module.get() }.get_context();
         (0..self.num_qubits)
-            .map(|id| unsafe { Value::from_any(py, context.clone(), qubit(&context_ref, id)) })
+            .map(|id| unsafe {
+                Value::from_any(py, context.clone(), values::qubit(&context_ref, id))
+            })
             .collect()
     }
 
@@ -97,7 +97,9 @@ impl SimpleModule {
         let context = module.context();
         let context_ref = unsafe { module.get() }.get_context();
         (0..self.num_results)
-            .map(|id| unsafe { Value::from_any(py, context.clone(), result(&context_ref, id)) })
+            .map(|id| unsafe {
+                Value::from_any(py, context.clone(), values::result(&context_ref, id))
+            })
             .collect()
     }
 
@@ -169,12 +171,17 @@ impl SimpleModule {
     fn emit<T>(&self, py: Python, f: impl Fn(&inkwell::module::Module) -> T) -> PyResult<T> {
         let module = self.module.borrow(py);
         let builder = self.builder.borrow(py);
+        let context = inkwell::context::Context::create();
+
         let ret = unsafe { builder.get() }.build_return(None);
-        let new_context = inkwell::context::Context::create();
-        let new_module = clone_module(unsafe { module.get() }, &new_context)?;
+        let module = clone_module(unsafe { module.get() }, &context)?;
         ret.erase_from_basic_block();
-        module::simple_finalize(&new_module).map_err(PyOSError::new_err)?;
-        Ok(f(&new_module))
+
+        passes::run_basic(&module);
+        module
+            .verify()
+            .map_err(|e| PyValueError::new_err(e.to_string()))?;
+        Ok(f(&module))
     }
 }
 
@@ -269,7 +276,7 @@ impl TypeFactory {
                 .chain([ret.context().clone()]),
         )?;
 
-        let ty = function_type(
+        let ty = crate::types::function(
             unsafe { &ret.get() },
             params.iter().map(|t| unsafe {
                 transmute::<AnyTypeEnum<'_>, AnyTypeEnum<'static>>(t.borrow(py).get())
@@ -293,23 +300,6 @@ impl TypeFactory {
     }
 }
 
-fn function_type<'ctx>(
-    return_type: &impl AnyType<'ctx>,
-    params: impl IntoIterator<Item = AnyTypeEnum<'ctx>>,
-) -> Option<FunctionType<'ctx>> {
-    let params = params
-        .into_iter()
-        .map(|ty| BasicTypeEnum::try_from(ty).map(Into::into).ok())
-        .collect::<Option<Vec<_>>>()?;
-
-    match return_type.as_any_type_enum() {
-        AnyTypeEnum::VoidType(void) => Some(void.fn_type(&params, false)),
-        any => BasicTypeEnum::try_from(any)
-            .map(|basic| basic.fn_type(&params, false))
-            .ok(),
-    }
-}
-
 fn clone_module<'ctx>(
     module: &inkwell::module::Module,
     context: &'ctx inkwell::context::Context,
diff --git a/pyqir/src/types.rs b/pyqir/src/types.rs
index 80a764ba..15d85872 100644
--- a/pyqir/src/types.rs
+++ b/pyqir/src/types.rs
@@ -3,14 +3,19 @@
 
 #![allow(clippy::used_underscore_binding)]
 
-use crate::context::Context;
-use inkwell::types::{AnyTypeEnum, BasicTypeEnum};
-use pyo3::{conversion::ToPyObject, prelude::*};
+use crate::context::{self, Context};
+use inkwell::{
+    types::{AnyType, AnyTypeEnum, BasicType, BasicTypeEnum},
+    AddressSpace, LLVMReference,
+};
+use llvm_sys::{core::LLVMGetTypeKind, LLVMTypeKind};
+use pyo3::{conversion::ToPyObject, exceptions::PyValueError, prelude::*};
 use qirlib::types;
-use std::mem::transmute;
+use std::{convert::TryFrom, mem::transmute};
 
 /// A type.
 #[pyclass(subclass, unsendable)]
+#[derive(Clone)]
 pub(crate) struct Type {
     ty: AnyTypeEnum<'static>,
     context: Py<Context>,
@@ -18,6 +23,36 @@ pub(crate) struct Type {
 
 #[pymethods]
 impl Type {
+    /// The void type.
+    ///
+    /// :param Context context: The global context.
+    /// :returns: The void type.
+    /// :rtype: Type
+    #[staticmethod]
+    fn void(py: Python, context: Py<Context>) -> Self {
+        let ty = {
+            let context = context.borrow(py);
+            let ty = context.void_type().into();
+            unsafe { transmute::<AnyTypeEnum<'_>, AnyTypeEnum<'static>>(ty) }
+        };
+        Type { ty, context }
+    }
+
+    /// The double type.
+    ///
+    /// :param Context context: The global context.
+    /// :returns: The double type.
+    /// :rtype: Type
+    #[staticmethod]
+    fn double(py: Python, context: Py<Context>) -> Self {
+        let ty = {
+            let context = context.borrow(py);
+            let ty = context.f64_type().into();
+            unsafe { transmute::<AnyTypeEnum<'_>, AnyTypeEnum<'static>>(ty) }
+        };
+        Type { ty, context }
+    }
+
     /// Whether this type is the void type.
     ///
     /// :type: bool
@@ -31,12 +66,7 @@ impl Type {
     /// :type: bool
     #[getter]
     fn is_double(&self) -> bool {
-        match self.ty {
-            AnyTypeEnum::FloatType(float) => {
-                float.size_of().get_zero_extended_constant() == Some(64)
-            }
-            _ => false,
-        }
+        (unsafe { LLVMGetTypeKind(self.ty.get_ref()) }) == LLVMTypeKind::LLVMDoubleTypeKind
     }
 }
 
@@ -78,11 +108,33 @@ impl Type {
 }
 
 /// An integer type.
+///
+/// :param Context context: The global context.
+/// :param int width: The number of bits in the integer.
 #[pyclass(extends = Type, unsendable)]
 pub(crate) struct IntType(inkwell::types::IntType<'static>);
 
 #[pymethods]
 impl IntType {
+    #[new]
+    fn new(py: Python, context: Py<Context>, width: u32) -> (Self, Type) {
+        let ty = {
+            let context = context.borrow(py);
+            let ty = context.custom_width_int_type(width);
+            unsafe {
+                transmute::<inkwell::types::IntType<'_>, inkwell::types::IntType<'static>>(ty)
+            }
+        };
+
+        (
+            Self(ty),
+            Type {
+                ty: ty.into(),
+                context,
+            },
+        )
+    }
+
     /// The number of bits in the integer.
     ///
     /// :type: int
@@ -93,19 +145,46 @@ impl IntType {
 }
 
 /// A function type.
+///
+/// :param Type ret: The return type.
+/// :param Sequence[Type] params: The parameter types.
 #[pyclass(extends = Type, unsendable)]
+#[derive(Clone)]
 pub(crate) struct FunctionType(inkwell::types::FunctionType<'static>);
 
 #[pymethods]
 impl FunctionType {
+    #[new]
+    #[allow(clippy::needless_pass_by_value)]
+    fn new(py: Python, ret: &Type, params: Vec<Type>) -> PyResult<(Self, Type)> {
+        context::require_same(
+            py,
+            params.iter().map(|ty| &ty.context).chain([&ret.context]),
+        )?;
+
+        let ty = function(&ret.ty, params.iter().map(|ty| ty.ty))
+            .ok_or_else(|| PyValueError::new_err("Not a valid function type."))?;
+
+        Ok((
+            Self(ty),
+            Type {
+                ty: ty.into(),
+                context: ret.context.clone(),
+            },
+        ))
+    }
+
     /// The return type of the function.
     ///
     /// :type: Type
     #[getter]
     fn ret(slf: PyRef<Self>, py: Python) -> PyResult<PyObject> {
-        let ty = basic_to_any(slf.0.get_return_type().unwrap());
+        let ret = slf.0.get_return_type();
         let context = slf.into_super().context.clone();
-        unsafe { Type::from_any(py, context, ty) }
+        match ret {
+            None => Ok(Py::new(py, Type::void(py, context))?.to_object(py)),
+            Some(ret) => unsafe { Type::from_any(py, context, basic_to_any(ret)) },
+        }
     }
 
     /// The types of the function parameters.
@@ -122,6 +201,12 @@ impl FunctionType {
     }
 }
 
+impl FunctionType {
+    pub(crate) unsafe fn get(&self) -> inkwell::types::FunctionType<'static> {
+        self.0
+    }
+}
+
 /// A structure type.
 #[pyclass(extends = Type, unsendable)]
 pub(crate) struct StructType(inkwell::types::StructType<'static>);
@@ -176,11 +261,35 @@ impl ArrayType {
 }
 
 /// A pointer type.
+///
+/// :param Type pointee: The type being pointed to.
 #[pyclass(extends = Type, unsendable)]
 pub(crate) struct PointerType(inkwell::types::PointerType<'static>);
 
 #[pymethods]
 impl PointerType {
+    #[new]
+    fn new(pointee: &Type) -> PyResult<(Self, Type)> {
+        let ty = match pointee.ty {
+            AnyTypeEnum::ArrayType(a) => Ok(a.ptr_type(AddressSpace::Generic)),
+            AnyTypeEnum::FloatType(f) => Ok(f.ptr_type(AddressSpace::Generic)),
+            AnyTypeEnum::FunctionType(f) => Ok(f.ptr_type(AddressSpace::Generic)),
+            AnyTypeEnum::IntType(i) => Ok(i.ptr_type(AddressSpace::Generic)),
+            AnyTypeEnum::PointerType(p) => Ok(p.ptr_type(AddressSpace::Generic)),
+            AnyTypeEnum::StructType(s) => Ok(s.ptr_type(AddressSpace::Generic)),
+            AnyTypeEnum::VectorType(v) => Ok(v.ptr_type(AddressSpace::Generic)),
+            AnyTypeEnum::VoidType(_) => Err(PyValueError::new_err("Pointer to void type.")),
+        }?;
+
+        Ok((
+            Self(ty),
+            Type {
+                ty: ty.into(),
+                context: pointee.context.clone(),
+            },
+        ))
+    }
+
     /// The type being pointed to.
     ///
     /// :type: Type
@@ -200,6 +309,21 @@ impl PointerType {
     }
 }
 
+/// The QIR qubit type.
+///
+/// :param Context context: The global context.
+/// :returns: The qubit type.
+/// :rtype: Type
+#[pyfunction]
+pub(crate) fn qubit_type(py: Python, context: Py<Context>) -> PyResult<PyObject> {
+    let ty = {
+        let context = context.borrow(py);
+        let ty = types::qubit(&context.void_type().get_context()).into();
+        unsafe { transmute::<AnyTypeEnum<'_>, AnyTypeEnum<'static>>(ty) }
+    };
+    unsafe { Type::from_any(py, context, ty) }
+}
+
 /// Whether the type is the QIR qubit type.
 ///
 /// :param Type ty: The type.
@@ -207,10 +331,25 @@ impl PointerType {
 /// :returns: True if the type is the QIR qubit type.
 #[pyfunction]
 #[pyo3(text_signature = "(ty)")]
-pub(crate) fn is_qubit(ty: &Type) -> bool {
+pub(crate) fn is_qubit_type(ty: &Type) -> bool {
     types::is_qubit(ty.ty)
 }
 
+/// The QIR result type.
+///
+/// :param Context context: The global context.
+/// :returns: The result type.
+/// :rtype: Type
+#[pyfunction]
+pub(crate) fn result_type(py: Python, context: Py<Context>) -> PyResult<PyObject> {
+    let ty = {
+        let context = context.borrow(py);
+        let ty = types::result(&context.void_type().get_context()).into();
+        unsafe { transmute::<AnyTypeEnum<'_>, AnyTypeEnum<'static>>(ty) }
+    };
+    unsafe { Type::from_any(py, context, ty) }
+}
+
 /// Whether the type is the QIR result type.
 ///
 /// :param Type ty: The type.
@@ -218,7 +357,7 @@ pub(crate) fn is_qubit(ty: &Type) -> bool {
 /// :returns: True if the type is the QIR result type.
 #[pyfunction]
 #[pyo3(text_signature = "(ty)")]
-pub(crate) fn is_result(ty: &Type) -> bool {
+pub(crate) fn is_result_type(ty: &Type) -> bool {
     types::is_result(ty.ty)
 }
 
@@ -232,3 +371,20 @@ fn basic_to_any(ty: BasicTypeEnum) -> AnyTypeEnum {
         BasicTypeEnum::VectorType(v) => v.into(),
     }
 }
+
+pub(crate) fn function<'ctx>(
+    ret: &impl AnyType<'ctx>,
+    params: impl IntoIterator<Item = AnyTypeEnum<'ctx>>,
+) -> Option<inkwell::types::FunctionType<'ctx>> {
+    let params = params
+        .into_iter()
+        .map(|ty| BasicTypeEnum::try_from(ty).map(Into::into).ok())
+        .collect::<Option<Vec<_>>>()?;
+
+    match ret.as_any_type_enum() {
+        AnyTypeEnum::VoidType(void) => Some(void.fn_type(&params, false)),
+        any => BasicTypeEnum::try_from(any)
+            .map(|basic| basic.fn_type(&params, false))
+            .ok(),
+    }
+}
diff --git a/pyqir/src/values.rs b/pyqir/src/values.rs
index 2660325e..7ba90bc5 100644
--- a/pyqir/src/values.rs
+++ b/pyqir/src/values.rs
@@ -3,7 +3,12 @@
 
 #![allow(clippy::used_underscore_binding)]
 
-use crate::{context::Context, instructions::Instruction, module::Attribute, types::Type};
+use crate::{
+    context::{self, Context},
+    instructions::Instruction,
+    module::{Attribute, Module},
+    types::{FunctionType, Type},
+};
 use inkwell::{
     attributes::AttributeLoc,
     types::{AnyType, AnyTypeEnum},
@@ -105,11 +110,51 @@ impl Value {
 }
 
 /// A basic block.
+///
+/// :param Context context: The global context.
+/// :param str name: The block name.
+/// :param Union[Function, BasicBlock] insertion:
+///     Append the block to the given function or insert the block after the given block.
 #[pyclass(extends = Value, unsendable)]
 pub(crate) struct BasicBlock(inkwell::basic_block::BasicBlock<'static>);
 
+#[derive(FromPyObject)]
+enum BlockInsertion {
+    AppendTo(Py<Function>),
+    InsertAfter(Py<BasicBlock>),
+}
+
 #[pymethods]
 impl BasicBlock {
+    #[new]
+    fn new(
+        py: Python,
+        context: Py<Context>,
+        name: &str,
+        insertion: BlockInsertion,
+    ) -> PyClassInitializer<Self> {
+        let block = {
+            let context = context.borrow(py);
+            let block = match insertion {
+                BlockInsertion::AppendTo(function) => {
+                    context.append_basic_block(function.borrow(py).0, name)
+                }
+                BlockInsertion::InsertAfter(block) => {
+                    context.insert_basic_block_after(block.borrow(py).0, name)
+                }
+            };
+            unsafe {
+                transmute::<
+                    inkwell::basic_block::BasicBlock<'_>,
+                    inkwell::basic_block::BasicBlock<'static>,
+                >(block)
+            }
+        };
+
+        let value = block.into();
+        PyClassInitializer::from(Value { value, context }).add_subclass(Self(block))
+    }
+
     /// The instructions in this basic block.
     ///
     /// :type: List[Instruction]
@@ -143,6 +188,12 @@ impl BasicBlock {
     }
 }
 
+impl BasicBlock {
+    pub(crate) unsafe fn get(&self) -> inkwell::basic_block::BasicBlock<'static> {
+        self.0
+    }
+}
+
 /// A constant value.
 #[pyclass(extends = Value, subclass)]
 pub(crate) struct Constant;
@@ -214,11 +265,34 @@ impl FloatConstant {
 }
 
 /// A function value.
+///
+/// :param FunctionType ty: The function type.
+/// :param Linkage linkage: The linkage kind.
+/// :param str name: The function name.
+/// :param Module module: The parent module.
 #[pyclass(extends = Constant, unsendable)]
 pub(crate) struct Function(FunctionValue<'static>);
 
 #[pymethods]
 impl Function {
+    #[new]
+    fn new(
+        py: Python,
+        ty: PyRef<FunctionType>,
+        linkage: Linkage,
+        name: &str,
+        module: &Module,
+    ) -> PyResult<PyClassInitializer<Self>> {
+        let function_ty = unsafe { ty.get() };
+        let context = module.context();
+        context::require_same(py, [ty.into_super().context(), context])?;
+        let function =
+            unsafe { module.get() }.add_function(name, function_ty, Some(linkage.into()));
+        Ok(unsafe { Value::init(context.clone(), function.into()) }
+            .add_subclass(Constant)
+            .add_subclass(Self(function)))
+    }
+
     /// The parameters to this function.
     ///
     /// :type: List[Value]
@@ -265,6 +339,52 @@ impl Function {
     }
 }
 
+/// The linkage kind for a global value in a module.
+#[pyclass]
+#[derive(Clone)]
+pub(crate) enum Linkage {
+    #[pyo3(name = "APPENDING")]
+    Appending,
+    #[pyo3(name = "AVAILABLE_EXTERNALLY")]
+    AvailableExternally,
+    #[pyo3(name = "COMMON")]
+    Common,
+    #[pyo3(name = "EXTERNAL")]
+    External,
+    #[pyo3(name = "EXTERNAL_WEAK")]
+    ExternalWeak,
+    #[pyo3(name = "INTERNAL")]
+    Internal,
+    #[pyo3(name = "LINK_ONCE_ANY")]
+    LinkOnceAny,
+    #[pyo3(name = "LINK_ONCE_ODR")]
+    LinkOnceOdr,
+    #[pyo3(name = "PRIVATE")]
+    Private,
+    #[pyo3(name = "WEAK_ANY")]
+    WeakAny,
+    #[pyo3(name = "WEAK_ODR")]
+    WeakOdr,
+}
+
+impl From<Linkage> for inkwell::module::Linkage {
+    fn from(linkage: Linkage) -> Self {
+        match linkage {
+            Linkage::Appending => Self::Appending,
+            Linkage::AvailableExternally => Self::AvailableExternally,
+            Linkage::Common => Self::Common,
+            Linkage::External => Self::External,
+            Linkage::ExternalWeak => Self::ExternalWeak,
+            Linkage::Internal => Self::Internal,
+            Linkage::LinkOnceAny => Self::LinkOnceAny,
+            Linkage::LinkOnceOdr => Self::LinkOnceODR,
+            Linkage::Private => Self::Private,
+            Linkage::WeakAny => Self::WeakAny,
+            Linkage::WeakOdr => Self::WeakODR,
+        }
+    }
+}
+
 #[derive(Clone, Copy)]
 pub(crate) enum AnyValue<'ctx> {
     Any(AnyValueEnum<'ctx>),
@@ -399,7 +519,7 @@ impl<'ctx> TryFrom<AnyValue<'ctx>> for AnyValueEnum<'ctx> {
     }
 }
 
-impl<'ctx> TryFrom<AnyValue<'ctx>> for BasicMetadataValueEnum<'ctx> {
+impl<'ctx> TryFrom<AnyValue<'ctx>> for BasicValueEnum<'ctx> {
     type Error = ConvertError;
 
     fn try_from(value: AnyValue<'ctx>) -> Result<Self, Self::Error> {
@@ -412,15 +532,31 @@ impl<'ctx> TryFrom<AnyValue<'ctx>> for BasicMetadataValueEnum<'ctx> {
             AnyValue::Any(AnyValueEnum::VectorValue(v)) => Some(v.into()),
             AnyValue::Any(AnyValueEnum::InstructionValue(i)) => i
                 .try_into()
-                .map(BasicMetadataValueEnum::IntValue)
-                .or_else(|()| i.try_into().map(BasicMetadataValueEnum::FloatValue))
-                .or_else(|()| i.try_into().map(BasicMetadataValueEnum::PointerValue))
+                .map(BasicValueEnum::IntValue)
+                .or_else(|()| i.try_into().map(BasicValueEnum::FloatValue))
+                .or_else(|()| i.try_into().map(BasicValueEnum::PointerValue))
                 .ok(),
-            AnyValue::Any(AnyValueEnum::MetadataValue(m)) => Some(m.into()),
-            AnyValue::Any(AnyValueEnum::PhiValue(_) | AnyValueEnum::FunctionValue(_))
+            AnyValue::Any(
+                AnyValueEnum::PhiValue(_)
+                | AnyValueEnum::FunctionValue(_)
+                | AnyValueEnum::MetadataValue(_),
+            )
             | AnyValue::BasicBlock(_) => None,
         }
-        .ok_or(ConvertError("argument value"))
+        .ok_or(ConvertError("basic value"))
+    }
+}
+
+impl<'ctx> TryFrom<AnyValue<'ctx>> for BasicMetadataValueEnum<'ctx> {
+    type Error = ConvertError;
+
+    fn try_from(value: AnyValue<'ctx>) -> Result<Self, Self::Error> {
+        match value {
+            AnyValue::Any(AnyValueEnum::MetadataValue(m)) => Ok(m.into()),
+            _ => BasicValueEnum::try_from(value)
+                .map(BasicMetadataValueEnum::from)
+                .map_err(|_| ConvertError("argument value")),
+        }
     }
 }
 
@@ -555,28 +691,85 @@ pub(crate) fn const_getelementptr(
     unsafe { Value::from_any(py, value.context.clone(), gep) }
 }
 
+/// Creates a static qubit value.
+///
+/// :param Context context: The global context.
+/// :param int id: The static qubit ID.
+/// :returns: A static qubit value.
+/// :rtype: Value
+#[pyfunction]
+pub(crate) fn qubit(py: Python, context: Py<Context>, id: u64) -> PyResult<PyObject> {
+    let value = {
+        let context = context.borrow(py);
+        let value = values::qubit(&context.void_type().get_context(), id);
+        unsafe { transmute::<PointerValue<'_>, PointerValue<'static>>(value) }
+    };
+    unsafe { Value::from_any(py, context, value) }
+}
+
 /// If the value is a static qubit ID, extracts it.
 ///
 /// :param Value value: The value.
-/// :rtype: Optional[int]
 /// :returns: The static qubit ID.
+/// :rtype: Optional[int]
 #[pyfunction]
 #[pyo3(text_signature = "(value)")]
 pub(crate) fn qubit_id(value: &Value) -> Option<u64> {
     values::qubit_id(unsafe { value.get() }.try_into().ok()?)
 }
 
+/// Creates a static result value.
+///
+/// :param Context context: The global context.
+/// :param int id: The static result ID.
+/// :returns: A static result value.
+/// :rtype: Value
+#[pyfunction]
+pub(crate) fn result(py: Python, context: Py<Context>, id: u64) -> PyResult<PyObject> {
+    let value = {
+        let context = context.borrow(py);
+        let value = values::result(&context.void_type().get_context(), id);
+        unsafe { transmute::<PointerValue<'_>, PointerValue<'static>>(value) }
+    };
+    unsafe { Value::from_any(py, context, value) }
+}
+
 /// If the value is a static result ID, extracts it.
 ///
 /// :param Value value: The value.
-/// :rtype: Optional[int]
 /// :returns: The static result ID.
+/// :rtype: Optional[int]
 #[pyfunction]
 #[pyo3(text_signature = "(value)")]
 pub(crate) fn result_id(value: &Value) -> Option<u64> {
     values::result_id(unsafe { value.get() }.try_into().ok()?)
 }
 
+/// Creates an 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.
+/// :returns: An entry point.
+/// :rtype: Function
+#[pyfunction]
+pub(crate) fn entry_point(
+    py: Python,
+    module: &Module,
+    name: &str,
+    required_num_qubits: u64,
+    required_num_results: u64,
+) -> PyResult<PyObject> {
+    let entry_point = values::entry_point(
+        unsafe { module.get() },
+        name,
+        required_num_qubits,
+        required_num_results,
+    );
+    unsafe { Value::from_any(py, module.context().clone(), entry_point) }
+}
+
 /// Whether the function is an entry point.
 ///
 /// :param Function function: The function.
diff --git a/qirlib/src/evaluation/jit.rs b/qirlib/src/evaluation/jit.rs
index 25c7bb06..2245530b 100644
--- a/qirlib/src/evaluation/jit.rs
+++ b/qirlib/src/evaluation/jit.rs
@@ -7,7 +7,7 @@ use crate::{
         intrinsics::{reset_max_qubit_id, reset_static_result_cache, set_measure_stream},
         runtime::Simulator,
     },
-    module,
+    passes,
 };
 use bitvec::prelude::BitVec;
 use inkwell::{
@@ -61,7 +61,7 @@ pub fn run_module(
         return Err("Target doesn't have a target machine.".to_owned());
     }
 
-    module::run_basic_passes(module);
+    passes::run_basic(module);
     let entry_point = choose_entry_point(module_functions(module), entry_point)?;
 
     // load the symbols for the current process (empty/null string)
diff --git a/qirlib/src/lib.rs b/qirlib/src/lib.rs
index f6b49efc..5c529ee2 100644
--- a/qirlib/src/lib.rs
+++ b/qirlib/src/lib.rs
@@ -17,7 +17,7 @@ pub mod builder;
 #[cfg(not(feature = "no-llvm-linking"))]
 pub mod evaluation;
 #[cfg(not(feature = "no-llvm-linking"))]
-pub mod module;
+pub mod passes;
 #[cfg(not(feature = "no-llvm-linking"))]
 pub mod qis;
 #[cfg(all(test, not(feature = "no-llvm-linking")))]
diff --git a/qirlib/src/module.rs b/qirlib/src/module.rs
deleted file mode 100644
index debdc3d3..00000000
--- a/qirlib/src/module.rs
+++ /dev/null
@@ -1,99 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
-
-use inkwell::{
-    attributes::AttributeLoc,
-    builder::Builder,
-    module::Module,
-    passes::{PassManager, PassManagerBuilder},
-    values::FunctionValue,
-    OptimizationLevel,
-};
-
-pub fn simple_init(
-    module: &Module,
-    builder: &Builder,
-    required_num_qubits: u64,
-    required_num_results: u64,
-) {
-    let context = module.get_context();
-    let entry_point = create_entry_point(module);
-    add_num_attribute(entry_point, "requiredQubits", required_num_qubits);
-    add_num_attribute(entry_point, "requiredResults", required_num_results);
-    let entry = context.append_basic_block(entry_point, "entry");
-    builder.position_at_end(entry);
-}
-
-#[allow(clippy::missing_errors_doc)]
-pub fn simple_finalize(module: &Module) -> Result<(), String> {
-    run_basic_passes(module);
-    module.verify().map_err(|e| e.to_string())
-}
-
-// This method returns true if any of the passes modified the function or module and false otherwise.
-pub(crate) fn run_basic_passes(module: &Module) -> bool {
-    let pass_manager_builder = PassManagerBuilder::create();
-    pass_manager_builder.set_optimization_level(OptimizationLevel::None);
-    let fpm = PassManager::create(());
-    fpm.add_global_dce_pass();
-    fpm.add_strip_dead_prototypes_pass();
-    pass_manager_builder.populate_module_pass_manager(&fpm);
-    fpm.run_on(module)
-}
-
-fn create_entry_point<'ctx>(module: &Module<'ctx>) -> FunctionValue<'ctx> {
-    let context = module.get_context();
-    let fn_type = context.void_type().fn_type(&[], false);
-    let fn_value = module.add_function("main", fn_type, None);
-
-    let entry_point_attribute = context.create_string_attribute("EntryPoint", "");
-    fn_value.add_attribute(AttributeLoc::Function, entry_point_attribute);
-    fn_value
-}
-
-fn add_num_attribute(function: FunctionValue, key: &str, value: u64) {
-    let context = function.get_type().get_context();
-    let attribute = context.create_string_attribute(key, &value.to_string());
-    function.add_attribute(AttributeLoc::Function, attribute);
-}
-
-#[cfg(test)]
-mod tests {
-    use crate::{module::create_entry_point, tests::assert_reference_ir};
-    use inkwell::context::Context;
-
-    #[test]
-    fn entry_point_function_has_correct_signature_and_default_attribute() {
-        let context = Context::create();
-        let module = context.create_module("test");
-        let builder = context.create_builder();
-
-        let entry_point = create_entry_point(&module);
-        let entry = context.append_basic_block(entry_point, "entry");
-        builder.position_at_end(entry);
-        builder.build_return(None);
-        let ir_string = module.print_to_string().to_string();
-        let expected = "; ModuleID = 'test'\nsource_filename = \"test\"\n\ndefine void @main() #0 {\nentry:\n  ret void\n}\n\nattributes #0 = { \"EntryPoint\" }\n";
-        assert_eq!(expected, ir_string);
-    }
-
-    #[test]
-    fn zero_required_qubits_results() -> Result<(), String> {
-        assert_reference_ir("module/zero_required_qubits_results", 0, 0, |_| ())
-    }
-
-    #[test]
-    fn one_required_qubit() -> Result<(), String> {
-        assert_reference_ir("module/one_required_qubit", 1, 0, |_| ())
-    }
-
-    #[test]
-    fn one_required_result() -> Result<(), String> {
-        assert_reference_ir("module/one_required_result", 0, 1, |_| ())
-    }
-
-    #[test]
-    fn many_required_qubits_results() -> Result<(), String> {
-        assert_reference_ir("module/many_required_qubits_results", 5, 7, |_| ())
-    }
-}
diff --git a/qirlib/src/passes.rs b/qirlib/src/passes.rs
new file mode 100644
index 00000000..e33caa53
--- /dev/null
+++ b/qirlib/src/passes.rs
@@ -0,0 +1,27 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+use inkwell::{
+    module::Module,
+    passes::{PassManager, PassManagerBuilder},
+    OptimizationLevel,
+};
+
+pub enum Status {
+    Changed,
+    NoOp,
+}
+
+pub fn run_basic(module: &Module) -> Status {
+    let builder = PassManagerBuilder::create();
+    builder.set_optimization_level(OptimizationLevel::None);
+    let manager = PassManager::create(());
+    manager.add_global_dce_pass();
+    manager.add_strip_dead_prototypes_pass();
+    builder.populate_module_pass_manager(&manager);
+    if manager.run_on(module) {
+        Status::Changed
+    } else {
+        Status::NoOp
+    }
+}
diff --git a/qirlib/src/values.rs b/qirlib/src/values.rs
index 190cdcfb..8f950e53 100644
--- a/qirlib/src/values.rs
+++ b/qirlib/src/values.rs
@@ -6,6 +6,7 @@ use core::slice;
 use inkwell::{
     attributes::AttributeLoc,
     context::ContextRef,
+    module::Module,
     types::{AnyTypeEnum, PointerType},
     values::{AnyValueEnum, FunctionValue, PointerValue},
     LLVMReference,
@@ -53,6 +54,24 @@ pub fn result_id(value: AnyValueEnum) -> Option<u64> {
     }
 }
 
+pub fn entry_point<'ctx>(
+    module: &Module<'ctx>,
+    name: &str,
+    required_num_qubits: u64,
+    required_num_results: u64,
+) -> FunctionValue<'ctx> {
+    let context = module.get_context();
+    let ty = context.void_type().fn_type(&[], false);
+    let entry_point = module.add_function(name, ty, None);
+    entry_point.add_attribute(
+        AttributeLoc::Function,
+        context.create_string_attribute("EntryPoint", ""),
+    );
+    add_num_attribute(entry_point, "requiredQubits", required_num_qubits);
+    add_num_attribute(entry_point, "requiredResults", required_num_results);
+    entry_point
+}
+
 #[must_use]
 pub fn is_entry_point(function: FunctionValue) -> bool {
     function
@@ -107,6 +126,12 @@ pub fn constant_bytes(value: AnyValueEnum) -> Option<&[u8]> {
     Some(&data[offset..])
 }
 
+fn add_num_attribute(function: FunctionValue, key: &str, value: u64) {
+    let context = function.get_type().get_context();
+    let attribute = context.create_string_attribute(key, &value.to_string());
+    function.add_attribute(AttributeLoc::Function, attribute);
+}
+
 fn pointer_to_int(value: AnyValueEnum) -> Option<u64> {
     match value {
         AnyValueEnum::PointerValue(p) => {
@@ -124,3 +149,28 @@ fn is_byte_string(ty: PointerType) -> bool {
         _ => false,
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use crate::tests::assert_reference_ir;
+
+    #[test]
+    fn zero_required_qubits_results() -> Result<(), String> {
+        assert_reference_ir("module/zero_required_qubits_results", 0, 0, |_| ())
+    }
+
+    #[test]
+    fn one_required_qubit() -> Result<(), String> {
+        assert_reference_ir("module/one_required_qubit", 1, 0, |_| ())
+    }
+
+    #[test]
+    fn one_required_result() -> Result<(), String> {
+        assert_reference_ir("module/one_required_result", 0, 1, |_| ())
+    }
+
+    #[test]
+    fn many_required_qubits_results() -> Result<(), String> {
+        assert_reference_ir("module/many_required_qubits_results", 5, 7, |_| ())
+    }
+}


Test patch:
diff --git a/pyqir/tests/test_entry_points.py b/pyqir/tests/test_entry_points.py
new file mode 100644
index 00000000..5ff6bbb8
--- /dev/null
+++ b/pyqir/tests/test_entry_points.py
@@ -0,0 +1,86 @@
+# Copyright (c) Microsoft Corporation.
+# Licensed under the MIT License.
+
+from pyqir import (
+    BasicBlock,
+    BasicQisBuilder,
+    Builder,
+    Context,
+    Function,
+    FunctionType,
+    Linkage,
+    Module,
+    Type,
+    entry_point,
+    is_entry_point,
+    qubit,
+    result,
+    verify_module,
+)
+
+
+def bell(module: Module) -> Function:
+    entry = entry_point(module, "bell", 2, 2)
+    context = module.context
+    builder = Builder(context)
+    builder.insert_at_end(BasicBlock(context, "", entry))
+    qis = BasicQisBuilder(builder)
+    qis.h(qubit(context, 0))
+    qis.cx(qubit(context, 0), qubit(context, 1))
+    qis.mz(qubit(context, 0), result(context, 0))
+    qis.mz(qubit(context, 1), result(context, 1))
+    builder.ret(None)
+    return entry
+
+
+def external(module: Module) -> Function:
+    context = module.context
+    barrier = Function(
+        FunctionType(Type.void(context), []),
+        Linkage.EXTERNAL,
+        "__quantum__qis__barrier__body",
+        module,
+    )
+
+    entry = entry_point(module, "barrier", 0, 0)
+    builder = Builder(context)
+    builder.insert_at_end(BasicBlock(context, "", entry))
+    builder.call(barrier, [])
+    builder.ret(None)
+    return entry
+
+
+def test_multiple() -> None:
+    module = Module(Context(), "test")
+    bell_function = bell(module)
+    external_function = external(module)
+    assert verify_module(module) is None
+
+    names = list(map(lambda f: f.name, filter(is_entry_point, module.functions)))
+    assert len(names) == 2
+    assert bell_function.name in names
+    assert external_function.name in names
+
+
+def test_duplicate_name() -> None:
+    module = Module(Context(), "test")
+    bell1 = bell(module)
+    bell2 = bell(module)
+    assert verify_module(module) is None
+    assert bell1.name != bell2.name
+
+    names = list(map(lambda f: f.name, filter(is_entry_point, module.functions)))
+    assert len(names) == 2
+    assert bell1.name in names
+    assert bell2.name in names
+
+
+def test_invalid_module() -> None:
+    context = Context()
+    module = Module(context, "test")
+    main = entry_point(module, "main", 0, 0)
+    BasicBlock(context, "", main)
+    assert (
+        verify_module(module)
+        == "Basic Block in function 'main' does not have terminator!\nlabel %0\n"
+    )
diff --git a/pyqir/tests/test_parser.py b/pyqir/tests/test_parser.py
index eb1690c4..47961037 100644
--- a/pyqir/tests/test_parser.py
+++ b/pyqir/tests/test_parser.py
@@ -14,7 +14,7 @@
     constant_bytes,
     is_entry_point,
     is_interop_friendly,
-    is_qubit,
+    is_qubit_type,
     qubit_id,
     required_num_qubits,
     result_id,
@@ -219,7 +219,7 @@ def test_parser_internals() -> None:
 
     arg = args[0]
     assert isinstance(arg, Constant)
-    assert is_qubit(arg.type)
+    assert is_qubit_type(arg.type)
     assert qubit_id(arg) == 0
 
     call = entry_block.instructions[8]
diff --git a/pyqir/tests/test_types.py b/pyqir/tests/test_types.py
new file mode 100644
index 00000000..5d8e75b4
--- /dev/null
+++ b/pyqir/tests/test_types.py
@@ -0,0 +1,68 @@
+# Copyright (c) Microsoft Corporation.
+# Licensed under the MIT License.
+
+from pyqir import (
+    Context,
+    FunctionType,
+    IntType,
+    PointerType,
+    Type,
+    is_qubit_type,
+    is_result_type,
+    qubit_type,
+    result_type,
+)
+import pytest
+
+
+def test_void() -> None:
+    void = Type.void(Context())
+    assert type(void) is Type
+    assert void.is_void
+
+
+def test_double() -> None:
+    double = Type.double(Context())
+    assert type(double) is Type
+    assert double.is_double
+
+
+@pytest.mark.parametrize("width", [1, 2, 8, 16, 32, 64, 128])
+def test_int(width: int) -> None:
+    i = IntType(Context(), width)
+    assert i.width == width
+
+
+def test_function() -> None:
+    context = Context()
+    function = FunctionType(
+        Type.void(context),
+        [
+            Type.double(context),
+            IntType(context, 64),
+        ],
+    )
+    assert function.ret.is_void
+    assert len(function.params) == 2
+    assert function.params[0].is_double
+    assert isinstance(function.params[1], IntType)
+
+
+def test_pointer() -> None:
+    pointer = PointerType(IntType(Context(), 1))
+    assert isinstance(pointer.pointee, IntType)
+
+
+def test_void_pointer() -> None:
+    with pytest.raises(ValueError):
+        PointerType(Type.void(Context()))
+
+
+def test_qubit() -> None:
+    qubit = qubit_type(Context())
+    assert is_qubit_type(qubit)
+
+
+def test_result() -> None:
+    result = result_type(Context())
+    assert is_result_type(result)
diff --git a/qirlib/resources/tests/module/many_required_qubits_results.ll b/qirlib/resources/tests/module/many_required_qubits_results.ll
index 101faa1a..19034029 100644
--- a/qirlib/resources/tests/module/many_required_qubits_results.ll
+++ b/qirlib/resources/tests/module/many_required_qubits_results.ll
@@ -2,7 +2,6 @@
 source_filename = "many_required_qubits_results"
 
 define void @main() #0 {
-entry:
   ret void
 }
 
diff --git a/qirlib/resources/tests/module/one_required_qubit.ll b/qirlib/resources/tests/module/one_required_qubit.ll
index 74fdef3b..b31b4caa 100644
--- a/qirlib/resources/tests/module/one_required_qubit.ll
+++ b/qirlib/resources/tests/module/one_required_qubit.ll
@@ -2,7 +2,6 @@
 source_filename = "one_required_qubit"
 
 define void @main() #0 {
-entry:
   ret void
 }
 
diff --git a/qirlib/resources/tests/module/one_required_result.ll b/qirlib/resources/tests/module/one_required_result.ll
index 9cd4d4f7..d30f915f 100644
--- a/qirlib/resources/tests/module/one_required_result.ll
+++ b/qirlib/resources/tests/module/one_required_result.ll
@@ -2,7 +2,6 @@
 source_filename = "one_required_result"
 
 define void @main() #0 {
-entry:
   ret void
 }
 
diff --git a/qirlib/resources/tests/module/zero_required_qubits_results.ll b/qirlib/resources/tests/module/zero_required_qubits_results.ll
index af4ccf41..bafd0f9c 100644
--- a/qirlib/resources/tests/module/zero_required_qubits_results.ll
+++ b/qirlib/resources/tests/module/zero_required_qubits_results.ll
@@ -2,7 +2,6 @@
 source_filename = "zero_required_qubits_results"
 
 define void @main() #0 {
-entry:
   ret void
 }
 
diff --git a/qirlib/resources/tests/qis/cx.ll b/qirlib/resources/tests/qis/cx.ll
index 3feffb77..3635c434 100644
--- a/qirlib/resources/tests/qis/cx.ll
+++ b/qirlib/resources/tests/qis/cx.ll
@@ -4,7 +4,6 @@ source_filename = "cx"
 %Qubit = type opaque
 
 define void @main() #0 {
-entry:
   call void @__quantum__qis__cnot__body(%Qubit* null, %Qubit* inttoptr (i64 1 to %Qubit*))
   ret void
 }
diff --git a/qirlib/resources/tests/qis/cz.ll b/qirlib/resources/tests/qis/cz.ll
index a9390d41..61afbc7c 100644
--- a/qirlib/resources/tests/qis/cz.ll
+++ b/qirlib/resources/tests/qis/cz.ll
@@ -4,7 +4,6 @@ source_filename = "cz"
 %Qubit = type opaque
 
 define void @main() #0 {
-entry:
   call void @__quantum__qis__cz__body(%Qubit* null, %Qubit* inttoptr (i64 1 to %Qubit*))
   ret void
 }
diff --git a/qirlib/resources/tests/qis/empty_if.ll b/qirlib/resources/tests/qis/empty_if.ll
index c01111c7..aaa7f94f 100644
--- a/qirlib/resources/tests/qis/empty_if.ll
+++ b/qirlib/resources/tests/qis/empty_if.ll
@@ -5,15 +5,14 @@ source_filename = "empty_if"
 %Result = type opaque
 
 define void @main() #0 {
-entry:
   call void @__quantum__qis__mz__body(%Qubit* null, %Result* null)
-  %0 = call i1 @__quantum__qis__read_result__body(%Result* null)
-  br i1 %0, label %then, label %else
+  %1 = call i1 @__quantum__qis__read_result__body(%Result* null)
+  br i1 %1, label %then, label %else
 
-then:                                             ; preds = %entry
+then:                                             ; preds = %0
   br label %continue
 
-else:                                             ; preds = %entry
+else:                                             ; preds = %0
   br label %continue
 
 continue:                                         ; preds = %else, %then
diff --git a/qirlib/resources/tests/qis/h.ll b/qirlib/resources/tests/qis/h.ll
index 1400e5fa..fe45c122 100644
--- a/qirlib/resources/tests/qis/h.ll
+++ b/qirlib/resources/tests/qis/h.ll
@@ -4,7 +4,6 @@ source_filename = "h"
 %Qubit = type opaque
 
 define void @main() #0 {
-entry:
   call void @__quantum__qis__h__body(%Qubit* null)
   ret void
 }
diff --git a/qirlib/resources/tests/qis/if_else.ll b/qirlib/resources/tests/qis/if_else.ll
index 56c0c82d..25b07185 100644
--- a/qirlib/resources/tests/qis/if_else.ll
+++ b/qirlib/resources/tests/qis/if_else.ll
@@ -5,15 +5,14 @@ source_filename = "if_else"
 %Result = type opaque
 
 define void @main() #0 {
-entry:
   call void @__quantum__qis__mz__body(%Qubit* null, %Result* null)
-  %0 = call i1 @__quantum__qis__read_result__body(%Result* null)
-  br i1 %0, label %then, label %else
+  %1 = call i1 @__quantum__qis__read_result__body(%Result* null)
+  br i1 %1, label %then, label %else
 
-then:                                             ; preds = %entry
+then:                                             ; preds = %0
   br label %continue
 
-else:                                             ; preds = %entry
+else:                                             ; preds = %0
   call void @__quantum__qis__x__body(%Qubit* null)
   br label %continue
 
diff --git a/qirlib/resources/tests/qis/if_else_continue.ll b/qirlib/resources/tests/qis/if_else_continue.ll
index 4089ca27..1db2a6f5 100644
--- a/qirlib/resources/tests/qis/if_else_continue.ll
+++ b/qirlib/resources/tests/qis/if_else_continue.ll
@@ -5,15 +5,14 @@ source_filename = "if_else_continue"
 %Result = type opaque
 
 define void @main() #0 {
-entry:
   call void @__quantum__qis__mz__body(%Qubit* null, %Result* null)
-  %0 = call i1 @__quantum__qis__read_result__body(%Result* null)
-  br i1 %0, label %then, label %else
+  %1 = call i1 @__quantum__qis__read_result__body(%Result* null)
+  br i1 %1, label %then, label %else
 
-then:                                             ; preds = %entry
+then:                                             ; preds = %0
   br label %continue
 
-else:                                             ; preds = %entry
+else:                                             ; preds = %0
   call void @__quantum__qis__x__body(%Qubit* null)
   br label %continue
 
diff --git a/qirlib/resources/tests/qis/if_else_else.ll b/qirlib/resources/tests/qis/if_else_else.ll
index b411a364..eb387f83 100644
--- a/qirlib/resources/tests/qis/if_else_else.ll
+++ b/qirlib/resources/tests/qis/if_else_else.ll
@@ -5,18 +5,17 @@ source_filename = "if_else_else"
 %Result = type opaque
 
 define void @main() #0 {
-entry:
   call void @__quantum__qis__mz__body(%Qubit* null, %Result* null)
   call void @__quantum__qis__mz__body(%Qubit* null, %Result* inttoptr (i64 1 to %Result*))
-  %0 = call i1 @__quantum__qis__read_result__body(%Result* null)
-  br i1 %0, label %then, label %else
+  %1 = call i1 @__quantum__qis__read_result__body(%Result* null)
+  br i1 %1, label %then, label %else
 
-then:                                             ; preds = %entry
+then:                                             ; preds = %0
   br label %continue
 
-else:                                             ; preds = %entry
-  %1 = call i1 @__quantum__qis__read_result__body(%Result* inttoptr (i64 1 to %Result*))
-  br i1 %1, label %then1, label %else2
+else:                                             ; preds = %0
+  %2 = call i1 @__quantum__qis__read_result__body(%Result* inttoptr (i64 1 to %Result*))
+  br i1 %2, label %then1, label %else2
 
 continue:                                         ; preds = %continue3, %then
   ret void
diff --git a/qirlib/resources/tests/qis/if_else_then.ll b/qirlib/resources/tests/qis/if_else_then.ll
index f9883650..05f02e51 100644
--- a/qirlib/resources/tests/qis/if_else_then.ll
+++ b/qirlib/resources/tests/qis/if_else_then.ll
@@ -5,18 +5,17 @@ source_filename = "if_else_then"
 %Result = type opaque
 
 define void @main() #0 {
-entry:
   call void @__quantum__qis__mz__body(%Qubit* null, %Result* null)
   call void @__quantum__qis__mz__body(%Qubit* null, %Result* inttoptr (i64 1 to %Result*))
-  %0 = call i1 @__quantum__qis__read_result__body(%Result* null)
-  br i1 %0, label %then, label %else
+  %1 = call i1 @__quantum__qis__read_result__body(%Result* null)
+  br i1 %1, label %then, label %else
 
-then:                                             ; preds = %entry
+then:                                             ; preds = %0
   br label %continue
 
-else:                                             ; preds = %entry
-  %1 = call i1 @__quantum__qis__read_result__body(%Result* inttoptr (i64 1 to %Result*))
-  br i1 %1, label %then1, label %else2
+else:                                             ; preds = %0
+  %2 = call i1 @__quantum__qis__read_result__body(%Result* inttoptr (i64 1 to %Result*))
+  br i1 %2, label %then1, label %else2
 
 continue:                                         ; preds = %continue3, %then
   ret void
diff --git a/qirlib/resources/tests/qis/if_then.ll b/qirlib/resources/tests/qis/if_then.ll
index 912da36e..88f3d548 100644
--- a/qirlib/resources/tests/qis/if_then.ll
+++ b/qirlib/resources/tests/qis/if_then.ll
@@ -5,16 +5,15 @@ source_filename = "if_then"
 %Result = type opaque
 
 define void @main() #0 {
-entry:
   call void @__quantum__qis__mz__body(%Qubit* null, %Result* null)
-  %0 = call i1 @__quantum__qis__read_result__body(%Result* null)
-  br i1 %0, label %then, label %else
+  %1 = call i1 @__quantum__qis__read_result__body(%Result* null)
+  br i1 %1, label %then, label %else
 
-then:                                             ; preds = %entry
+then:                                             ; preds = %0
   call void @__quantum__qis__x__body(%Qubit* null)
   br label %continue
 
-else:                                             ; preds = %entry
+else:                                             ; preds = %0
   br label %continue
 
 continue:                                         ; preds = %else, %then
diff --git a/qirlib/resources/tests/qis/if_then_continue.ll b/qirlib/resources/tests/qis/if_then_continue.ll
index fa6f2e3c..b083c5ec 100644
--- a/qirlib/resources/tests/qis/if_then_continue.ll
+++ b/qirlib/resources/tests/qis/if_then_continue.ll
@@ -5,16 +5,15 @@ source_filename = "if_then_continue"
 %Result = type opaque
 
 define void @main() #0 {
-entry:
   call void @__quantum__qis__mz__body(%Qubit* null, %Result* null)
-  %0 = call i1 @__quantum__qis__read_result__body(%Result* null)
-  br i1 %0, label %then, label %else
+  %1 = call i1 @__quantum__qis__read_result__body(%Result* null)
+  br i1 %1, label %then, label %else
 
-then:                                             ; preds = %entry
+then:                                             ; preds = %0
   call void @__quantum__qis__x__body(%Qubit* null)
   br label %continue
 
-else:                                             ; preds = %entry
+else:                                             ; preds = %0
   br label %continue
 
 continue:                                         ; preds = %else, %then
diff --git a/qirlib/resources/tests/qis/if_then_else.ll b/qirlib/resources/tests/qis/if_then_else.ll
index 49bbe99a..60b8c2cf 100644
--- a/qirlib/resources/tests/qis/if_then_else.ll
+++ b/qirlib/resources/tests/qis/if_then_else.ll
@@ -5,17 +5,16 @@ source_filename = "if_then_else"
 %Result = type opaque
 
 define void @main() #0 {
-entry:
   call void @__quantum__qis__mz__body(%Qubit* null, %Result* null)
   call void @__quantum__qis__mz__body(%Qubit* null, %Result* inttoptr (i64 1 to %Result*))
-  %0 = call i1 @__quantum__qis__read_result__body(%Result* null)
-  br i1 %0, label %then, label %else
+  %1 = call i1 @__quantum__qis__read_result__body(%Result* null)
+  br i1 %1, label %then, label %else
 
-then:                                             ; preds = %entry
-  %1 = call i1 @__quantum__qis__read_result__body(%Result* inttoptr (i64 1 to %Result*))
-  br i1 %1, label %then1, label %else2
+then:                                             ; preds = %0
+  %2 = call i1 @__quantum__qis__read_result__body(%Result* inttoptr (i64 1 to %Result*))
+  br i1 %2, label %then1, label %else2
 
-else:                                             ; preds = %entry
+else:                                             ; preds = %0
   br label %continue
 
 continue:                                         ; preds = %else, %continue3
diff --git a/qirlib/resources/tests/qis/if_then_else_continue.ll b/qirlib/resources/tests/qis/if_then_else_continue.ll
index 6ec0e42e..e990141b 100644
--- a/qirlib/resources/tests/qis/if_then_else_continue.ll
+++ b/qirlib/resources/tests/qis/if_then_else_continue.ll
@@ -5,16 +5,15 @@ source_filename = "if_then_else_continue"
 %Result = type opaque
 
 define void @main() #0 {
-entry:
   call void @__quantum__qis__mz__body(%Qubit* null, %Result* null)
-  %0 = call i1 @__quantum__qis__read_result__body(%Result* null)
-  br i1 %0, label %then, label %else
+  %1 = call i1 @__quantum__qis__read_result__body(%Result* null)
+  br i1 %1, label %then, label %else
 
-then:                                             ; preds = %entry
+then:                                             ; preds = %0
   call void @__quantum__qis__x__body(%Qubit* null)
   br label %continue
 
-else:                                             ; preds = %entry
+else:                                             ; preds = %0
   call void @__quantum__qis__y__body(%Qubit* null)
   br label %continue
 
diff --git a/qirlib/resources/tests/qis/if_then_then.ll b/qirlib/resources/tests/qis/if_then_then.ll
index 61ea8e70..8d70f8fb 100644
--- a/qirlib/resources/tests/qis/if_then_then.ll
+++ b/qirlib/resources/tests/qis/if_then_then.ll
@@ -5,17 +5,16 @@ source_filename = "if_then_then"
 %Result = type opaque
 
 define void @main() #0 {
-entry:
   call void @__quantum__qis__mz__body(%Qubit* null, %Result* null)
   call void @__quantum__qis__mz__body(%Qubit* null, %Result* inttoptr (i64 1 to %Result*))
-  %0 = call i1 @__quantum__qis__read_result__body(%Result* null)
-  br i1 %0, label %then, label %else
+  %1 = call i1 @__quantum__qis__read_result__body(%Result* null)
+  br i1 %1, label %then, label %else
 
-then:                                             ; preds = %entry
-  %1 = call i1 @__quantum__qis__read_result__body(%Result* inttoptr (i64 1 to %Result*))
-  br i1 %1, label %then1, label %else2
+then:                                             ; preds = %0
+  %2 = call i1 @__quantum__qis__read_result__body(%Result* inttoptr (i64 1 to %Result*))
+  br i1 %2, label %then1, label %else2
 
-else:                                             ; preds = %entry
+else:                                             ; preds = %0
   br label %continue
 
 continue:                                         ; preds = %else, %continue3
diff --git a/qirlib/resources/tests/qis/if_unmeasured_result.ll b/qirlib/resources/tests/qis/if_unmeasured_result.ll
index fe282066..bba80979 100644
--- a/qirlib/resources/tests/qis/if_unmeasured_result.ll
+++ b/qirlib/resources/tests/qis/if_unmeasured_result.ll
@@ -5,15 +5,14 @@ source_filename = "if_unmeasured_result"
 %Qubit = type opaque
 
 define void @main() #0 {
-entry:
-  %0 = call i1 @__quantum__qis__read_result__body(%Result* null)
-  br i1 %0, label %then, label %else
+  %1 = call i1 @__quantum__qis__read_result__body(%Result* null)
+  br i1 %1, label %then, label %else
 
-then:                                             ; preds = %entry
+then:                                             ; preds = %0
   call void @__quantum__qis__x__body(%Qubit* null)
   br label %continue
 
-else:                                             ; preds = %entry
+else:                                             ; preds = %0
   call void @__quantum__qis__h__body(%Qubit* null)
   br label %continue
 
diff --git a/qirlib/resources/tests/qis/mz.ll b/qirlib/resources/tests/qis/mz.ll
index 3576d88b..2ff884f9 100644
--- a/qirlib/resources/tests/qis/mz.ll
+++ b/qirlib/resources/tests/qis/mz.ll
@@ -5,7 +5,6 @@ source_filename = "mz"
 %Result = type opaque
 
 define void @main() #0 {
-entry:
   call void @__quantum__qis__mz__body(%Qubit* null, %Result* null)
   ret void
 }
diff --git a/qirlib/resources/tests/qis/read_result.ll b/qirlib/resources/tests/qis/read_result.ll
index faa6407f..d4fb87d9 100644
--- a/qirlib/resources/tests/qis/read_result.ll
+++ b/qirlib/resources/tests/qis/read_result.ll
@@ -4,8 +4,7 @@ source_filename = "read_result"
 %Result = type opaque
 
 define void @main() #0 {
-entry:
-  %0 = call i1 @__quantum__qis__read_result__body(%Result* null)
+  %1 = call i1 @__quantum__qis__read_result__body(%Result* null)
   ret void
 }
 
diff --git a/qirlib/resources/tests/qis/reset.ll b/qirlib/resources/tests/qis/reset.ll
index bf90e6ba..4530487c 100644
--- a/qirlib/resources/tests/qis/reset.ll
+++ b/qirlib/resources/tests/qis/reset.ll
@@ -4,7 +4,6 @@ source_filename = "reset"
 %Qubit = type opaque
 
 define void @main() #0 {
-entry:
   call void @__quantum__qis__reset__body(%Qubit* null)
   ret void
 }
diff --git a/qirlib/resources/tests/qis/rx.ll b/qirlib/resources/tests/qis/rx.ll
index 15a53630..2fd52620 100644
--- a/qirlib/resources/tests/qis/rx.ll
+++ b/qirlib/resources/tests/qis/rx.ll
@@ -4,7 +4,6 @@ source_filename = "rx"
 %Qubit = type opaque
 
 define void @main() #0 {
-entry:
   call void @__quantum__qis__rx__body(double 0.000000e+00, %Qubit* null)
   ret void
 }
diff --git a/qirlib/resources/tests/qis/ry.ll b/qirlib/resources/tests/qis/ry.ll
index 15691a00..6623fdd7 100644
--- a/qirlib/resources/tests/qis/ry.ll
+++ b/qirlib/resources/tests/qis/ry.ll
@@ -4,7 +4,6 @@ source_filename = "ry"
 %Qubit = type opaque
 
 define void @main() #0 {
-entry:
   call void @__quantum__qis__ry__body(double 0.000000e+00, %Qubit* null)
   ret void
 }
diff --git a/qirlib/resources/tests/qis/rz.ll b/qirlib/resources/tests/qis/rz.ll
index ea99c80c..eafe35c4 100644
--- a/qirlib/resources/tests/qis/rz.ll
+++ b/qirlib/resources/tests/qis/rz.ll
@@ -4,7 +4,6 @@ source_filename = "rz"
 %Qubit = type opaque
 
 define void @main() #0 {
-entry:
   call void @__quantum__qis__rz__body(double 0.000000e+00, %Qubit* null)
   ret void
 }
diff --git a/qirlib/resources/tests/qis/s.ll b/qirlib/resources/tests/qis/s.ll
index 0e6e9892..0971b015 100644
--- a/qirlib/resources/tests/qis/s.ll
+++ b/qirlib/resources/tests/qis/s.ll
@@ -4,7 +4,6 @@ source_filename = "s"
 %Qubit = type opaque
 
 define void @main() #0 {
-entry:
   call void @__quantum__qis__s__body(%Qubit* null)
   ret void
 }
diff --git a/qirlib/resources/tests/qis/s_adj.ll b/qirlib/resources/tests/qis/s_adj.ll
index 637d2a53..ec334adc 100644
--- a/qirlib/resources/tests/qis/s_adj.ll
+++ b/qirlib/resources/tests/qis/s_adj.ll
@@ -4,7 +4,6 @@ source_filename = "s_adj"
 %Qubit = type opaque
 
 define void @main() #0 {
-entry:
   call void @__quantum__qis__s__adj(%Qubit* null)
   ret void
 }
diff --git a/qirlib/resources/tests/qis/t.ll b/qirlib/resources/tests/qis/t.ll
index 658e30b6..bda3b047 100644
--- a/qirlib/resources/tests/qis/t.ll
+++ b/qirlib/resources/tests/qis/t.ll
@@ -4,7 +4,6 @@ source_filename = "t"
 %Qubit = type opaque
 
 define void @main() #0 {
-entry:
   call void @__quantum__qis__t__body(%Qubit* null)
   ret void
 }
diff --git a/qirlib/resources/tests/qis/t_adj.ll b/qirlib/resources/tests/qis/t_adj.ll
index 78c5fec3..a65947b9 100644
--- a/qirlib/resources/tests/qis/t_adj.ll
+++ b/qirlib/resources/tests/qis/t_adj.ll
@@ -4,7 +4,6 @@ source_filename = "t_adj"
 %Qubit = type opaque
 
 define void @main() #0 {
-entry:
   call void @__quantum__qis__t__adj(%Qubit* null)
   ret void
 }
diff --git a/qirlib/resources/tests/qis/x.ll b/qirlib/resources/tests/qis/x.ll
index 148d2a9d..dcde67f8 100644
--- a/qirlib/resources/tests/qis/x.ll
+++ b/qirlib/resources/tests/qis/x.ll
@@ -4,7 +4,6 @@ source_filename = "x"
 %Qubit = type opaque
 
 define void @main() #0 {
-entry:
   call void @__quantum__qis__x__body(%Qubit* null)
   ret void
 }
diff --git a/qirlib/resources/tests/qis/y.ll b/qirlib/resources/tests/qis/y.ll
index f548265e..6eafa53a 100644
--- a/qirlib/resources/tests/qis/y.ll
+++ b/qirlib/resources/tests/qis/y.ll
@@ -4,7 +4,6 @@ source_filename = "y"
 %Qubit = type opaque
 
 define void @main() #0 {
-entry:
   call void @__quantum__qis__y__body(%Qubit* null)
   ret void
 }
diff --git a/qirlib/resources/tests/qis/z.ll b/qirlib/resources/tests/qis/z.ll
index 3f544219..bf2b7030 100644
--- a/qirlib/resources/tests/qis/z.ll
+++ b/qirlib/resources/tests/qis/z.ll
@@ -4,7 +4,6 @@ source_filename = "z"
 %Qubit = type opaque
 
 define void @main() #0 {
-entry:
   call void @__quantum__qis__z__body(%Qubit* null)
   ret void
 }
diff --git a/qirlib/src/tests.rs b/qirlib/src/tests.rs
index 98fb533e..433bf63b 100644
--- a/qirlib/src/tests.rs
+++ b/qirlib/src/tests.rs
@@ -1,8 +1,8 @@
 // Copyright (c) Microsoft Corporation.
 // Licensed under the MIT License.
 
-use crate::module;
-use inkwell::{builder::Builder, context::Context};
+use crate::{passes, values};
+use inkwell::{builder::Builder, context::Context, support::LLVMString};
 use normalize_line_endings::normalized;
 use std::{env, fs, path::PathBuf};
 
@@ -21,7 +21,8 @@ pub(crate) fn assert_reference_ir(
 ) -> Result<(), String> {
     const PYQIR_TEST_SAVE_REFERENCES: &str = "PYQIR_TEST_SAVE_REFERENCES";
     let (prefix, name) = split_id(id);
-    let actual_ir = build_ir(name, required_num_qubits, required_num_results, build)?;
+    let actual_ir = build_ir(name, required_num_qubits, required_num_results, build)
+        .map_err(|e| e.to_string())?;
 
     let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
     path.push("resources");
@@ -32,8 +33,7 @@ pub(crate) fn assert_reference_ir(
 
     if env::var(PYQIR_TEST_SAVE_REFERENCES).is_ok() {
         fs::create_dir_all(path.parent().unwrap()).map_err(|e| e.to_string())?;
-        fs::write(&path, actual_ir).map_err(|e| e.to_string())?;
-
+        fs::write(&path, actual_ir.to_bytes()).map_err(|e| e.to_string())?;
         Err(format!(
             "Saved reference IR. Run again without the {} environment variable.",
             PYQIR_TEST_SAVE_REFERENCES
@@ -41,7 +41,7 @@ pub(crate) fn assert_reference_ir(
     } else {
         let contents = fs::read_to_string(&path).map_err(|e| e.to_string())?;
         let expected_ir: String = normalized(contents.chars()).collect();
-        assert_eq!(expected_ir, actual_ir);
+        assert_eq!(expected_ir, actual_ir.to_str().map_err(|e| e.to_string())?);
         Ok(())
     }
 }
@@ -51,19 +51,24 @@ fn build_ir(
     required_num_qubits: u64,
     required_num_results: u64,
     build: impl for<'ctx> Fn(&Builder<'ctx>),
-) -> Result<String, String> {
+) -> Result<LLVMString, LLVMString> {
     let context = Context::create();
     let module = context.create_module(name);
+    let entry_point =
+        values::entry_point(&module, "main", required_num_qubits, required_num_results);
+
     let builder = context.create_builder();
-    module::simple_init(&module, &builder, required_num_qubits, required_num_results);
+    builder.position_at_end(context.append_basic_block(entry_point, ""));
     build(&builder);
     builder.build_return(None);
-    module::simple_finalize(&module)?;
-    Ok(module.print_to_string().to_string())
+
+    passes::run_basic(&module);
+    module.verify()?;
+    Ok(module.print_to_string())
 }
 
 fn split_id(id: &str) -> (Vec<&str>, &str) {
     let mut parts: Vec<_> = id.split('/').collect();
-    let name = parts.pop().unwrap();
+    let name = parts.pop().expect("Empty string.");
     (parts, name)
 }