unsafe fn gen_bounds_check(
        &mut self,
        ctx: &mut FunctionContext<'_>,
        parfor: &ParallelForData,
    ) -> WeldResult<LLVMValueRef> {
        let mut pass_blocks = vec![];
        for _ in 0..parfor.data.len() {
            pass_blocks.push(LLVMAppendBasicBlockInContext(
                self.context,
                ctx.llvm_function,
                c_str!("bounds.check"),
            ));
        }
        // Jump here if the iterator will cause an array out of bounds error.
        let fail_boundscheck_block =
            LLVMAppendBasicBlockInContext(self.context, ctx.llvm_function, c_str!("bounds.fail"));
        // Jump here if the zipped vectors produce different numbers of iterations.
        let fail_zip_block =
            LLVMAppendBasicBlockInContext(self.context, ctx.llvm_function, c_str!("bounds.fail"));
        // Jump here if all checks pass.
        let pass_all_block =
            LLVMAppendBasicBlockInContext(self.context, ctx.llvm_function, c_str!("bounds.passed"));

        if self.conf.enable_bounds_checks {
            info!("Generating bounds checking code")
        } else {
            info!("Omitting bounds checking code")
        }

        let mut num_iterations = vec![];
        for (iter, pass_block) in parfor.data.iter().zip(pass_blocks) {
            let iterations =
                self.gen_iter_bounds_check(ctx, iter, pass_block, fail_boundscheck_block)?;
            num_iterations.push(iterations);
            LLVMPositionBuilderAtEnd(ctx.builder, pass_block);
        }

        assert!(!num_iterations.is_empty());

        // Make sure each iterator produces the same number of iterations.
        if num_iterations.len() > 1 {
            self.gen_check_equal(ctx, &num_iterations, pass_all_block, fail_zip_block)?;
        } else {
            let _ = LLVMBuildBr(ctx.builder, pass_all_block);
        }

        LLVMPositionBuilderAtEnd(ctx.builder, fail_boundscheck_block);
        let error = self.i64(WeldRuntimeErrno::BadIteratorLength as i64);
        self.intrinsics
            .call_weld_run_set_errno(ctx.builder, ctx.get_run(), error, None);
        LLVMBuildUnreachable(ctx.builder);

        LLVMPositionBuilderAtEnd(ctx.builder, fail_zip_block);
        let error = self.i64(WeldRuntimeErrno::MismatchedZipSize as i64);
        self.intrinsics
            .call_weld_run_set_errno(ctx.builder, ctx.get_run(), error, None);
        LLVMBuildUnreachable(ctx.builder);

        // Bounds check passed - jump to the final block.
        LLVMPositionBuilderAtEnd(ctx.builder, pass_all_block);
        Ok(num_iterations[0])
    }
Generate runtime bounds checking, which looks as follows:

passed0 = <check bounds of iterator 0>
if passed: goto next, else: goto fail
next:
passed1 = <check bounds of iterator 1>
...
fail:
raise error

The bounds check code positions the `FunctionContext` builder after all bounds checking is
complete.
weld/src/codegen/llvm2/builder/for_loop.rs