Skip to content
Snippets Groups Projects
cpu.rs 35.82 KiB
use std::collections::BTreeMap;
use std::fmt::{Error, Write};
use std::iter::zip;
use std::sync::atomic::{AtomicUsize, Ordering};

use hercules_ir::*;

use crate::*;

static NUM_FILLER_REGS: AtomicUsize = AtomicUsize::new(0);

/*
 * The top level function to compile a Hercules IR function into LLVM IR for
 * execution on the CPU. We generate LLVM IR textually, since there are no good
 * LLVM bindings for Rust, and we are *not* writing any C++.
 */
pub fn cpu_codegen<W: Write>(
    function: &Function,
    types: &Vec<Type>,
    constants: &Vec<Constant>,
    dynamic_constants: &Vec<DynamicConstant>,
    typing: &Vec<TypeID>,
    control_subgraph: &Subgraph,
    bbs: &BasicBlocks,
    w: &mut W,
) -> Result<(), Error> {
    let ctx = CPUContext {
        function,
        types,
        constants,
        dynamic_constants,
        typing,
        control_subgraph,
        bbs,
    };
    ctx.codegen_function(w)
}

struct CPUContext<'a> {
    function: &'a Function,
    types: &'a Vec<Type>,
    constants: &'a Vec<Constant>,
    dynamic_constants: &'a Vec<DynamicConstant>,
    typing: &'a Vec<TypeID>,
    control_subgraph: &'a Subgraph,
    bbs: &'a BasicBlocks,
}

#[derive(Default, Debug)]
struct LLVMBlock {
    phis: String,
    body: String,
    term: String,
}

impl<'a> CPUContext<'a> {
    fn codegen_function<W: Write>(&self, w: &mut W) -> Result<(), Error> {
        // Dump the function signature.
        if self.types[self.function.return_type.idx()].is_primitive() {
            write!(
                w,
                "define dso_local {} @{}(",
                self.get_type(self.function.return_type),
                self.function.name
            )?;
        } else {
            write!(
                w,
                "define dso_local nonnull noundef {} @{}(",
                self.get_type(self.function.return_type),
                self.function.name
            )?;
        }
        let mut first_param = true;
        // The first set of parameters are dynamic constants.
        for idx in 0..self.function.num_dynamic_constants {
            if first_param {
                first_param = false;
            } else {
                write!(w, ", ")?;
            }
            write!(w, "i64 %dc_p{}", idx)?;
        }
        // The second set of parameters are normal parameters.
        for (idx, ty) in self.function.param_types.iter().enumerate() {
            if first_param {
                first_param = false;
            } else {
                write!(w, ", ")?;
            }
            if self.types[ty.idx()].is_primitive() {
                write!(w, "{} %p{}", self.get_type(*ty), idx)?;
            } else {
                write!(
                    w,
                    "{} noalias nofree nonnull noundef %p{}",
                    self.get_type(*ty),
                    idx
                )?;
            }
        }
        write!(w, ") {{\n")?;

        let mut blocks: BTreeMap<_, _> = (0..self.function.nodes.len())
            .filter(|idx| self.function.nodes[*idx].is_control())
            .map(|idx| (NodeID::new(idx), LLVMBlock::default()))
            .collect();

        // Emit calculation of dynamic constants into the start block. Calculate
        // every valid dynamic constant, and let LLVM clean them up.
        self.codegen_dynamic_constants(
            blocks.get_mut(&NodeID::new(0)).unwrap(),
            self.function.num_dynamic_constants,
        )?;

        // Emit data flow into basic blocks.
        for block in self.bbs.1.iter() {
            for id in block {
                self.codegen_data_node(*id, &mut blocks)?;
            }
        }

        // Emit control flow into basic blocks.
        for id in (0..self.function.nodes.len()).map(NodeID::new) {
            if !self.function.nodes[id.idx()].is_control() {
                continue;
            }
            self.codegen_control_node(id, &mut blocks)?;
        }

        // Dump the emitted basic blocks.
        for (id, block) in blocks {
            write!(
                w,
                "{}:\n{}{}{}",
                self.get_block_name(id),
                block.phis,
                block.body,
                block.term
            )?;
        }

        write!(w, "}}\n")?;
        Ok(())
    }

