unsafe fn gen_loop_body_function(
        &mut self,
        program: &SirProgram,
        func: &SirFunction,
        parfor: &ParallelForData,
    ) -> WeldResult<()> {
        // Construct the return type, which is the builder passed into the function.
        let builders: Vec<Type> = func
            .params
            .values()
            .filter(|v| v.is_builder())
            .cloned()
            .collect();

        // Each loop provides a single builder expression (which could be a struct of builders).
        // The loop's output is by definition derived from this builder.
        assert_eq!(builders.len(), 1);
        let weld_ty = &builders[0];

        let mut arg_tys = self.argument_types(func)?;
        // The second-to-last argument is the *total* number of iterations across all threads (in a
        // multi-threaded setting) that this loop will execute for.
        arg_tys.push(self.i64_type());
        let num_iterations_index = (arg_tys.len() - 1) as u32;
        // Last argument is run handle, as always.
        arg_tys.push(self.run_handle_type());

        let ret_ty = self.llvm_type(weld_ty)?;
        let func_ty = LLVMFunctionType(ret_ty, arg_tys.as_mut_ptr(), arg_tys.len() as u32, 0);
        let name = CString::new(format!("f{}_loop", func.id)).unwrap();
        let function = LLVMAddFunction(self.module, name.as_ptr(), func_ty);
        LLVMSetLinkage(function, LLVMLinkage::LLVMPrivateLinkage);

        LLVMExtAddDefaultAttrs(self.context(), function);
        // We always inline this since it will appear only once in the program, so there's no code
        // size cost to doing it.
        //
        // XXX this should only be here in the single threaded setting.
        LLVMExtAddAttrsOnFunction(self.context, function, &[AlwaysInline]);

        self.functions.insert(func.id, function);

        // Create a context for the function.
        let context = &mut FunctionContext::new(self.context, program, func, function);
        // Reference to the parameter storing the max number of iterations.
        let max = LLVMGetParam(context.llvm_function, num_iterations_index);
        // Create the entry basic block, where we define alloca'd variables.
        let entry_bb =
            LLVMAppendBasicBlockInContext(self.context, context.llvm_function, c_str!(""));
        LLVMPositionBuilderAtEnd(context.builder, entry_bb);

        self.gen_allocas(context)?;
        self.gen_store_parameters(context)?;

        // Store the loop induction variable and the builder argument.
        LLVMBuildStore(
            context.builder,
            self.load(context.builder, context.get_value(&parfor.builder)?)?,
            context.get_value(&parfor.builder_arg)?,
        );
        LLVMBuildStore(
            context.builder,
            self.i64(0),
            context.get_value(&parfor.idx_arg)?,
        );

        // Add the SIR function basic blocks.
        self.gen_basic_block_defs(context)?;

        // Add the loop end basic blocks.
        let loop_end_bb =
            LLVMAppendBasicBlockInContext(self.context, context.llvm_function, c_str!("loop.end"));
        let loop_exit_bb =
            LLVMAppendBasicBlockInContext(self.context, context.llvm_function, c_str!("loop.exit"));

        // Check whether we need to loop at all.
        let any_iters_cond = LLVMBuildICmp(
            context.builder,
            LLVMIntPredicate::LLVMIntNE,
            max,
            self.i64(0),
            c_str!(""),
        );
        // First body block of the SIR function.
        let first_body_block = context.blocks[&0];
        LLVMBuildCondBr(
            context.builder,
            any_iters_cond,
            first_body_block,
            loop_exit_bb,
        );

        // Build the loop body.
        LLVMPositionBuilderAtEnd(context.builder, first_body_block);

        // Load the loop element.
        let i = self.load(context.builder, context.get_value(&parfor.idx_arg)?)?;
        let e = context.get_value(&parfor.data_arg)?;
        self.gen_loop_element(context, i, e, parfor)?;

        // Generate the body - this resembles the usual SIR function generation, but we pass a
        // basic block ID to gen_terminator to change the `EndFunction` terminators to a basic
        // block jump to the end of the loop.
        for bb in func.blocks.iter() {
            LLVMPositionBuilderAtEnd(context.builder, context.get_block(bb.id)?);
            for statement in bb.statements.iter() {
                self.gen_statement(context, statement)?;
            }
            let loop_terminator = (loop_end_bb, context.get_value(&parfor.builder_arg)?);
            self.gen_terminator(context, &bb, Some(loop_terminator))?;
        }

        // The EndFunction terminators in the loop body jump to this block.
        LLVMPositionBuilderAtEnd(context.builder, loop_end_bb);

        // Increment the iteration variable i. We don't need to load it again because this block is
        // only reachable from the loop body, which does not mutate i.
        let updated = LLVMBuildNSWAdd(context.builder, i, self.i64(1), c_str!(""));
        LLVMBuildStore(
            context.builder,
            updated,
            context.get_value(&parfor.idx_arg)?,
        );

        // Check whether to continue looping.
        //
        // NOTE: It's important to use `eq` here! LLVM checks for it in its analyses and won't
        // unroll loops without it.
        let finished_cond = LLVMBuildICmp(
            context.builder,
            LLVMIntPredicate::LLVMIntEQ,
            max,
            updated,
            c_str!(""),
        );
        let _ = LLVMBuildCondBr(
            context.builder,
            finished_cond,
            loop_exit_bb,
            first_body_block,
        );

        // The last basic block loads the updated builder and returns it.
        LLVMPositionBuilderAtEnd(context.builder, loop_exit_bb);
        let updated_builder =
            self.load(context.builder, context.get_value(&parfor.builder_arg)?)?;
        LLVMBuildRet(context.builder, updated_builder);
        Ok(())
    }
Generate a loop body function.

A loop body function has the following layout:

{ builders } FuncName(arg1, arg2, ..., iterations, run):
entry:
alloca all variables except the local builder.
alias builder argument with parfor.builder_arg
br loop.begin
loop.begin:
i = <initializer code>
br loop.entry
loop.entry:
if i >= end:
br loop.exit
else
br loop.body
loop.body:
e = < load elements > based on i
< generate function body>, replace EndFunction with Br loop.check
loop.check:
update i
br loop.entry
loop.exit:
return { builders }
weld/src/codegen/llvm2/builder/for_loop.rs