an-inkwell 26.0.0

Astery's Narrower Inkwell
use llvm_sys::core::{LLVMAddIncoming, LLVMBuildPhi};
use llvm_sys::prelude::LLVMBasicBlockRef;

use crate::{Block, Builder, Error, Type, Value};

impl<'ctx> Builder<'ctx> {
    pub fn phi(&self, ty: &Type<'ctx>) -> Result<Value<'ctx>, Error> {
        if !std::ptr::eq(self.context(), ty.context()) {
            return Err(Error::DifferentContext);
        }

        let raw = unsafe { LLVMBuildPhi(self.as_raw(), ty.as_raw(), c"".as_ptr()) };
        Ok(Value::from_raw(self.context(), raw))
    }

    pub fn add_phi_incoming(
        &self,
        phi: &Value<'ctx>,
        incoming: &[(&Value<'ctx>, &Block<'ctx>)],
    ) -> Result<(), Error> {
        if !std::ptr::eq(self.context(), phi.context()) {
            return Err(Error::DifferentContext);
        }

        for (value, block) in incoming {
            if !std::ptr::eq(self.context(), value.context())
                || !std::ptr::eq(self.context(), block.context())
            {
                return Err(Error::DifferentContext);
            }
        }

        let mut values = incoming
            .iter()
            .map(|(value, _)| value.as_raw())
            .collect::<Vec<_>>();
        let mut blocks = incoming
            .iter()
            .map(|(_, block)| block.as_raw())
            .collect::<Vec<LLVMBasicBlockRef>>();

        unsafe {
            LLVMAddIncoming(
                phi.as_raw(),
                values.as_mut_ptr(),
                blocks.as_mut_ptr(),
                values.len() as u32,
            );
        }

        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::Builder;
    use crate::{Context, Type, Value};

    fn function(
        context: &Context,
    ) -> (
        crate::Module<'_>,
        Builder<'_>,
        crate::Block<'_>,
        crate::Block<'_>,
        crate::Block<'_>,
    ) {
        let module = context.module("test").unwrap();
        let builder = context.builder().unwrap();
        let function_type = Type::function(&Type::i32(context), &[], false).unwrap();
        let function = module.function("main", &function_type).unwrap();
        let entry = function.block("entry").unwrap();
        let left = function.block("left").unwrap();
        let right = function.block("right").unwrap();
        builder.position(&entry).unwrap();
        (module, builder, entry, left, right)
    }

    #[test]
    fn builds_phi_with_incoming_values() {
        let context = Context::create();
        let (module, builder, _entry, left, right) = function(&context);
        let integer = Type::i32(&context);
        let phi = builder.phi(&integer).unwrap();
        let left_value = Value::integer(&integer, 1, false);
        let right_value = Value::integer(&integer, 2, false);

        builder
            .add_phi_incoming(&phi, &[(&left_value, &left), (&right_value, &right)])
            .unwrap();

        let ir = module.as_ir();
        assert!(ir.contains("%"));
        assert!(phi.as_ir().contains("phi i32"));
    }

    #[test]
    fn rejects_phi_from_different_context() {
        let first = Context::create();
        let second = Context::create();
        let (_module, builder, _entry, left, _right) = function(&first);
        let integer = Type::i32(&second);
        let phi = builder.phi(&integer);

        assert!(matches!(phi, Err(crate::Error::DifferentContext)));

        let integer = Type::i32(&first);
        let phi = builder.phi(&integer).unwrap();
        let other_integer = Type::i32(&second);
        let value = Value::integer(&other_integer, 1, false);
        assert!(matches!(
            builder.add_phi_incoming(&phi, &[(&value, &left)]),
            Err(crate::Error::DifferentContext)
        ));
    }
}