    /*
     * While control nodes in Hercules IR are predecessor-centric (each take a
     * control input that defines the predecessor relationship), LLVM IR basic
     * blocks are successor centric (each branch to successor blocks with a
     * branch instruction). This difference requires explicit translation.
     */
    fn codegen_control_node(
        &self,
        id: NodeID,
        blocks: &mut BTreeMap<NodeID, LLVMBlock>,
    ) -> Result<(), Error> {
        match self.function.nodes[id.idx()] {
            // Start, region, and projection control nodes all have exactly one
            // successor and are otherwise simple.
            Node::Start
            | Node::Region { preds: _ }
            | Node::Projection {
                control: _,
                selection: _,
            } => {
                let term = &mut blocks.get_mut(&id).unwrap().term;
                let succ = self.control_subgraph.succs(id).next().unwrap();
                write!(term, "  br label %{}\n", self.get_block_name(succ))?
            }
            // If nodes have two successors - examine the projections to
            // determine which branch is which, and branch between them.
            Node::If { control: _, cond } => {
                let term = &mut blocks.get_mut(&id).unwrap().term;
                let mut succs = self.control_subgraph.succs(id);
                let succ1 = succs.next().unwrap();
                let succ2 = succs.next().unwrap();
                let succ1_is_true = self.function.nodes[succ1.idx()].try_projection(1).is_some();
                write!(
                    term,
                    "  br {}, label %{}, label %{}\n",
                    self.get_value(cond, true),
                    self.get_block_name(if succ1_is_true { succ1 } else { succ2 }),
                    self.get_block_name(if succ1_is_true { succ2 } else { succ1 }),
                )?
            }
            Node::Return { control: _, data } => {
                let term = &mut blocks.get_mut(&id).unwrap().term;
                write!(term, "  ret {}\n", self.get_value(data, true))?
            }
            _ => panic!(
                "PANIC: Can't lower {:?} in {}.",
                self.function.nodes[id.idx()],
                self.function.name
            ),
        }
        Ok(())
    }

    /*
     * Lower data nodes in Hercules IR into LLVM instructions.
     */
    fn codegen_data_node(
        &self,
        id: NodeID,
        blocks: &mut BTreeMap<NodeID, LLVMBlock>,
    ) -> Result<(), Error> {
        match self.function.nodes[id.idx()] {
            Node::Phi { control, ref data } => {
                let phis = &mut blocks.get_mut(&self.bbs.0[id.idx()]).unwrap().phis;
                let preds = self.function.nodes[control.idx()].try_region().unwrap();
                write!(
                    phis,
                    "  {} = phi {} ",
                    self.get_value(id, false),
                    self.get_type(self.typing[id.idx()])
                )?;
                for idx in 0..preds.len() {
                    if idx != 0 {
                        write!(phis, ", ")?;
                    }
                    write!(
                        phis,
                        "[ {}, %{} ]",
                        self.get_value(data[idx], false),
                        self.get_block_name(preds[idx])
                    )?;
                }
                write!(phis, "\n")?;
            }
            Node::Parameter { index } => {
                let body = &mut blocks.get_mut(&self.bbs.0[id.idx()]).unwrap().body;
                let ty = self.get_type(self.typing[id.idx()]);
                write!(
                    body,
                    "  {} = bitcast {} %p{} to {}\n",
                    self.get_value(id, false),
                    ty,
                    index,
                    ty
                )?;
            }
            Node::Constant { id: cons_id } => {
                let body = &mut blocks.get_mut(&self.bbs.0[id.idx()]).unwrap().body;
                write!(body, "  {} = bitcast ", self.get_value(id, false))?;
                match self.constants[cons_id.idx()] {
                    Constant::Boolean(val) => write!(body, "i1 {} to i1\n", val)?,
                    Constant::Integer8(val) => write!(body, "i8 {} to i8\n", val)?,
                    Constant::Integer16(val) => write!(body, "i16 {} to i16\n", val)?,
                    Constant::Integer32(val) => write!(body, "i32 {} to i32\n", val)?,
                    Constant::Integer64(val) => write!(body, "i64 {} to i64\n", val)?,
                    Constant::UnsignedInteger8(val) => write!(body, "i8 {} to i8\n", val)?,
                    Constant::UnsignedInteger16(val) => write!(body, "i16 {} to i16\n", val)?,
                    Constant::UnsignedInteger32(val) => write!(body, "i32 {} to i32\n", val)?,
                    Constant::UnsignedInteger64(val) => write!(body, "i64 {} to i64\n", val)?,
                    Constant::Float32(val) => {
                        if val.fract() == 0.0 {
                            write!(body, "float {}.0 to float\n", val)?
                        } else {
                            write!(body, "float {} to float\n", val)?
                        }
                    }
                    Constant::Float64(val) => {
                        if val.fract() == 0.0 {
                            write!(body, "double {}.0 to double", val)?
                        } else {
                            write!(body, "double {} to double", val)?
                        }
                    }
                    _ => panic!("PANIC: Can't dynamically allocate memory for an aggregate type within a CPU function ({:?} in {}).", id, self.function.name),
                }
            }
            Node::DynamicConstant { id: dc_id } => {
                let body = &mut blocks.get_mut(&self.bbs.0[id.idx()]).unwrap().body;
                // Dynamic constants are all pre-calculated at the top of the
                // function.
                write!(
                    body,
                    "  {} = bitcast i64 %dc{} to i64\n",
                    self.get_value(id, false),
                    dc_id.idx()
                )?
            }
            Node::Unary { op, input } => {
                let body = &mut blocks.get_mut(&self.bbs.0[id.idx()]).unwrap().body;
                match op {
                    UnaryOperator::Not => write!(
                        body,
                        "  {} = xor {}, -1\n",
                        self.get_value(id, false),
                        self.get_value(input, true)
                    )?,
                    UnaryOperator::Neg => {
                        if self.types[self.typing[input.idx()].idx()].is_float() {
                            write!(
                                body,
                                "  {} = fneg {}",
                                self.get_value(id, false),
                                self.get_value(input, true)
                            )?
                        } else {
                            write!(
                                body,
                                "  {} = mul {}, -1",
                                self.get_value(id, false),
                                self.get_value(input, true)
                            )?
                        }
                    }
                    UnaryOperator::Cast(dst_ty_id) => {
                        let src_ty_id = self.typing[input.idx()];
                        let dst_ty = &self.types[dst_ty_id.idx()];
                        let src_ty = &self.types[src_ty_id.idx()];
                        let opcode = if src_ty.is_integer()
                            && dst_ty.is_integer()
                            && src_ty.num_bits() > dst_ty.num_bits()
                        {
                            "trunc"
                        } else if src_ty.is_signed()
                            && dst_ty.is_integer()
                            && src_ty.num_bits() < dst_ty.num_bits()
                        {
                            "sext"
                        } else if src_ty.is_integer()
                            && dst_ty.is_integer()
                            && src_ty.num_bits() < dst_ty.num_bits()
                        {
                            "zext"
                        } else if src_ty.is_integer() && dst_ty.is_integer() {
                            // A no-op.
                            "bitcast"
                        } else if src_ty.is_float()
                            && dst_ty.is_float()
                            && src_ty.num_bits() > dst_ty.num_bits()
                        {
                            "fptrunc"
                        } else if src_ty.is_float()
                            && dst_ty.is_float()
                            && src_ty.num_bits() < dst_ty.num_bits()
                        {
                            "fpext"
                        } else if src_ty.is_float() && dst_ty.is_float() {
                            // A no-op.
                            "bitcast"
                        } else if src_ty.is_float() && dst_ty.is_signed() {
                            "fptosi"
                        } else if src_ty.is_float() && dst_ty.is_integer() {
                            "fptoui"
                        } else if src_ty.is_signed() && dst_ty.is_float() {
                            "sitofp"
                        } else if src_ty.is_integer() && dst_ty.is_float() {
                            "uitofp"
                        } else {
                            panic!("PANIC: Invalid cast type combination.");
                        };
                        write!(
                            body,
                            "  {} = {} {} to {}\n",
                            self.get_value(id, false),
                            opcode,
                            self.get_value(input, true),
                            self.get_type(dst_ty_id),
                        )?
                    }
                }
            }
            Node::Binary { op, left, right } => {
                enum OpTy {
                    Float,
                    Unsigned,
                    Signed,
                }

                let left_ty = &self.types[self.typing[left.idx()].idx()];
                let op_ty = if left_ty.is_float() {
                    OpTy::Float
                } else if left_ty.is_unsigned() {
                    OpTy::Unsigned
                } else {
                    OpTy::Signed
                };

                let opcode = match (op, op_ty) {
                    (BinaryOperator::Add, OpTy::Float) => "fadd",
                    (BinaryOperator::Add, _) => "add",
                    (BinaryOperator::Sub, OpTy::Float) => "fsub",
                    (BinaryOperator::Sub, _) => "sub",
                    (BinaryOperator::Mul, OpTy::Float) => "fmul",
                    (BinaryOperator::Mul, _) => "mul",
                    (BinaryOperator::Div, OpTy::Float) => "fdiv",
                    (BinaryOperator::Div, OpTy::Unsigned) => "udiv",
                    (BinaryOperator::Div, OpTy::Signed) => "sdiv",
                    (BinaryOperator::Rem, OpTy::Float) => "frem",
                    (BinaryOperator::Rem, OpTy::Unsigned) => "urem",
                    (BinaryOperator::Rem, OpTy::Signed) => "srem",
                    (BinaryOperator::LT, OpTy::Float) => "fcmp olt",
                    (BinaryOperator::LT, OpTy::Unsigned) => "icmp ult",
                    (BinaryOperator::LT, OpTy::Signed) => "icmp slt",
                    (BinaryOperator::LTE, OpTy::Float) => "fcmp ole",
                    (BinaryOperator::LTE, OpTy::Unsigned) => "icmp ule",
                    (BinaryOperator::LTE, OpTy::Signed) => "icmp sle",
                    (BinaryOperator::GT, OpTy::Float) => "fcmp ogt",
                    (BinaryOperator::GT, OpTy::Unsigned) => "icmp ugt",
                    (BinaryOperator::GT, OpTy::Signed) => "icmp sgt",
                    (BinaryOperator::GTE, OpTy::Float) => "fcmp oge",
                    (BinaryOperator::GTE, OpTy::Unsigned) => "icmp uge",
                    (BinaryOperator::GTE, OpTy::Signed) => "icmp sge",
                    (BinaryOperator::EQ, OpTy::Float) => "fcmp oeq",
                    (BinaryOperator::EQ, _) => "icmp eq",
                    (BinaryOperator::NE, OpTy::Float) => "fcmp one",
                    (BinaryOperator::NE, _) => "icmp ne",
                    (BinaryOperator::Or, _) => "or",
                    (BinaryOperator::And, _) => "and",
                    (BinaryOperator::Xor, _) => "xor",
                    (BinaryOperator::LSh, _) => "lsh",
                    (BinaryOperator::RSh, OpTy::Unsigned) => "lshr",
                    (BinaryOperator::RSh, _) => "ashr",
                };

                let body = &mut blocks.get_mut(&self.bbs.0[id.idx()]).unwrap().body;
                write!(
                    body,
                    "  {} = {} {}, {}\n",
                    self.get_value(id, false),
                    opcode,
                    self.get_value(left, true),
                    self.get_value(right, false),
                )?
            }
            Node::Ternary {
                op,
                first,
                second,
                third,
            } => {
                let body = &mut blocks.get_mut(&self.bbs.0[id.idx()]).unwrap().body;
                match op {
                    TernaryOperator::Select => write!(
                        body,
                        "  {} = select {}, {}, {}\n",
                        self.get_value(id, false),
                        self.get_value(first, true),
                        self.get_value(second, true),
                        self.get_value(third, true)
                    )?,
                }
            }
            Node::IntrinsicCall {
                intrinsic,
                ref args,
            } => {
                let body = &mut blocks.get_mut(&self.bbs.0[id.idx()]).unwrap().body;
                write!(
                    body,
                    "  {} = call {} {}(",
                    self.get_value(id, false),
                    self.get_type(self.typing[id.idx()]),
                    convert_intrinsic(&intrinsic, &self.types[self.typing[id.idx()].idx()]),
                )?;
                for idx in 0..args.len() {
                    if idx != 0 {
                        write!(body, ", ")?;
                    }
                    write!(body, "{}", self.get_value(args[idx], true))?;
                }
                write!(body, ")\n")?
            }
            Node::Read {
                collect,
                ref indices,
            } => {
                let body = &mut blocks.get_mut(&self.bbs.0[id.idx()]).unwrap().body;
                let collect_name = self.get_value(collect, false);
                let collect_ty = self.typing[collect.idx()];
                let index_ptr_name =
                    self.codegen_index_math(&collect_name, collect_ty, indices, body)?;
                let self_ty = self.typing[id.idx()];
                if self.types[self_ty.idx()].is_primitive() {
                    // If this read reaches a primitive type, actually perform a
                    // load.
                    write!(
                        body,
                        "  {} = load {}, ptr {}\n",
                        self.get_value(id, false),
                        self.get_type(self_ty),
                        index_ptr_name
                    )?;
                } else {
                    // If this read doesn't reach a primitive type, just return
                    // the calculated offset pointer for the sub-collection.
                    write!(
                        body,
                        "  {} = bitcast ptr {} to ptr\n",
                        self.get_value(id, false),
                        index_ptr_name
                    )?;
                }
            }
            Node::Write {
                collect,
                data,
                ref indices,
            } => {
                let body = &mut blocks.get_mut(&self.bbs.0[id.idx()]).unwrap().body;
                let collect_name = self.get_value(collect, false);
                let collect_ty = self.typing[collect.idx()];
                let index_ptr_name =
                    self.codegen_index_math(&collect_name, collect_ty, indices, body)?;
                let data_ty = self.typing[data.idx()];
                if self.types[data_ty.idx()].is_primitive() {
                    // If the data item being written is a primitive type,
                    // perform a single store of the data value.
                    write!(
                        body,
                        "  store {}, ptr {}\n",
                        self.get_value(data, true),
                        index_ptr_name
                    )?;
                } else {
                    // If the data item being written is not a primitive type,
                    // then perform a memcpy from the data collection to the
                    // destination collection.
                    let data_size = self.codegen_type_size(data_ty, body)?;
                    write!(
                        body,
                        "  call void @llvm.memcpy.p0.p0.i64(ptr {}, {}, i64 {}, i1 false)\n",
                        index_ptr_name,
                        self.get_value(data, true),
                        data_size
                    )?;
                }
                write!(
                    body,
                    "  {} = bitcast {} to ptr\n",
                    self.get_value(id, false),
                    self.get_value(collect, true)
                )?;
            }
            Node::Undef { ty } => {
                let body = &mut blocks.get_mut(&self.bbs.0[id.idx()]).unwrap().body;
                let ty = self.get_type(ty);
                write!(
                    body,
                    "  {} = bitcast {} undef to {}\n",
                    self.get_value(id, false),
                    ty,
                    ty
                )?;
            }
            _ => panic!("PANIC: Can't lower {:?}.", self.function.nodes[id.idx()]),
        }
        Ok(())
    }

    /*
     * Calculate all of the dynamic constants upfront.
     */
    fn codegen_dynamic_constants(
        &self,
        block: &mut LLVMBlock,
        num_dc_params: u32,
    ) -> Result<(), Error> {
        let body = &mut block.body;
        for dc in dynamic_constants_bottom_up(&self.dynamic_constants) {
            match self.dynamic_constants[dc.idx()] {
                DynamicConstant::Constant(val) => {
                    write!(body, "  %dc{} = bitcast i64 {} to i64\n", dc.idx(), val)?
                }
                DynamicConstant::Parameter(idx) => {
                    if idx < num_dc_params as usize {
                        write!(
                            body,
                            "  %dc{} = bitcast i64 %dc_p{} to i64\n",
                            dc.idx(),
                            idx
                        )?
                    } else {
                        write!(body, "  %dc{} = bitcast i64 0 to i64\n", dc.idx())?
                    }
                }
                DynamicConstant::Add(left, right) => write!(
                    body,
                    "  %dc{} = add i64%dc{},%dc{}\n",
                    dc.idx(),
                    left.idx(),
                    right.idx()
                )?,
                DynamicConstant::Sub(left, right) => write!(
                    body,
                    "  %dc{} = sub i64%dc{},%dc{}\n",
                    dc.idx(),
                    left.idx(),
                    right.idx()
                )?,
                DynamicConstant::Mul(left, right) => write!(
                    body,
                    "  %dc{} = mul i64%dc{},%dc{}\n",
                    dc.idx(),
                    left.idx(),
                    right.idx()
                )?,
                DynamicConstant::Div(left, right) => write!(
                    body,
                    "  %dc{} = udiv i64%dc{},%dc{}\n",
                    dc.idx(),
                    left.idx(),
                    right.idx()
                )?,
                DynamicConstant::Rem(left, right) => write!(
                    body,
                    "  %dc{} = urem i64%dc{},%dc{}\n",
                    dc.idx(),
                    left.idx(),
                    right.idx()
                )?,
                DynamicConstant::Min(left, right) => write!(
                    body,
                    "  %dc{} = call @llvm.umin.i64(i64%dc{},i64%dc{})\n",
                    dc.idx(),
                    left.idx(),
                    right.idx()
                )?,
                DynamicConstant::Max(left, right) => write!(
                    body,
                    "  %dc{} = call @llvm.umax.i64(i64%dc{},i64%dc{})\n",
                    dc.idx(),
                    left.idx(),
                    right.idx()
                )?,
            }
        }
        Ok(())
    }

    /*
     * Emit logic to index into an collection.
     */
    fn codegen_index_math(
        &self,
        collect_name: &str,
        collect_ty: TypeID,
        indices: &[Index],
        body: &mut String,
    ) -> Result<String, Error> {
        let mut acc_ptr = collect_name.to_string();
        for index in indices {
            match index {
                Index::Field(idx) => {
                    let Type::Product(ref fields) = self.types[collect_ty.idx()] else {
                        panic!()
                    };

                    // Get the offset of the field at index `idx` by calculating
                    // the product's size up to field `idx`, then offseting the
                    // base pointer by that amount.
                    let mut acc_offset = "0".to_string();
                    for field in &fields[..*idx] {
                        let field_align = get_type_alignment(&self.types, *field);
                        let field = self.codegen_type_size(*field, body)?;
                        acc_offset = Self::round_up_to(&acc_offset, field_align, body)?;
                        acc_offset = Self::append(&acc_offset, &field, body)?;
                    }
                    acc_offset = Self::round_up_to(
                        &acc_offset,
                        get_type_alignment(&self.types, fields[*idx]),
                        body,
                    )?;
                    acc_ptr = Self::gep(&acc_ptr, &acc_offset, body)?;
                }
                Index::Variant(_) => {
                    // The tag of a summation is at the end of the summation, so
                    // the variant pointer is just the base pointer. Do nothing.
                }
                Index::Position(ref pos) => {
                    let Type::Array(elem, ref dims) = self.types[collect_ty.idx()] else {
                        panic!()
                    };

                    // The offset of the position into an array is:
                    //
                    //     ((0 * s1 + p1) * s2 + p2) * s3 + p3 ...
                    let elem_size = self.codegen_type_size(elem, body)?;
                    let mut acc_offset = "0".to_string();
                    for (p, s) in zip(pos, dims) {
                        let p = self.get_value(*p, false);
                        let s = format!("%dc{}", s.idx());
                        acc_offset = Self::multiply(&acc_offset, &s, body)?;
                        acc_offset = Self::append(&acc_offset, &p, body)?;
                    }

                    // Convert offset in # elements -> # bytes.
                    acc_offset = Self::multiply(&acc_offset, &elem_size, body)?;
                    acc_ptr = Self::gep(&acc_ptr, &acc_offset, body)?;
                }
            }
        }
        Ok(acc_ptr)
    }

    /*
     * Emit logic to calculate the size of a type. This needs to be emitted as
     * IR since the size of an array may depend on array constants.
     */
    fn codegen_type_size(&self, ty: TypeID, body: &mut String) -> Result<String, Error> {
        match self.types[ty.idx()] {
            Type::Control => panic!(),
            Type::Boolean | Type::Integer8 | Type::UnsignedInteger8 => Ok("1".to_string()),
            Type::Integer16 | Type::UnsignedInteger16 => Ok("2".to_string()),
            Type::Integer32 | Type::UnsignedInteger32 | Type::Float32 => Ok("4".to_string()),
            Type::Integer64 | Type::UnsignedInteger64 | Type::Float64 => Ok("8".to_string()),
            Type::Product(ref fields) => {
                let fields_align = fields
                    .into_iter()
                    .map(|id| get_type_alignment(&self.types, *id));
                let fields: Result<Vec<String>, Error> = fields
                    .into_iter()
                    .map(|id| self.codegen_type_size(*id, body))
                    .collect();

                // Emit LLVM IR to round up to the alignment of the next field,
                // and then add the size of that field. At the end, round up to
                // the alignment of the whole struct.
                let mut acc_size = "0".to_string();
                for (field_align, field) in zip(fields_align, fields?) {
                    acc_size = Self::round_up_to(&acc_size, field_align, body)?;
                    acc_size = Self::append(&acc_size, &field, body)?;
                }
                Self::round_up_to(&acc_size, get_type_alignment(&self.types, ty), body)
            }
            Type::Summation(ref variants) => {
                let variants: Result<Vec<String>, Error> = variants
                    .into_iter()
                    .map(|id| self.codegen_type_size(*id, body))
                    .collect();

                // The size of a summation is the size of the largest field,
                // plus 1 byte and alignment for the discriminant.
                let mut acc_size = "0".to_string();
                for variant in variants? {
                    acc_size = Self::max(&acc_size, &variant, body)?;
                }

                // No alignment is necessary for the 1 byte discriminant.
                acc_size = Self::append(&acc_size, "1", body)?;
                Self::round_up_to(&acc_size, get_type_alignment(&self.types, ty), body)
            }
            Type::Array(elem, ref bounds) => {
                // The size of an array is the size of the element multipled by
                // the dynamic constant bounds.
                let mut acc_size = self.codegen_type_size(elem, body)?;
                for dc in bounds {
                    acc_size = Self::multiply(&acc_size, &format!("%dc{}", dc.idx()), body)?;
                }
                Ok(acc_size)
            }
        }
    }

    fn get_value(&self, id: NodeID, ty: bool) -> String {
        if ty {
            format!("{} %v{}", self.get_type(self.typing[id.idx()]), id.idx())
        } else {
            format!("%v{}", id.idx())
        }
    }

    fn get_block_name(&self, id: NodeID) -> String {
        format!("bb_{}", id.idx())
    }
    fn get_type(&self, id: TypeID) -> &'static str {
        convert_type(&self.types[id.idx()])
    }

    // Use the trick that given a number `x` and a power of two `m`, we can
    // compute rounding up `x` to the next multiple of
    // `m` via:
    //
    //     (x + m - 1) & -m
    //
    // Which is equivalent to the following LLVM IR (`m` is a constant):
    //
    //     %1 = add i64 %x, (m-1)
    //     %2 = and i64 %1, -m
    fn round_up_to(x: &str, m: usize, body: &mut String) -> Result<String, Error> {
        let init_body_len = Self::gen_filler_id();
        write!(
            body,
            "  %round_up_to.{} = add i64 {}, {}\n",
            init_body_len,
            x,
            m - 1
        )?;
        let name = format!("%round_up_to.{}", Self::gen_filler_id());
        write!(
            body,
            "  {} = and i64 %round_up_to.{}, -{}\n",
            name, init_body_len, m
        )?;
        Ok(name)
    }

    // Emit LLVM IR to add the size of the next field.
    fn append(x: &str, f: &str, body: &mut String) -> Result<String, Error> {
        let name = format!("%append.{}", Self::gen_filler_id());
        write!(body, "  {} = add i64 {}, {}\n", name, x, f)?;
        Ok(name)
    }

    // Emit LLVM IR to get the maximum of two sizes.
    fn max(a: &str, b: &str, body: &mut String) -> Result<String, Error> {
        let name = format!("%max.{}", Self::gen_filler_id());
        write!(
            body,
            "  {} = call i64 @llvm.umax.i64(i64 {}, i64 {})\n",
            name, a, b
        )?;
        Ok(name)
    }

    // Emit LLVM IR to multiply two sizes.
    fn multiply(a: &str, b: &str, body: &mut String) -> Result<String, Error> {
        let name = format!("%multiply.{}", Self::gen_filler_id());
        write!(body, "  {} = mul i64 {}, {}\n", name, a, b)?;
        Ok(name)
    }

    // Emit LLVM IR to gep a pointer from a byte size.
    fn gep(ptr: &str, size: &str, body: &mut String) -> Result<String, Error> {
        let name = format!("%gep.{}", Self::gen_filler_id());
        write!(
            body,
            "  {} = getelementptr i8, ptr {}, i64 {}\n",
            name, ptr, size
        )?;
        Ok(name)
    }

    fn gen_filler_id() -> usize {
        NUM_FILLER_REGS.fetch_add(1, Ordering::Relaxed)
    }
}

fn convert_type(ty: &Type) -> &'static str {
    match ty {
        Type::Boolean => "i1",
        Type::Integer8 | Type::UnsignedInteger8 => "i8",
        Type::Integer16 | Type::UnsignedInteger16 => "i16",
        Type::Integer32 | Type::UnsignedInteger32 => "i32",
        Type::Integer64 | Type::UnsignedInteger64 => "i64",
        Type::Float32 => "float",
        Type::Float64 => "double",
        Type::Product(_) | Type::Summation(_) | Type::Array(_, _) => "ptr",
        _ => panic!(),
    }
}

fn convert_intrinsic(intrinsic: &Intrinsic, ty: &Type) -> String {
    let intrinsic = match intrinsic {
        Intrinsic::Abs => "abs",
        Intrinsic::ACos => "acos",
        Intrinsic::ASin => "asin",
        Intrinsic::ATan => "atan",
        Intrinsic::ATan2 => "atan2",
        Intrinsic::Ceil => "ceil",
        Intrinsic::Cos => "cos",
        Intrinsic::Cosh => "cosh",
        Intrinsic::Exp => "exp",
        Intrinsic::Exp2 => "exp2",
        Intrinsic::Floor => "floor",
        Intrinsic::Ln => "log",
        Intrinsic::Log10 => "log10",
        Intrinsic::Log2 => "log2",
        Intrinsic::Max => {
            if ty.is_float() {
                "maxnum"
            } else if ty.is_unsigned() {
                "umax"
            } else if ty.is_signed() {
                "smax"
            } else {
                panic!()
            }
        }
        Intrinsic::Min => {
            if ty.is_float() {
                "minnum"
            } else if ty.is_unsigned() {
                "umin"
            } else if ty.is_signed() {
                "smin"
            } else {
                panic!()
            }
        }
        Intrinsic::Pow => "pow",
        Intrinsic::Powf => "pow",
        Intrinsic::Powi => "powi",
        Intrinsic::Round => "round",
        Intrinsic::Sin => "sin",
        Intrinsic::Sinh => "sinh",
        Intrinsic::Sqrt => "sqrt",
        Intrinsic::Tan => "tan",
        Intrinsic::Tanh => "tanh",
        _ => panic!(),
    };

    // We can't just use our previous routines for emitting types, because only
    // inside intrinsics does LLVM use "f32" and "f64" properly!
    let ty = match ty {
        Type::Boolean => "i1",
        Type::Integer8 | Type::UnsignedInteger8 => "i8",
        Type::Integer16 | Type::UnsignedInteger16 => "i16",
        Type::Integer32 | Type::UnsignedInteger32 => "i32",
        Type::Integer64 | Type::UnsignedInteger64 => "i64",
        Type::Float32 => "f32",
        Type::Float64 => "f64",
        _ => panic!(),
    };

    format!("@llvm.{}.{}", intrinsic, ty)
}