/*
 * Copyright (c) 2015 Andrew Kelley
 *
 * This file is part of zig, which is MIT licensed.
 * See http://opensource.org/licenses/MIT
 */

#include "analyze.hpp"
#include "ast_render.hpp"
#include "codegen.hpp"
#include "compiler.hpp"
#include "config.h"
#include "errmsg.hpp"
#include "error.hpp"
#include "hash_map.hpp"
#include "ir.hpp"
#include "os.hpp"
#include "target.hpp"
#include "util.hpp"
#include "zig_llvm.h"
#include "userland.h"
#include "dump_analysis.hpp"

#include <stdio.h>
#include <errno.h>

enum ResumeId {
    ResumeIdManual,
    ResumeIdReturn,
    ResumeIdCall,
};

// TODO https://github.com/ziglang/zig/issues/2883
// Until then we have this same default as Clang.
// This avoids https://github.com/ziglang/zig/issues/3275
static const char *riscv_default_features = "+a,+c,+d,+f,+m,+relax";

static void init_darwin_native(CodeGen *g) {
    char *osx_target = getenv("MACOSX_DEPLOYMENT_TARGET");
    char *ios_target = getenv("IPHONEOS_DEPLOYMENT_TARGET");

    // Allow conflicts among OSX and iOS, but choose the default platform.
    if (osx_target && ios_target) {
        if (g->zig_target->arch == ZigLLVM_arm ||
            g->zig_target->arch == ZigLLVM_aarch64 ||
            g->zig_target->arch == ZigLLVM_thumb)
        {
            osx_target = nullptr;
        } else {
            ios_target = nullptr;
        }
    }

    if (osx_target) {
        g->mmacosx_version_min = buf_create_from_str(osx_target);
    } else if (ios_target) {
        g->mios_version_min = buf_create_from_str(ios_target);
    } else if (g->zig_target->os != OsIOS) {
        g->mmacosx_version_min = buf_create_from_str("10.14");
    }
}

static ZigPackage *new_package(const char *root_src_dir, const char *root_src_path, const char *pkg_path) {
    ZigPackage *entry = allocate<ZigPackage>(1);
    entry->package_table.init(4);
    buf_init_from_str(&entry->root_src_dir, root_src_dir);
    buf_init_from_str(&entry->root_src_path, root_src_path);
    buf_init_from_str(&entry->pkg_path, pkg_path);
    return entry;
}

ZigPackage *new_anonymous_package() {
    return new_package("", "", "");
}

static const char *symbols_that_llvm_depends_on[] = {
    "memcpy",
    "memset",
    "sqrt",
    "powi",
    "sin",
    "cos",
    "pow",
    "exp",
    "exp2",
    "log",
    "log10",
    "log2",
    "fma",
    "fabs",
    "minnum",
    "maxnum",
    "copysign",
    "floor",
    "ceil",
    "trunc",
    "rint",
    "nearbyint",
    "round",
    // TODO probably all of compiler-rt needs to go here
};

void codegen_set_clang_argv(CodeGen *g, const char **args, size_t len) {
    g->clang_argv = args;
    g->clang_argv_len = len;
}

void codegen_set_llvm_argv(CodeGen *g, const char **args, size_t len) {
    g->llvm_argv = args;
    g->llvm_argv_len = len;
}

void codegen_set_test_filter(CodeGen *g, Buf *filter) {
    g->test_filter = filter;
}

void codegen_set_test_name_prefix(CodeGen *g, Buf *prefix) {
    g->test_name_prefix = prefix;
}

void codegen_set_lib_version(CodeGen *g, size_t major, size_t minor, size_t patch) {
    g->version_major = major;
    g->version_minor = minor;
    g->version_patch = patch;
}

void codegen_set_emit_file_type(CodeGen *g, EmitFileType emit_file_type) {
    g->emit_file_type = emit_file_type;
}

void codegen_set_each_lib_rpath(CodeGen *g, bool each_lib_rpath) {
    g->each_lib_rpath = each_lib_rpath;
}

void codegen_set_errmsg_color(CodeGen *g, ErrColor err_color) {
    g->err_color = err_color;
}

void codegen_set_strip(CodeGen *g, bool strip) {
    g->strip_debug_symbols = strip;
    if (!target_has_debug_info(g->zig_target)) {
        g->strip_debug_symbols = true;
    }
}

void codegen_set_out_name(CodeGen *g, Buf *out_name) {
    g->root_out_name = out_name;
}

void codegen_add_lib_dir(CodeGen *g, const char *dir) {
    g->lib_dirs.append(dir);
}

void codegen_add_rpath(CodeGen *g, const char *name) {
    g->rpath_list.append(buf_create_from_str(name));
}

LinkLib *codegen_add_link_lib(CodeGen *g, Buf *name) {
    return add_link_lib(g, name);
}

void codegen_add_forbidden_lib(CodeGen *codegen, Buf *lib) {
    codegen->forbidden_libs.append(lib);
}

void codegen_add_framework(CodeGen *g, const char *framework) {
    g->darwin_frameworks.append(buf_create_from_str(framework));
}

void codegen_set_mmacosx_version_min(CodeGen *g, Buf *mmacosx_version_min) {
    g->mmacosx_version_min = mmacosx_version_min;
}

void codegen_set_mios_version_min(CodeGen *g, Buf *mios_version_min) {
    g->mios_version_min = mios_version_min;
}

void codegen_set_rdynamic(CodeGen *g, bool rdynamic) {
    g->linker_rdynamic = rdynamic;
}

void codegen_set_linker_script(CodeGen *g, const char *linker_script) {
    g->linker_script = linker_script;
}


static void render_const_val(CodeGen *g, ZigValue *const_val, const char *name);
static void render_const_val_global(CodeGen *g, ZigValue *const_val, const char *name);
static LLVMValueRef gen_const_val(CodeGen *g, ZigValue *const_val, const char *name);
static void generate_error_name_table(CodeGen *g);
static bool value_is_all_undef(CodeGen *g, ZigValue *const_val);
static void gen_undef_init(CodeGen *g, uint32_t ptr_align_bytes, ZigType *value_type, LLVMValueRef ptr);
static LLVMValueRef build_alloca(CodeGen *g, ZigType *type_entry, const char *name, uint32_t alignment);
static LLVMValueRef gen_await_early_return(CodeGen *g, IrInstruction *source_instr,
        LLVMValueRef target_frame_ptr, ZigType *result_type, ZigType *ptr_result_type,
        LLVMValueRef result_loc, bool non_async);
static Error get_tmp_filename(CodeGen *g, Buf *out, Buf *suffix);

static void addLLVMAttr(LLVMValueRef val, LLVMAttributeIndex attr_index, const char *attr_name) {
    unsigned kind_id = LLVMGetEnumAttributeKindForName(attr_name, strlen(attr_name));
    assert(kind_id != 0);
    LLVMAttributeRef llvm_attr = LLVMCreateEnumAttribute(LLVMGetGlobalContext(), kind_id, 0);
    LLVMAddAttributeAtIndex(val, attr_index, llvm_attr);
}

static void addLLVMAttrStr(LLVMValueRef val, LLVMAttributeIndex attr_index,
        const char *attr_name, const char *attr_val)
{
    LLVMAttributeRef llvm_attr = LLVMCreateStringAttribute(LLVMGetGlobalContext(),
            attr_name, (unsigned)strlen(attr_name), attr_val, (unsigned)strlen(attr_val));
    LLVMAddAttributeAtIndex(val, attr_index, llvm_attr);
}

static void addLLVMAttrInt(LLVMValueRef val, LLVMAttributeIndex attr_index,
        const char *attr_name, uint64_t attr_val)
{
    unsigned kind_id = LLVMGetEnumAttributeKindForName(attr_name, strlen(attr_name));
    assert(kind_id != 0);
    LLVMAttributeRef llvm_attr = LLVMCreateEnumAttribute(LLVMGetGlobalContext(), kind_id, attr_val);
    LLVMAddAttributeAtIndex(val, attr_index, llvm_attr);
}

static void addLLVMFnAttr(LLVMValueRef fn_val, const char *attr_name) {
    return addLLVMAttr(fn_val, -1, attr_name);
}

static void addLLVMFnAttrStr(LLVMValueRef fn_val, const char *attr_name, const char *attr_val) {
    return addLLVMAttrStr(fn_val, -1, attr_name, attr_val);
}

static void addLLVMFnAttrInt(LLVMValueRef fn_val, const char *attr_name, uint64_t attr_val) {
    return addLLVMAttrInt(fn_val, -1, attr_name, attr_val);
}

static void addLLVMArgAttr(LLVMValueRef fn_val, unsigned param_index, const char *attr_name) {
    return addLLVMAttr(fn_val, param_index + 1, attr_name);
}

static void addLLVMArgAttrInt(LLVMValueRef fn_val, unsigned param_index, const char *attr_name, uint64_t attr_val) {
    return addLLVMAttrInt(fn_val, param_index + 1, attr_name, attr_val);
}

static bool is_symbol_available(CodeGen *g, const char *name) {
    Buf *buf_name = buf_create_from_str(name);
    bool result =
        g->exported_symbol_names.maybe_get(buf_name) == nullptr &&
        g->external_prototypes.maybe_get(buf_name) == nullptr;
    buf_destroy(buf_name);
    return result;
}

static const char *get_mangled_name(CodeGen *g, const char *original_name, bool external_linkage) {
    if (external_linkage || is_symbol_available(g, original_name)) {
        return original_name;
    }

    int n = 0;
    for (;; n += 1) {
        const char *new_name = buf_ptr(buf_sprintf("%s.%d", original_name, n));
        if (is_symbol_available(g, new_name)) {
            return new_name;
        }
    }
}

static ZigLLVM_CallingConv get_llvm_cc(CodeGen *g, CallingConvention cc) {
    switch (cc) {
        case CallingConventionUnspecified:
            return ZigLLVM_Fast;
        case CallingConventionC:
            return ZigLLVM_C;
        case CallingConventionCold:
            if ((g->zig_target->arch == ZigLLVM_x86 ||
                 g->zig_target->arch == ZigLLVM_x86_64) &&
                g->zig_target->os != OsWindows)
                return ZigLLVM_Cold;
            return ZigLLVM_C;
        case CallingConventionNaked:
            zig_unreachable();
        case CallingConventionStdcall:
            if (g->zig_target->arch == ZigLLVM_x86)
                return ZigLLVM_X86_StdCall;
            return ZigLLVM_C;
        case CallingConventionFastcall:
            if (g->zig_target->arch == ZigLLVM_x86)
                return ZigLLVM_X86_FastCall;
            return ZigLLVM_C;
        case CallingConventionVectorcall:
            if (g->zig_target->arch == ZigLLVM_x86)
                return ZigLLVM_X86_VectorCall;
            if (target_is_arm(g->zig_target) &&
                target_arch_pointer_bit_width(g->zig_target->arch) == 64)
                return ZigLLVM_AArch64_VectorCall;
            return ZigLLVM_C;
        case CallingConventionThiscall:
            if (g->zig_target->arch == ZigLLVM_x86)
                return ZigLLVM_X86_ThisCall;
            return ZigLLVM_C;
        case CallingConventionAsync:
            return ZigLLVM_Fast;
        case CallingConventionAPCS:
            if (target_is_arm(g->zig_target))
                return ZigLLVM_ARM_APCS;
            return ZigLLVM_C;
        case CallingConventionAAPCS:
            if (target_is_arm(g->zig_target))
                return ZigLLVM_ARM_AAPCS;
            return ZigLLVM_C;
        case CallingConventionAAPCSVFP:
            if (target_is_arm(g->zig_target))
                return ZigLLVM_ARM_AAPCS_VFP;
            return ZigLLVM_C;
        case CallingConventionInterrupt:
            if (g->zig_target->arch == ZigLLVM_x86 ||
                g->zig_target->arch == ZigLLVM_x86_64)
                return ZigLLVM_X86_INTR;
            if (g->zig_target->arch == ZigLLVM_avr)
                return ZigLLVM_AVR_INTR;
            if (g->zig_target->arch == ZigLLVM_msp430)
                return ZigLLVM_MSP430_INTR;
            return ZigLLVM_C;
        case CallingConventionSignal:
            if (g->zig_target->arch == ZigLLVM_avr)
                return ZigLLVM_AVR_SIGNAL;
            return ZigLLVM_C;
    }
    zig_unreachable();
}

static void add_uwtable_attr(CodeGen *g, LLVMValueRef fn_val) {
    if (g->zig_target->os == OsWindows) {
        addLLVMFnAttr(fn_val, "uwtable");
    }
}

static LLVMLinkage to_llvm_linkage(GlobalLinkageId id) {
    switch (id) {
        case GlobalLinkageIdInternal:
            return LLVMInternalLinkage;
        case GlobalLinkageIdStrong:
            return LLVMExternalLinkage;
        case GlobalLinkageIdWeak:
            return LLVMWeakODRLinkage;
        case GlobalLinkageIdLinkOnce:
            return LLVMLinkOnceODRLinkage;
    }
    zig_unreachable();
}

// label (grep this): [fn_frame_struct_layout]
static uint32_t frame_index_trace_arg(CodeGen *g, ZigType *return_type) {
    // [0] *ReturnType (callee's)
    // [1] *ReturnType (awaiter's)
    // [2] ReturnType
    uint32_t return_field_count = type_has_bits(return_type) ? 3 : 0;
    return frame_ret_start + return_field_count;
}

// label (grep this): [fn_frame_struct_layout]
static uint32_t frame_index_arg(CodeGen *g, ZigType *return_type) {
    bool have_stack_trace = codegen_fn_has_err_ret_tracing_arg(g, return_type);
    // [0] *StackTrace (callee's)
    // [1] *StackTrace (awaiter's)
    uint32_t trace_field_count = have_stack_trace ? 2 : 0;
    return frame_index_trace_arg(g, return_type) + trace_field_count;
}

// label (grep this): [fn_frame_struct_layout]
static uint32_t frame_index_trace_stack(CodeGen *g, FnTypeId *fn_type_id) {
    uint32_t result = frame_index_arg(g, fn_type_id->return_type);
    for (size_t i = 0; i < fn_type_id->param_count; i += 1) {
        if (type_has_bits(fn_type_id->param_info->type)) {
            result += 1;
        }
    }
    return result;
}


static uint32_t get_err_ret_trace_arg_index(CodeGen *g, ZigFn *fn_table_entry) {
    if (!g->have_err_ret_tracing) {
        return UINT32_MAX;
    }
    if (fn_is_async(fn_table_entry)) {
        return UINT32_MAX;
    }
    ZigType *fn_type = fn_table_entry->type_entry;
    if (!fn_type_can_fail(&fn_type->data.fn.fn_type_id)) {
        return UINT32_MAX;
    }
    ZigType *return_type = fn_type->data.fn.fn_type_id.return_type;
    bool first_arg_ret = type_has_bits(return_type) && handle_is_ptr(return_type);
    return first_arg_ret ? 1 : 0;
}

static void maybe_export_dll(CodeGen *g, LLVMValueRef global_value, GlobalLinkageId linkage) {
    if (linkage != GlobalLinkageIdInternal && g->zig_target->os == OsWindows && g->is_dynamic) {
        LLVMSetDLLStorageClass(global_value, LLVMDLLExportStorageClass);
    }
}

static void maybe_import_dll(CodeGen *g, LLVMValueRef global_value, GlobalLinkageId linkage) {
    if (linkage != GlobalLinkageIdInternal && g->zig_target->os == OsWindows) {
        // TODO come up with a good explanation/understanding for why we never do
        // DLLImportStorageClass. Empirically it only causes problems. But let's have
        // this documented and then clean up the code accordingly.
        //LLVMSetDLLStorageClass(global_value, LLVMDLLImportStorageClass);
    }
}

static bool cc_want_sret_attr(CallingConvention cc) {
    switch (cc) {
        case CallingConventionNaked:
            zig_unreachable();
        case CallingConventionC:
        case CallingConventionCold:
        case CallingConventionInterrupt:
        case CallingConventionSignal:
        case CallingConventionStdcall:
        case CallingConventionFastcall:
        case CallingConventionVectorcall:
        case CallingConventionThiscall:
        case CallingConventionAPCS:
        case CallingConventionAAPCS:
        case CallingConventionAAPCSVFP:
            return true;
        case CallingConventionAsync:
        case CallingConventionUnspecified:
            return false;
    }
    zig_unreachable();
}

static bool codegen_have_frame_pointer(CodeGen *g) {
    return g->build_mode == BuildModeDebug;
}

static LLVMValueRef make_fn_llvm_value(CodeGen *g, ZigFn *fn) {
    const char *unmangled_name = buf_ptr(&fn->symbol_name);
    const char *symbol_name;
    GlobalLinkageId linkage;
    if (fn->body_node == nullptr) {
        symbol_name = unmangled_name;
        linkage = GlobalLinkageIdStrong;
    } else if (fn->export_list.length == 0) {
        symbol_name = get_mangled_name(g, unmangled_name, false);
        linkage = GlobalLinkageIdInternal;
    } else {
        GlobalExport *fn_export = &fn->export_list.items[0];
        symbol_name = buf_ptr(&fn_export->name);
        linkage = fn_export->linkage;
    }

    CallingConvention cc = fn->type_entry->data.fn.fn_type_id.cc;
    bool is_async = fn_is_async(fn);

    ZigType *fn_type = fn->type_entry;
    // Make the raw_type_ref populated
    resolve_llvm_types_fn(g, fn);
    LLVMTypeRef fn_llvm_type = fn->raw_type_ref;
    LLVMValueRef llvm_fn = nullptr;
    if (fn->body_node == nullptr) {
        const unsigned fn_addrspace = ZigLLVMDataLayoutGetProgramAddressSpace(g->target_data_ref);
        LLVMValueRef existing_llvm_fn = LLVMGetNamedFunction(g->module, symbol_name);
        if (existing_llvm_fn) {
            return LLVMConstBitCast(existing_llvm_fn, LLVMPointerType(fn_llvm_type, fn_addrspace));
        } else {
            Buf *buf_symbol_name = buf_create_from_str(symbol_name);
            auto entry = g->exported_symbol_names.maybe_get(buf_symbol_name);
            buf_destroy(buf_symbol_name);

            if (entry == nullptr) {
                llvm_fn = LLVMAddFunction(g->module, symbol_name, fn_llvm_type);

                if (target_is_wasm(g->zig_target)) {
                    assert(fn->proto_node->type == NodeTypeFnProto);
                    AstNodeFnProto *fn_proto = &fn->proto_node->data.fn_proto;
                    if (fn_proto-> is_extern && fn_proto->lib_name != nullptr ) {
                        addLLVMFnAttrStr(llvm_fn, "wasm-import-module", buf_ptr(fn_proto->lib_name));
                    }
                }
            } else {
                assert(entry->value->id == TldIdFn);
                TldFn *tld_fn = reinterpret_cast<TldFn *>(entry->value);
                // Make the raw_type_ref populated
                resolve_llvm_types_fn(g, tld_fn->fn_entry);
                tld_fn->fn_entry->llvm_value = LLVMAddFunction(g->module, symbol_name,
                        tld_fn->fn_entry->raw_type_ref);
                llvm_fn = LLVMConstBitCast(tld_fn->fn_entry->llvm_value, LLVMPointerType(fn_llvm_type, fn_addrspace));
                return llvm_fn;
            }
        }
    } else {
        if (llvm_fn == nullptr) {
            llvm_fn = LLVMAddFunction(g->module, symbol_name, fn_llvm_type);
        }

        for (size_t i = 1; i < fn->export_list.length; i += 1) {
            GlobalExport *fn_export = &fn->export_list.items[i];
            LLVMAddAlias(g->module, LLVMTypeOf(llvm_fn), llvm_fn, buf_ptr(&fn_export->name));
        }
    }

    switch (fn->fn_inline) {
        case FnInlineAlways:
            addLLVMFnAttr(llvm_fn, "alwaysinline");
            g->inline_fns.append(fn);
            break;
        case FnInlineNever:
            addLLVMFnAttr(llvm_fn, "noinline");
            break;
        case FnInlineAuto:
            if (fn->alignstack_value != 0) {
                addLLVMFnAttr(llvm_fn, "noinline");
            }
            break;
    }

    if (cc == CallingConventionNaked) {
        addLLVMFnAttr(llvm_fn, "naked");
    } else {
        ZigLLVMFunctionSetCallingConv(llvm_fn, get_llvm_cc(g, cc));
    }

    bool want_cold = fn->is_cold || cc == CallingConventionCold;
    if (want_cold) {
        ZigLLVMAddFunctionAttrCold(llvm_fn);
    }


    LLVMSetLinkage(llvm_fn, to_llvm_linkage(linkage));

    if (linkage == GlobalLinkageIdInternal) {
        LLVMSetUnnamedAddr(llvm_fn, true);
    }

    ZigType *return_type = fn_type->data.fn.fn_type_id.return_type;
    if (return_type->id == ZigTypeIdUnreachable) {
        addLLVMFnAttr(llvm_fn, "noreturn");
    }

    if (fn->body_node != nullptr) {
        maybe_export_dll(g, llvm_fn, linkage);

        bool want_fn_safety = g->build_mode != BuildModeFastRelease &&
                              g->build_mode != BuildModeSmallRelease &&
                              !fn->def_scope->safety_off;
        if (want_fn_safety) {
            if (g->libc_link_lib != nullptr) {
                addLLVMFnAttr(llvm_fn, "sspstrong");
                addLLVMFnAttrStr(llvm_fn, "stack-protector-buffer-size", "4");
            }
        }
        if (g->have_stack_probing && !fn->def_scope->safety_off) {
            addLLVMFnAttrStr(llvm_fn, "probe-stack", "__zig_probe_stack");
        } else if (g->zig_target->os == OsUefi) {
            addLLVMFnAttrStr(llvm_fn, "no-stack-arg-probe", "");
        }
    } else {
        maybe_import_dll(g, llvm_fn, linkage);
    }

    if (fn->alignstack_value != 0) {
        addLLVMFnAttrInt(llvm_fn, "alignstack", fn->alignstack_value);
    }

    addLLVMFnAttr(llvm_fn, "nounwind");
    add_uwtable_attr(g, llvm_fn);
    addLLVMFnAttr(llvm_fn, "nobuiltin");
    if (codegen_have_frame_pointer(g) && fn->fn_inline != FnInlineAlways) {
        ZigLLVMAddFunctionAttr(llvm_fn, "no-frame-pointer-elim", "true");
        ZigLLVMAddFunctionAttr(llvm_fn, "no-frame-pointer-elim-non-leaf", nullptr);
    }
    if (fn->section_name) {
        LLVMSetSection(llvm_fn, buf_ptr(fn->section_name));
    }
    if (fn->align_bytes > 0) {
        LLVMSetAlignment(llvm_fn, (unsigned)fn->align_bytes);
    } else {
        // We'd like to set the best alignment for the function here, but on Darwin LLVM gives
        // "Cannot getTypeInfo() on a type that is unsized!" assertion failure when calling
        // any of the functions for getting alignment. Not specifying the alignment should
        // use the ABI alignment, which is fine.
    }

    if (is_async) {
        addLLVMArgAttr(llvm_fn, 0, "nonnull");
    } else {
        unsigned init_gen_i = 0;
        if (!type_has_bits(return_type)) {
            // nothing to do
        } else if (type_is_nonnull_ptr(return_type)) {
            addLLVMAttr(llvm_fn, 0, "nonnull");
        } else if (want_first_arg_sret(g, &fn_type->data.fn.fn_type_id)) {
            // Sret pointers must not be address 0
            addLLVMArgAttr(llvm_fn, 0, "nonnull");
            addLLVMArgAttr(llvm_fn, 0, "sret");
            if (cc_want_sret_attr(cc)) {
                addLLVMArgAttr(llvm_fn, 0, "noalias");
            }
            init_gen_i = 1;
        }

        // set parameter attributes
        FnWalk fn_walk = {};
        fn_walk.id = FnWalkIdAttrs;
        fn_walk.data.attrs.fn = fn;
        fn_walk.data.attrs.llvm_fn = llvm_fn;
        fn_walk.data.attrs.gen_i = init_gen_i;
        walk_function_params(g, fn_type, &fn_walk);

        uint32_t err_ret_trace_arg_index = get_err_ret_trace_arg_index(g, fn);
        if (err_ret_trace_arg_index != UINT32_MAX) {
            // Error return trace memory is in the stack, which is impossible to be at address 0
            // on any architecture.
            addLLVMArgAttr(llvm_fn, (unsigned)err_ret_trace_arg_index, "nonnull");
        }
    }

    return llvm_fn;
}

static LLVMValueRef fn_llvm_value(CodeGen *g, ZigFn *fn) {
    if (fn->llvm_value)
        return fn->llvm_value;

    fn->llvm_value = make_fn_llvm_value(g, fn);
    fn->llvm_name = strdup(LLVMGetValueName(fn->llvm_value));
    return fn->llvm_value;
}

static ZigLLVMDIScope *get_di_scope(CodeGen *g, Scope *scope) {
    if (scope->di_scope)
        return scope->di_scope;

    ZigType *import = get_scope_import(scope);
    switch (scope->id) {
        case ScopeIdCImport:
            zig_unreachable();
        case ScopeIdFnDef:
        {
            assert(scope->parent);
            ScopeFnDef *fn_scope = (ScopeFnDef *)scope;
            ZigFn *fn_table_entry = fn_scope->fn_entry;
            if (!fn_table_entry->proto_node)
                return get_di_scope(g, scope->parent);
            unsigned line_number = (unsigned)(fn_table_entry->proto_node->line == 0) ?
                0 : (fn_table_entry->proto_node->line + 1);
            unsigned scope_line = line_number;
            bool is_definition = fn_table_entry->body_node != nullptr;
            bool is_optimized = g->build_mode != BuildModeDebug;
            bool is_internal_linkage = (fn_table_entry->body_node != nullptr &&
                    fn_table_entry->export_list.length == 0);
            unsigned flags = ZigLLVM_DIFlags_StaticMember;
            ZigLLVMDIScope *fn_di_scope = get_di_scope(g, scope->parent);
            assert(fn_di_scope != nullptr);
            assert(fn_table_entry->raw_di_type != nullptr);
            ZigLLVMDISubprogram *subprogram = ZigLLVMCreateFunction(g->dbuilder,
                fn_di_scope, buf_ptr(&fn_table_entry->symbol_name), "",
                import->data.structure.root_struct->di_file, line_number,
                fn_table_entry->raw_di_type, is_internal_linkage,
                is_definition, scope_line, flags, is_optimized, nullptr);

            scope->di_scope = ZigLLVMSubprogramToScope(subprogram);
            if (!g->strip_debug_symbols) {
                ZigLLVMFnSetSubprogram(fn_llvm_value(g, fn_table_entry), subprogram);
            }
            return scope->di_scope;
        }
        case ScopeIdDecls:
            if (scope->parent) {
                ScopeDecls *decls_scope = (ScopeDecls *)scope;
                assert(decls_scope->container_type);
                scope->di_scope = ZigLLVMTypeToScope(get_llvm_di_type(g, decls_scope->container_type));
            } else {
                scope->di_scope = ZigLLVMFileToScope(import->data.structure.root_struct->di_file);
            }
            return scope->di_scope;
        case ScopeIdBlock:
        case ScopeIdDefer:
        {
            assert(scope->parent);
            ZigLLVMDILexicalBlock *di_block = ZigLLVMCreateLexicalBlock(g->dbuilder,
                get_di_scope(g, scope->parent),
                import->data.structure.root_struct->di_file,
                (unsigned)scope->source_node->line + 1,
                (unsigned)scope->source_node->column + 1);
            scope->di_scope = ZigLLVMLexicalBlockToScope(di_block);
            return scope->di_scope;
        }
        case ScopeIdVarDecl:
        case ScopeIdDeferExpr:
        case ScopeIdLoop:
        case ScopeIdSuspend:
        case ScopeIdCompTime:
        case ScopeIdRuntime:
        case ScopeIdTypeOf:
        case ScopeIdExpr:
            return get_di_scope(g, scope->parent);
    }
    zig_unreachable();
}

static void clear_debug_source_node(CodeGen *g) {
    ZigLLVMClearCurrentDebugLocation(g->builder);
}

static LLVMValueRef get_arithmetic_overflow_fn(CodeGen *g, ZigType *operand_type,
        const char *signed_name, const char *unsigned_name)
{
    ZigType *int_type = (operand_type->id == ZigTypeIdVector) ? operand_type->data.vector.elem_type : operand_type;
    char fn_name[64];

    assert(int_type->id == ZigTypeIdInt);
    const char *signed_str = int_type->data.integral.is_signed ? signed_name : unsigned_name;

    LLVMTypeRef param_types[] = {
        get_llvm_type(g, operand_type),
        get_llvm_type(g, operand_type),
    };

    if (operand_type->id == ZigTypeIdVector) {
        sprintf(fn_name, "llvm.%s.with.overflow.v%" PRIu32 "i%" PRIu32, signed_str,
                operand_type->data.vector.len, int_type->data.integral.bit_count);

        LLVMTypeRef return_elem_types[] = {
            get_llvm_type(g, operand_type),
            LLVMVectorType(LLVMInt1Type(), operand_type->data.vector.len),
        };
        LLVMTypeRef return_struct_type = LLVMStructType(return_elem_types, 2, false);
        LLVMTypeRef fn_type = LLVMFunctionType(return_struct_type, param_types, 2, false);
        LLVMValueRef fn_val = LLVMAddFunction(g->module, fn_name, fn_type);
        assert(LLVMGetIntrinsicID(fn_val));
        return fn_val;
    } else {
        sprintf(fn_name, "llvm.%s.with.overflow.i%" PRIu32, signed_str, int_type->data.integral.bit_count);

        LLVMTypeRef return_elem_types[] = {
            get_llvm_type(g, operand_type),
            LLVMInt1Type(),
        };
        LLVMTypeRef return_struct_type = LLVMStructType(return_elem_types, 2, false);
        LLVMTypeRef fn_type = LLVMFunctionType(return_struct_type, param_types, 2, false);
        LLVMValueRef fn_val = LLVMAddFunction(g->module, fn_name, fn_type);
        assert(LLVMGetIntrinsicID(fn_val));
        return fn_val;
    }
}

static LLVMValueRef get_int_overflow_fn(CodeGen *g, ZigType *operand_type, AddSubMul add_sub_mul) {
    ZigType *int_type = (operand_type->id == ZigTypeIdVector) ? operand_type->data.vector.elem_type : operand_type;
    assert(int_type->id == ZigTypeIdInt);

    ZigLLVMFnKey key = {};
    key.id = ZigLLVMFnIdOverflowArithmetic;
    key.data.overflow_arithmetic.is_signed = int_type->data.integral.is_signed;
    key.data.overflow_arithmetic.add_sub_mul = add_sub_mul;
    key.data.overflow_arithmetic.bit_count = (uint32_t)int_type->data.integral.bit_count;
    key.data.overflow_arithmetic.vector_len = (operand_type->id == ZigTypeIdVector) ?
        operand_type->data.vector.len : 0;

    auto existing_entry = g->llvm_fn_table.maybe_get(key);
    if (existing_entry)
        return existing_entry->value;

    LLVMValueRef fn_val;
    switch (add_sub_mul) {
        case AddSubMulAdd:
            fn_val = get_arithmetic_overflow_fn(g, operand_type, "sadd", "uadd");
            break;
        case AddSubMulSub:
            fn_val = get_arithmetic_overflow_fn(g, operand_type, "ssub", "usub");
            break;
        case AddSubMulMul:
            fn_val = get_arithmetic_overflow_fn(g, operand_type, "smul", "umul");
            break;
    }

    g->llvm_fn_table.put(key, fn_val);
    return fn_val;
}

static LLVMValueRef get_float_fn(CodeGen *g, ZigType *type_entry, ZigLLVMFnId fn_id, BuiltinFnId op) {
    assert(type_entry->id == ZigTypeIdFloat ||
           type_entry->id == ZigTypeIdVector);

    bool is_vector = (type_entry->id == ZigTypeIdVector);
    ZigType *float_type = is_vector ? type_entry->data.vector.elem_type : type_entry;

    ZigLLVMFnKey key = {};
    key.id = fn_id;
    key.data.floating.bit_count = (uint32_t)float_type->data.floating.bit_count;
    key.data.floating.vector_len = is_vector ? (uint32_t)type_entry->data.vector.len : 0;
    key.data.floating.op = op;

    auto existing_entry = g->llvm_fn_table.maybe_get(key);
    if (existing_entry)
        return existing_entry->value;

    const char *name;
    uint32_t num_args;
    if (fn_id == ZigLLVMFnIdFMA) {
        name = "fma";
        num_args = 3;
    } else if (fn_id == ZigLLVMFnIdFloatOp) {
        name = float_op_to_name(op);
        num_args = 1;
    } else {
        zig_unreachable();
    }

    char fn_name[64];
    if (is_vector)
        sprintf(fn_name, "llvm.%s.v%" PRIu32 "f%" PRIu32, name, key.data.floating.vector_len, key.data.floating.bit_count);
    else
        sprintf(fn_name, "llvm.%s.f%" PRIu32, name, key.data.floating.bit_count);
    LLVMTypeRef float_type_ref = get_llvm_type(g, type_entry);
    LLVMTypeRef return_elem_types[3] = {
        float_type_ref,
        float_type_ref,
        float_type_ref,
    };
    LLVMTypeRef fn_type = LLVMFunctionType(float_type_ref, return_elem_types, num_args, false);
    LLVMValueRef fn_val = LLVMAddFunction(g->module, fn_name, fn_type);
    assert(LLVMGetIntrinsicID(fn_val));

    g->llvm_fn_table.put(key, fn_val);
    return fn_val;
}

static LLVMValueRef gen_store_untyped(CodeGen *g, LLVMValueRef value, LLVMValueRef ptr,
        uint32_t alignment, bool is_volatile)
{
    LLVMValueRef instruction = LLVMBuildStore(g->builder, value, ptr);
    if (is_volatile) LLVMSetVolatile(instruction, true);
    if (alignment != 0) {
        LLVMSetAlignment(instruction, alignment);
    }
    return instruction;
}

static LLVMValueRef gen_store(CodeGen *g, LLVMValueRef value, LLVMValueRef ptr, ZigType *ptr_type) {
    assert(ptr_type->id == ZigTypeIdPointer);
    uint32_t alignment = get_ptr_align(g, ptr_type);
    return gen_store_untyped(g, value, ptr, alignment, ptr_type->data.pointer.is_volatile);
}

static LLVMValueRef gen_load_untyped(CodeGen *g, LLVMValueRef ptr, uint32_t alignment, bool is_volatile,
        const char *name)
{
    LLVMValueRef result = LLVMBuildLoad(g->builder, ptr, name);
    if (is_volatile) LLVMSetVolatile(result, true);
    if (alignment == 0) {
        LLVMSetAlignment(result, LLVMABIAlignmentOfType(g->target_data_ref, LLVMGetElementType(LLVMTypeOf(ptr))));
    } else {
        LLVMSetAlignment(result, alignment);
    }
    return result;
}

static LLVMValueRef gen_load(CodeGen *g, LLVMValueRef ptr, ZigType *ptr_type, const char *name) {
    assert(ptr_type->id == ZigTypeIdPointer);
    uint32_t alignment = get_ptr_align(g, ptr_type);
    return gen_load_untyped(g, ptr, alignment, ptr_type->data.pointer.is_volatile, name);
}

static LLVMValueRef get_handle_value(CodeGen *g, LLVMValueRef ptr, ZigType *type, ZigType *ptr_type) {
    if (type_has_bits(type)) {
        if (handle_is_ptr(type)) {
            return ptr;
        } else {
            assert(ptr_type->id == ZigTypeIdPointer);
            return gen_load(g, ptr, ptr_type, "");
        }
    } else {
        return nullptr;
    }
}

static void ir_assert(bool ok, IrInstruction *source_instruction) {
    if (ok) return;
    src_assert(ok, source_instruction->source_node);
}

static bool ir_want_fast_math(CodeGen *g, IrInstruction *instruction) {
    // TODO memoize
    Scope *scope = instruction->scope;
    while (scope) {
        if (scope->id == ScopeIdBlock) {
            ScopeBlock *block_scope = (ScopeBlock *)scope;
            if (block_scope->fast_math_set_node)
                return block_scope->fast_math_on;
        } else if (scope->id == ScopeIdDecls) {
            ScopeDecls *decls_scope = (ScopeDecls *)scope;
            if (decls_scope->fast_math_set_node)
                return decls_scope->fast_math_on;
        }
        scope = scope->parent;
    }
    return false;
}

static bool ir_want_runtime_safety_scope(CodeGen *g, Scope *scope) {
    // TODO memoize
    while (scope) {
        if (scope->id == ScopeIdBlock) {
            ScopeBlock *block_scope = (ScopeBlock *)scope;
            if (block_scope->safety_set_node)
                return !block_scope->safety_off;
        } else if (scope->id == ScopeIdDecls) {
            ScopeDecls *decls_scope = (ScopeDecls *)scope;
            if (decls_scope->safety_set_node)
                return !decls_scope->safety_off;
        }
        scope = scope->parent;
    }

    return (g->build_mode != BuildModeFastRelease &&
            g->build_mode != BuildModeSmallRelease);
}

static bool ir_want_runtime_safety(CodeGen *g, IrInstruction *instruction) {
    return ir_want_runtime_safety_scope(g, instruction->scope);
}

static Buf *panic_msg_buf(PanicMsgId msg_id) {
    switch (msg_id) {
        case PanicMsgIdCount:
            zig_unreachable();
        case PanicMsgIdBoundsCheckFailure:
            return buf_create_from_str("index out of bounds");
        case PanicMsgIdCastNegativeToUnsigned:
            return buf_create_from_str("attempt to cast negative value to unsigned integer");
        case PanicMsgIdCastTruncatedData:
            return buf_create_from_str("integer cast truncated bits");
        case PanicMsgIdIntegerOverflow:
            return buf_create_from_str("integer overflow");
        case PanicMsgIdShlOverflowedBits:
            return buf_create_from_str("left shift overflowed bits");
        case PanicMsgIdShrOverflowedBits:
            return buf_create_from_str("right shift overflowed bits");
        case PanicMsgIdDivisionByZero:
            return buf_create_from_str("division by zero");
        case PanicMsgIdRemainderDivisionByZero:
            return buf_create_from_str("remainder division by zero or negative value");
        case PanicMsgIdExactDivisionRemainder:
            return buf_create_from_str("exact division produced remainder");
        case PanicMsgIdSliceWidenRemainder:
            return buf_create_from_str("slice widening size mismatch");
        case PanicMsgIdUnwrapOptionalFail:
            return buf_create_from_str("attempt to unwrap null");
        case PanicMsgIdUnreachable:
            return buf_create_from_str("reached unreachable code");
        case PanicMsgIdInvalidErrorCode:
            return buf_create_from_str("invalid error code");
        case PanicMsgIdIncorrectAlignment:
            return buf_create_from_str("incorrect alignment");
        case PanicMsgIdBadUnionField:
            return buf_create_from_str("access of inactive union field");
        case PanicMsgIdBadEnumValue:
            return buf_create_from_str("invalid enum value");
        case PanicMsgIdFloatToInt:
            return buf_create_from_str("integer part of floating point value out of bounds");
        case PanicMsgIdPtrCastNull:
            return buf_create_from_str("cast causes pointer to be null");
        case PanicMsgIdBadResume:
            return buf_create_from_str("resumed an async function which already returned");
        case PanicMsgIdBadAwait:
            return buf_create_from_str("async function awaited twice");
        case PanicMsgIdBadReturn:
            return buf_create_from_str("async function returned twice");
        case PanicMsgIdResumedAnAwaitingFn:
            return buf_create_from_str("awaiting function resumed");
        case PanicMsgIdFrameTooSmall:
            return buf_create_from_str("frame too small");
        case PanicMsgIdResumedFnPendingAwait:
            return buf_create_from_str("resumed an async function which can only be awaited");
        case PanicMsgIdBadNoAsyncCall:
            return buf_create_from_str("async function called with noasync suspended");
        case PanicMsgIdResumeNotSuspendedFn:
            return buf_create_from_str("resumed a non-suspended function");
        case PanicMsgIdBadSentinel:
            return buf_create_from_str("sentinel mismatch");
    }
    zig_unreachable();
}

static LLVMValueRef get_panic_msg_ptr_val(CodeGen *g, PanicMsgId msg_id) {
    ZigValue *val = &g->panic_msg_vals[msg_id];
    if (!val->llvm_global) {

        Buf *buf_msg = panic_msg_buf(msg_id);
        ZigValue *array_val = create_const_str_lit(g, buf_msg)->data.x_ptr.data.ref.pointee;
        init_const_slice(g, val, array_val, 0, buf_len(buf_msg), true);

        render_const_val(g, val, "");
        render_const_val_global(g, val, "");

        assert(val->llvm_global);
    }

    ZigType *u8_ptr_type = get_pointer_to_type_extra(g, g->builtin_types.entry_u8, true, false,
            PtrLenUnknown, get_abi_alignment(g, g->builtin_types.entry_u8), 0, 0, false);
    ZigType *str_type = get_slice_type(g, u8_ptr_type);
    return LLVMConstBitCast(val->llvm_global, LLVMPointerType(get_llvm_type(g, str_type), 0));
}

static ZigType *ptr_to_stack_trace_type(CodeGen *g) {
    return get_pointer_to_type(g, get_stack_trace_type(g), false);
}

static void gen_panic(CodeGen *g, LLVMValueRef msg_arg, LLVMValueRef stack_trace_arg,
        bool stack_trace_is_llvm_alloca)
{
    assert(g->panic_fn != nullptr);
    LLVMValueRef fn_val = fn_llvm_value(g, g->panic_fn);
    ZigLLVM_CallingConv llvm_cc = get_llvm_cc(g, g->panic_fn->type_entry->data.fn.fn_type_id.cc);
    if (stack_trace_arg == nullptr) {
        stack_trace_arg = LLVMConstNull(get_llvm_type(g, ptr_to_stack_trace_type(g)));
    }
    LLVMValueRef args[] = {
        msg_arg,
        stack_trace_arg,
    };
    ZigLLVMBuildCall(g->builder, fn_val, args, 2, llvm_cc, ZigLLVM_CallAttrAuto, "");
    if (!stack_trace_is_llvm_alloca) {
        // The stack trace argument is not in the stack of the caller, so
        // we'd like to set tail call here, but because slices (the type of msg_arg) are
        // still passed as pointers (see https://github.com/ziglang/zig/issues/561) we still
        // cannot make this a tail call.
        //LLVMSetTailCall(call_instruction, true);
    }
    LLVMBuildUnreachable(g->builder);
}

// TODO update most callsites to call gen_assertion instead of this
static void gen_safety_crash(CodeGen *g, PanicMsgId msg_id) {
    gen_panic(g, get_panic_msg_ptr_val(g, msg_id), nullptr, false);
}

static void gen_assertion_scope(CodeGen *g, PanicMsgId msg_id, Scope *source_scope) {
    if (ir_want_runtime_safety_scope(g, source_scope)) {
        gen_safety_crash(g, msg_id);
    } else {
        LLVMBuildUnreachable(g->builder);
    }
}

static void gen_assertion(CodeGen *g, PanicMsgId msg_id, IrInstruction *source_instruction) {
    return gen_assertion_scope(g, msg_id, source_instruction->scope);
}

static LLVMValueRef get_stacksave_fn_val(CodeGen *g) {
    if (g->stacksave_fn_val)
        return g->stacksave_fn_val;

    // declare i8* @llvm.stacksave()

    LLVMTypeRef fn_type = LLVMFunctionType(LLVMPointerType(LLVMInt8Type(), 0), nullptr, 0, false);
    g->stacksave_fn_val = LLVMAddFunction(g->module, "llvm.stacksave", fn_type);
    assert(LLVMGetIntrinsicID(g->stacksave_fn_val));

    return g->stacksave_fn_val;
}

static LLVMValueRef get_stackrestore_fn_val(CodeGen *g) {
    if (g->stackrestore_fn_val)
        return g->stackrestore_fn_val;

    // declare void @llvm.stackrestore(i8* %ptr)

    LLVMTypeRef param_type = LLVMPointerType(LLVMInt8Type(), 0);
    LLVMTypeRef fn_type = LLVMFunctionType(LLVMVoidType(), &param_type, 1, false);
    g->stackrestore_fn_val = LLVMAddFunction(g->module, "llvm.stackrestore", fn_type);
    assert(LLVMGetIntrinsicID(g->stackrestore_fn_val));

    return g->stackrestore_fn_val;
}

static LLVMValueRef get_write_register_fn_val(CodeGen *g) {
    if (g->write_register_fn_val)
        return g->write_register_fn_val;

    // declare void @llvm.write_register.i64(metadata, i64 @value)
    // !0 = !{!"sp\00"}

    LLVMTypeRef param_types[] = {
        LLVMMetadataTypeInContext(LLVMGetGlobalContext()),
        LLVMIntType(g->pointer_size_bytes * 8),
    };

    LLVMTypeRef fn_type = LLVMFunctionType(LLVMVoidType(), param_types, 2, false);
    Buf *name = buf_sprintf("llvm.write_register.i%d", g->pointer_size_bytes * 8);
    g->write_register_fn_val = LLVMAddFunction(g->module, buf_ptr(name), fn_type);
    assert(LLVMGetIntrinsicID(g->write_register_fn_val));

    return g->write_register_fn_val;
}

static LLVMValueRef get_return_address_fn_val(CodeGen *g) {
    if (g->return_address_fn_val)
        return g->return_address_fn_val;

    ZigType *return_type = get_pointer_to_type(g, g->builtin_types.entry_u8, true);

    LLVMTypeRef fn_type = LLVMFunctionType(get_llvm_type(g, return_type),
            &g->builtin_types.entry_i32->llvm_type, 1, false);
    g->return_address_fn_val = LLVMAddFunction(g->module, "llvm.returnaddress", fn_type);
    assert(LLVMGetIntrinsicID(g->return_address_fn_val));

    return g->return_address_fn_val;
}

static LLVMValueRef get_add_error_return_trace_addr_fn(CodeGen *g) {
    if (g->add_error_return_trace_addr_fn_val != nullptr)
        return g->add_error_return_trace_addr_fn_val;

    LLVMTypeRef arg_types[] = {
        get_llvm_type(g, ptr_to_stack_trace_type(g)),
        g->builtin_types.entry_usize->llvm_type,
    };
    LLVMTypeRef fn_type_ref = LLVMFunctionType(LLVMVoidType(), arg_types, 2, false);

    const char *fn_name = get_mangled_name(g, "__zig_add_err_ret_trace_addr", false);
    LLVMValueRef fn_val = LLVMAddFunction(g->module, fn_name, fn_type_ref);
    addLLVMFnAttr(fn_val, "alwaysinline");
    LLVMSetLinkage(fn_val, LLVMInternalLinkage);
    ZigLLVMFunctionSetCallingConv(fn_val, get_llvm_cc(g, CallingConventionUnspecified));
    addLLVMFnAttr(fn_val, "nounwind");
    add_uwtable_attr(g, fn_val);
    // Error return trace memory is in the stack, which is impossible to be at address 0
    // on any architecture.
    addLLVMArgAttr(fn_val, (unsigned)0, "nonnull");
    if (codegen_have_frame_pointer(g)) {
        ZigLLVMAddFunctionAttr(fn_val, "no-frame-pointer-elim", "true");
        ZigLLVMAddFunctionAttr(fn_val, "no-frame-pointer-elim-non-leaf", nullptr);
    }

    LLVMBasicBlockRef entry_block = LLVMAppendBasicBlock(fn_val, "Entry");
    LLVMBasicBlockRef prev_block = LLVMGetInsertBlock(g->builder);
    LLVMValueRef prev_debug_location = LLVMGetCurrentDebugLocation(g->builder);
    LLVMPositionBuilderAtEnd(g->builder, entry_block);
    ZigLLVMClearCurrentDebugLocation(g->builder);

    LLVMTypeRef usize_type_ref = g->builtin_types.entry_usize->llvm_type;

    // stack_trace.instruction_addresses[stack_trace.index & (stack_trace.instruction_addresses.len - 1)] = return_address;

    LLVMValueRef err_ret_trace_ptr = LLVMGetParam(fn_val, 0);
    LLVMValueRef address_value = LLVMGetParam(fn_val, 1);

    size_t index_field_index = g->stack_trace_type->data.structure.fields[0]->gen_index;
    LLVMValueRef index_field_ptr = LLVMBuildStructGEP(g->builder, err_ret_trace_ptr, (unsigned)index_field_index, "");
    size_t addresses_field_index = g->stack_trace_type->data.structure.fields[1]->gen_index;
    LLVMValueRef addresses_field_ptr = LLVMBuildStructGEP(g->builder, err_ret_trace_ptr, (unsigned)addresses_field_index, "");

    ZigType *slice_type = g->stack_trace_type->data.structure.fields[1]->type_entry;
    size_t ptr_field_index = slice_type->data.structure.fields[slice_ptr_index]->gen_index;
    LLVMValueRef ptr_field_ptr = LLVMBuildStructGEP(g->builder, addresses_field_ptr, (unsigned)ptr_field_index, "");
    size_t len_field_index = slice_type->data.structure.fields[slice_len_index]->gen_index;
    LLVMValueRef len_field_ptr = LLVMBuildStructGEP(g->builder, addresses_field_ptr, (unsigned)len_field_index, "");

    LLVMValueRef len_value = gen_load_untyped(g, len_field_ptr, 0, false, "");
    LLVMValueRef index_val = gen_load_untyped(g, index_field_ptr, 0, false, "");
    LLVMValueRef len_val_minus_one = LLVMBuildSub(g->builder, len_value, LLVMConstInt(usize_type_ref, 1, false), "");
    LLVMValueRef masked_val = LLVMBuildAnd(g->builder, index_val, len_val_minus_one, "");
    LLVMValueRef address_indices[] = {
        masked_val,
    };

    LLVMValueRef ptr_value = gen_load_untyped(g, ptr_field_ptr, 0, false, "");
    LLVMValueRef address_slot = LLVMBuildInBoundsGEP(g->builder, ptr_value, address_indices, 1, "");

    gen_store_untyped(g, address_value, address_slot, 0, false);

    // stack_trace.index += 1;
    LLVMValueRef index_plus_one_val = LLVMBuildNUWAdd(g->builder, index_val, LLVMConstInt(usize_type_ref, 1, false), "");
    gen_store_untyped(g, index_plus_one_val, index_field_ptr, 0, false);

    // return;
    LLVMBuildRetVoid(g->builder);

    LLVMPositionBuilderAtEnd(g->builder, prev_block);
    if (!g->strip_debug_symbols) {
        LLVMSetCurrentDebugLocation(g->builder, prev_debug_location);
    }

    g->add_error_return_trace_addr_fn_val = fn_val;
    return fn_val;
}

static LLVMValueRef get_return_err_fn(CodeGen *g) {
    if (g->return_err_fn != nullptr)
        return g->return_err_fn;

    assert(g->err_tag_type != nullptr);

    LLVMTypeRef arg_types[] = {
        // error return trace pointer
        get_llvm_type(g, ptr_to_stack_trace_type(g)),
    };
    LLVMTypeRef fn_type_ref = LLVMFunctionType(LLVMVoidType(), arg_types, 1, false);

    const char *fn_name = get_mangled_name(g, "__zig_return_error", false);
    LLVMValueRef fn_val = LLVMAddFunction(g->module, fn_name, fn_type_ref);
    addLLVMFnAttr(fn_val, "noinline"); // so that we can look at return address
    addLLVMFnAttr(fn_val, "cold");
    LLVMSetLinkage(fn_val, LLVMInternalLinkage);
    ZigLLVMFunctionSetCallingConv(fn_val, get_llvm_cc(g, CallingConventionUnspecified));
    addLLVMFnAttr(fn_val, "nounwind");
    add_uwtable_attr(g, fn_val);
    if (codegen_have_frame_pointer(g)) {
        ZigLLVMAddFunctionAttr(fn_val, "no-frame-pointer-elim", "true");
        ZigLLVMAddFunctionAttr(fn_val, "no-frame-pointer-elim-non-leaf", nullptr);
    }

    // this is above the ZigLLVMClearCurrentDebugLocation
    LLVMValueRef add_error_return_trace_addr_fn_val = get_add_error_return_trace_addr_fn(g);

    LLVMBasicBlockRef entry_block = LLVMAppendBasicBlock(fn_val, "Entry");
    LLVMBasicBlockRef prev_block = LLVMGetInsertBlock(g->builder);
    LLVMValueRef prev_debug_location = LLVMGetCurrentDebugLocation(g->builder);
    LLVMPositionBuilderAtEnd(g->builder, entry_block);
    ZigLLVMClearCurrentDebugLocation(g->builder);

    LLVMValueRef err_ret_trace_ptr = LLVMGetParam(fn_val, 0);

    LLVMTypeRef usize_type_ref = g->builtin_types.entry_usize->llvm_type;
    LLVMValueRef zero = LLVMConstNull(get_llvm_type(g, g->builtin_types.entry_i32));
    LLVMValueRef return_address_ptr = LLVMBuildCall(g->builder, get_return_address_fn_val(g), &zero, 1, "");
    LLVMValueRef return_address = LLVMBuildPtrToInt(g->builder, return_address_ptr, usize_type_ref, "");

    LLVMBasicBlockRef return_block = LLVMAppendBasicBlock(fn_val, "Return");
    LLVMBasicBlockRef dest_non_null_block = LLVMAppendBasicBlock(fn_val, "DestNonNull");

    LLVMValueRef null_dest_bit = LLVMBuildICmp(g->builder, LLVMIntEQ, err_ret_trace_ptr,
            LLVMConstNull(LLVMTypeOf(err_ret_trace_ptr)), "");
    LLVMBuildCondBr(g->builder, null_dest_bit, return_block, dest_non_null_block);

    LLVMPositionBuilderAtEnd(g->builder, return_block);
    LLVMBuildRetVoid(g->builder);

    LLVMPositionBuilderAtEnd(g->builder, dest_non_null_block);
    LLVMValueRef args[] = { err_ret_trace_ptr, return_address };
    ZigLLVMBuildCall(g->builder, add_error_return_trace_addr_fn_val, args, 2,
            get_llvm_cc(g, CallingConventionUnspecified), ZigLLVM_CallAttrAlwaysInline, "");
    LLVMBuildRetVoid(g->builder);

    LLVMPositionBuilderAtEnd(g->builder, prev_block);
    if (!g->strip_debug_symbols) {
        LLVMSetCurrentDebugLocation(g->builder, prev_debug_location);
    }

    g->return_err_fn = fn_val;
    return fn_val;
}

static LLVMValueRef get_safety_crash_err_fn(CodeGen *g) {
    if (g->safety_crash_err_fn != nullptr)
        return g->safety_crash_err_fn;

    static const char *unwrap_err_msg_text = "attempt to unwrap error: ";

    g->generate_error_name_table = true;
    generate_error_name_table(g);
    assert(g->err_name_table != nullptr);

    // Generate the constant part of the error message
    LLVMValueRef msg_prefix_init = LLVMConstString(unwrap_err_msg_text, strlen(unwrap_err_msg_text), 1);
    LLVMValueRef msg_prefix = LLVMAddGlobal(g->module, LLVMTypeOf(msg_prefix_init), "");
    LLVMSetInitializer(msg_prefix, msg_prefix_init);
    LLVMSetLinkage(msg_prefix, LLVMPrivateLinkage);
    LLVMSetGlobalConstant(msg_prefix, true);

    const char *fn_name = get_mangled_name(g, "__zig_fail_unwrap", false);
    LLVMTypeRef fn_type_ref;
    if (g->have_err_ret_tracing) {
        LLVMTypeRef arg_types[] = {
            get_llvm_type(g, get_pointer_to_type(g, get_stack_trace_type(g), false)),
            get_llvm_type(g, g->err_tag_type),
        };
        fn_type_ref = LLVMFunctionType(LLVMVoidType(), arg_types, 2, false);
    } else {
        LLVMTypeRef arg_types[] = {
            get_llvm_type(g, g->err_tag_type),
        };
        fn_type_ref = LLVMFunctionType(LLVMVoidType(), arg_types, 1, false);
    }
    LLVMValueRef fn_val = LLVMAddFunction(g->module, fn_name, fn_type_ref);
    addLLVMFnAttr(fn_val, "noreturn");
    addLLVMFnAttr(fn_val, "cold");
    LLVMSetLinkage(fn_val, LLVMInternalLinkage);
    ZigLLVMFunctionSetCallingConv(fn_val, get_llvm_cc(g, CallingConventionUnspecified));
    addLLVMFnAttr(fn_val, "nounwind");
    add_uwtable_attr(g, fn_val);
    if (codegen_have_frame_pointer(g)) {
        ZigLLVMAddFunctionAttr(fn_val, "no-frame-pointer-elim", "true");
        ZigLLVMAddFunctionAttr(fn_val, "no-frame-pointer-elim-non-leaf", nullptr);
    }
    // Not setting alignment here. See the comment above about
    // "Cannot getTypeInfo() on a type that is unsized!"
    // assertion failure on Darwin.

    LLVMBasicBlockRef entry_block = LLVMAppendBasicBlock(fn_val, "Entry");
    LLVMBasicBlockRef prev_block = LLVMGetInsertBlock(g->builder);
    LLVMValueRef prev_debug_location = LLVMGetCurrentDebugLocation(g->builder);
    LLVMPositionBuilderAtEnd(g->builder, entry_block);
    ZigLLVMClearCurrentDebugLocation(g->builder);

    ZigType *usize_ty = g->builtin_types.entry_usize;
    ZigType *u8_ptr_type = get_pointer_to_type_extra(g, g->builtin_types.entry_u8, true, false,
            PtrLenUnknown, get_abi_alignment(g, g->builtin_types.entry_u8), 0, 0, false);
    ZigType *str_type = get_slice_type(g, u8_ptr_type);

    // Allocate a buffer to hold the fully-formatted error message
    const size_t err_buf_len = strlen(unwrap_err_msg_text) + g->largest_err_name_len;
    LLVMValueRef max_msg_len = LLVMConstInt(usize_ty->llvm_type, err_buf_len, 0);
    LLVMValueRef msg_buffer = LLVMBuildArrayAlloca(g->builder, LLVMInt8Type(), max_msg_len, "msg_buffer");

    // Allocate a []u8 slice for the message
    LLVMValueRef msg_slice = build_alloca(g, str_type, "msg_slice", 0);

    LLVMValueRef err_ret_trace_arg;
    LLVMValueRef err_val;
    if (g->have_err_ret_tracing) {
        err_ret_trace_arg = LLVMGetParam(fn_val, 0);
        err_val = LLVMGetParam(fn_val, 1);
    } else {
        err_ret_trace_arg = nullptr;
        err_val = LLVMGetParam(fn_val, 0);
    }

    // Fetch the error name from the global table
    LLVMValueRef err_table_indices[] = {
        LLVMConstNull(usize_ty->llvm_type),
        err_val,
    };
    LLVMValueRef err_name_val = LLVMBuildInBoundsGEP(g->builder, g->err_name_table, err_table_indices, 2, "");

    LLVMValueRef ptr_field_ptr = LLVMBuildStructGEP(g->builder, err_name_val, slice_ptr_index, "");
    LLVMValueRef err_name_ptr = gen_load_untyped(g, ptr_field_ptr, 0, false, "");

    LLVMValueRef len_field_ptr = LLVMBuildStructGEP(g->builder, err_name_val, slice_len_index, "");
    LLVMValueRef err_name_len = gen_load_untyped(g, len_field_ptr, 0, false, "");

    LLVMValueRef msg_prefix_len = LLVMConstInt(usize_ty->llvm_type, strlen(unwrap_err_msg_text), false);
    // Points to the beginning of msg_buffer
    LLVMValueRef msg_buffer_ptr_indices[] = {
        LLVMConstNull(usize_ty->llvm_type),
    };
    LLVMValueRef msg_buffer_ptr = LLVMBuildInBoundsGEP(g->builder, msg_buffer, msg_buffer_ptr_indices, 1, "");
    // Points to the beginning of the constant prefix message
    LLVMValueRef msg_prefix_ptr_indices[] = {
        LLVMConstNull(usize_ty->llvm_type),
    };
    LLVMValueRef msg_prefix_ptr = LLVMConstInBoundsGEP(msg_prefix, msg_prefix_ptr_indices, 1);

    // Build the message using the prefix...
    ZigLLVMBuildMemCpy(g->builder, msg_buffer_ptr, 1, msg_prefix_ptr, 1, msg_prefix_len, false);
    // ..and append the error name
    LLVMValueRef msg_buffer_ptr_after_indices[] = {
        msg_prefix_len,
    };
    LLVMValueRef msg_buffer_ptr_after = LLVMBuildInBoundsGEP(g->builder, msg_buffer, msg_buffer_ptr_after_indices, 1, "");
    ZigLLVMBuildMemCpy(g->builder, msg_buffer_ptr_after, 1, err_name_ptr, 1, err_name_len, false);

    // Set the slice pointer
    LLVMValueRef msg_slice_ptr_field_ptr = LLVMBuildStructGEP(g->builder, msg_slice, slice_ptr_index, "");
    gen_store_untyped(g, msg_buffer_ptr, msg_slice_ptr_field_ptr, 0, false);

    // Set the slice length
    LLVMValueRef slice_len = LLVMBuildNUWAdd(g->builder, msg_prefix_len, err_name_len, "");
    LLVMValueRef msg_slice_len_field_ptr = LLVMBuildStructGEP(g->builder, msg_slice, slice_len_index, "");
    gen_store_untyped(g, slice_len, msg_slice_len_field_ptr, 0, false);

    // Call panic()
    gen_panic(g, msg_slice, err_ret_trace_arg, false);

    LLVMPositionBuilderAtEnd(g->builder, prev_block);
    if (!g->strip_debug_symbols) {
        LLVMSetCurrentDebugLocation(g->builder, prev_debug_location);
    }

    g->safety_crash_err_fn = fn_val;
    return fn_val;
}

static LLVMValueRef get_cur_err_ret_trace_val(CodeGen *g, Scope *scope, bool *is_llvm_alloca) {
    if (!g->have_err_ret_tracing) {
        *is_llvm_alloca = false;
        return nullptr;
    }
    if (g->cur_err_ret_trace_val_stack != nullptr) {
        *is_llvm_alloca = !fn_is_async(g->cur_fn);
        return g->cur_err_ret_trace_val_stack;
    }
    *is_llvm_alloca = false;
    return g->cur_err_ret_trace_val_arg;
}

static void gen_safety_crash_for_err(CodeGen *g, LLVMValueRef err_val, Scope *scope) {
    LLVMValueRef safety_crash_err_fn = get_safety_crash_err_fn(g);
    LLVMValueRef call_instruction;
    bool is_llvm_alloca = false;
    if (g->have_err_ret_tracing) {
        LLVMValueRef err_ret_trace_val = get_cur_err_ret_trace_val(g, scope, &is_llvm_alloca);
        if (err_ret_trace_val == nullptr) {
            err_ret_trace_val = LLVMConstNull(get_llvm_type(g, ptr_to_stack_trace_type(g)));
        }
        LLVMValueRef args[] = {
            err_ret_trace_val,
            err_val,
        };
        call_instruction = ZigLLVMBuildCall(g->builder, safety_crash_err_fn, args, 2,
                get_llvm_cc(g, CallingConventionUnspecified), ZigLLVM_CallAttrAuto, "");
    } else {
        LLVMValueRef args[] = {
            err_val,
        };
        call_instruction = ZigLLVMBuildCall(g->builder, safety_crash_err_fn, args, 1,
                get_llvm_cc(g, CallingConventionUnspecified), ZigLLVM_CallAttrAuto, "");
    }
    if (!is_llvm_alloca) {
        LLVMSetTailCall(call_instruction, true);
    }
    LLVMBuildUnreachable(g->builder);
}

static void add_bounds_check(CodeGen *g, LLVMValueRef target_val,
        LLVMIntPredicate lower_pred, LLVMValueRef lower_value,
        LLVMIntPredicate upper_pred, LLVMValueRef upper_value)
{
    if (!lower_value && !upper_value) {
        return;
    }
    if (upper_value && !lower_value) {
        lower_value = upper_value;
        lower_pred = upper_pred;
        upper_value = nullptr;
    }

    LLVMBasicBlockRef bounds_check_fail_block = LLVMAppendBasicBlock(g->cur_fn_val, "BoundsCheckFail");
    LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "BoundsCheckOk");
    LLVMBasicBlockRef lower_ok_block = upper_value ?
        LLVMAppendBasicBlock(g->cur_fn_val, "FirstBoundsCheckOk") : ok_block;

    LLVMValueRef lower_ok_val = LLVMBuildICmp(g->builder, lower_pred, target_val, lower_value, "");
    LLVMBuildCondBr(g->builder, lower_ok_val, lower_ok_block, bounds_check_fail_block);

    LLVMPositionBuilderAtEnd(g->builder, bounds_check_fail_block);
    gen_safety_crash(g, PanicMsgIdBoundsCheckFailure);

    if (upper_value) {
        LLVMPositionBuilderAtEnd(g->builder, lower_ok_block);
        LLVMValueRef upper_ok_val = LLVMBuildICmp(g->builder, upper_pred, target_val, upper_value, "");
        LLVMBuildCondBr(g->builder, upper_ok_val, ok_block, bounds_check_fail_block);
    }

    LLVMPositionBuilderAtEnd(g->builder, ok_block);
}

static void add_sentinel_check(CodeGen *g, LLVMValueRef sentinel_elem_ptr, ZigValue *sentinel) {
    LLVMValueRef expected_sentinel = gen_const_val(g, sentinel, "");

    LLVMValueRef actual_sentinel = gen_load_untyped(g, sentinel_elem_ptr, 0, false, "");
    LLVMValueRef ok_bit;
    if (sentinel->type->id == ZigTypeIdFloat) {
        ok_bit = LLVMBuildFCmp(g->builder, LLVMRealOEQ, actual_sentinel, expected_sentinel, "");
    } else {
        ok_bit = LLVMBuildICmp(g->builder, LLVMIntEQ, actual_sentinel, expected_sentinel, "");
    }

    LLVMBasicBlockRef fail_block = LLVMAppendBasicBlock(g->cur_fn_val, "SentinelFail");
    LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "SentinelOk");
    LLVMBuildCondBr(g->builder, ok_bit, ok_block, fail_block);

    LLVMPositionBuilderAtEnd(g->builder, fail_block);
    gen_safety_crash(g, PanicMsgIdBadSentinel);

    LLVMPositionBuilderAtEnd(g->builder, ok_block);
}

static LLVMValueRef gen_assert_zero(CodeGen *g, LLVMValueRef expr_val, ZigType *int_type) {
    LLVMValueRef zero = LLVMConstNull(get_llvm_type(g, int_type));
    LLVMValueRef ok_bit = LLVMBuildICmp(g->builder, LLVMIntEQ, expr_val, zero, "");
    LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "CastShortenOk");
    LLVMBasicBlockRef fail_block = LLVMAppendBasicBlock(g->cur_fn_val, "CastShortenFail");
    LLVMBuildCondBr(g->builder, ok_bit, ok_block, fail_block);

    LLVMPositionBuilderAtEnd(g->builder, fail_block);
    gen_safety_crash(g, PanicMsgIdCastTruncatedData);

    LLVMPositionBuilderAtEnd(g->builder, ok_block);
    return nullptr;
}

static LLVMValueRef gen_widen_or_shorten(CodeGen *g, bool want_runtime_safety, ZigType *actual_type,
        ZigType *wanted_type, LLVMValueRef expr_val)
{
    assert(actual_type->id == wanted_type->id);
    assert(expr_val != nullptr);

    uint64_t actual_bits;
    uint64_t wanted_bits;
    if (actual_type->id == ZigTypeIdFloat) {
        actual_bits = actual_type->data.floating.bit_count;
        wanted_bits = wanted_type->data.floating.bit_count;
    } else if (actual_type->id == ZigTypeIdInt) {
        actual_bits = actual_type->data.integral.bit_count;
        wanted_bits = wanted_type->data.integral.bit_count;
    } else {
        zig_unreachable();
    }

    if (actual_type->id == ZigTypeIdInt &&
        !wanted_type->data.integral.is_signed && actual_type->data.integral.is_signed &&
        want_runtime_safety)
    {
        LLVMValueRef zero = LLVMConstNull(get_llvm_type(g, actual_type));
        LLVMValueRef ok_bit = LLVMBuildICmp(g->builder, LLVMIntSGE, expr_val, zero, "");

        LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "SignCastOk");
        LLVMBasicBlockRef fail_block = LLVMAppendBasicBlock(g->cur_fn_val, "SignCastFail");
        LLVMBuildCondBr(g->builder, ok_bit, ok_block, fail_block);

        LLVMPositionBuilderAtEnd(g->builder, fail_block);
        gen_safety_crash(g, PanicMsgIdCastNegativeToUnsigned);

        LLVMPositionBuilderAtEnd(g->builder, ok_block);
    }

    if (actual_bits == wanted_bits) {
        return expr_val;
    } else if (actual_bits < wanted_bits) {
        if (actual_type->id == ZigTypeIdFloat) {
            return LLVMBuildFPExt(g->builder, expr_val, get_llvm_type(g, wanted_type), "");
        } else if (actual_type->id == ZigTypeIdInt) {
            if (actual_type->data.integral.is_signed) {
                return LLVMBuildSExt(g->builder, expr_val, get_llvm_type(g, wanted_type), "");
            } else {
                return LLVMBuildZExt(g->builder, expr_val, get_llvm_type(g, wanted_type), "");
            }
        } else {
            zig_unreachable();
        }
    } else if (actual_bits > wanted_bits) {
        if (actual_type->id == ZigTypeIdFloat) {
            return LLVMBuildFPTrunc(g->builder, expr_val, get_llvm_type(g, wanted_type), "");
        } else if (actual_type->id == ZigTypeIdInt) {
            if (wanted_bits == 0) {
                if (!want_runtime_safety)
                    return nullptr;

                return gen_assert_zero(g, expr_val, actual_type);
            }
            LLVMValueRef trunc_val = LLVMBuildTrunc(g->builder, expr_val, get_llvm_type(g, wanted_type), "");
            if (!want_runtime_safety) {
                return trunc_val;
            }
            LLVMValueRef orig_val;
            if (wanted_type->data.integral.is_signed) {
                orig_val = LLVMBuildSExt(g->builder, trunc_val, get_llvm_type(g, actual_type), "");
            } else {
                orig_val = LLVMBuildZExt(g->builder, trunc_val, get_llvm_type(g, actual_type), "");
            }
            LLVMValueRef ok_bit = LLVMBuildICmp(g->builder, LLVMIntEQ, expr_val, orig_val, "");
            LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "CastShortenOk");
            LLVMBasicBlockRef fail_block = LLVMAppendBasicBlock(g->cur_fn_val, "CastShortenFail");
            LLVMBuildCondBr(g->builder, ok_bit, ok_block, fail_block);

            LLVMPositionBuilderAtEnd(g->builder, fail_block);
            gen_safety_crash(g, PanicMsgIdCastTruncatedData);

            LLVMPositionBuilderAtEnd(g->builder, ok_block);
            return trunc_val;
        } else {
            zig_unreachable();
        }
    } else {
        zig_unreachable();
    }
}

typedef LLVMValueRef (*BuildBinOpFunc)(LLVMBuilderRef, LLVMValueRef, LLVMValueRef, const char *);
// These are lookup table using the AddSubMul enum as the lookup.
// If AddSubMul ever changes, then these tables will be out of
// date.
static const BuildBinOpFunc float_op[3] = { LLVMBuildFAdd, LLVMBuildFSub, LLVMBuildFMul };
static const BuildBinOpFunc wrap_op[3] = { LLVMBuildAdd, LLVMBuildSub, LLVMBuildMul };
static const BuildBinOpFunc signed_op[3] = { LLVMBuildNSWAdd, LLVMBuildNSWSub, LLVMBuildNSWMul };
static const BuildBinOpFunc unsigned_op[3] = { LLVMBuildNUWAdd, LLVMBuildNUWSub, LLVMBuildNUWMul };

static LLVMValueRef gen_overflow_op(CodeGen *g, ZigType *operand_type, AddSubMul op,
        LLVMValueRef val1, LLVMValueRef val2)
{
    LLVMValueRef overflow_bit;
    LLVMValueRef result;

    if (operand_type->id == ZigTypeIdVector) {
        ZigType *int_type = operand_type->data.vector.elem_type;
        assert(int_type->id == ZigTypeIdInt);
        LLVMTypeRef one_more_bit_int = LLVMIntType(int_type->data.integral.bit_count + 1);
        LLVMTypeRef one_more_bit_int_vector = LLVMVectorType(one_more_bit_int, operand_type->data.vector.len);
        const auto buildExtFn = int_type->data.integral.is_signed ? LLVMBuildSExt : LLVMBuildZExt;
        LLVMValueRef extended1 = buildExtFn(g->builder, val1, one_more_bit_int_vector, "");
        LLVMValueRef extended2 = buildExtFn(g->builder, val2, one_more_bit_int_vector, "");
        LLVMValueRef extended_result = wrap_op[op](g->builder, extended1, extended2, "");
        result = LLVMBuildTrunc(g->builder, extended_result, get_llvm_type(g, operand_type), "");

        LLVMValueRef re_extended_result = buildExtFn(g->builder, result, one_more_bit_int_vector, "");
        LLVMValueRef overflow_vector = LLVMBuildICmp(g->builder, LLVMIntNE, extended_result, re_extended_result, "");
        LLVMTypeRef bitcast_int_type = LLVMIntType(operand_type->data.vector.len);
        LLVMValueRef bitcasted_overflow = LLVMBuildBitCast(g->builder, overflow_vector, bitcast_int_type, "");
        LLVMValueRef zero = LLVMConstNull(bitcast_int_type);
        overflow_bit = LLVMBuildICmp(g->builder, LLVMIntNE, bitcasted_overflow, zero, "");
    } else {
        LLVMValueRef fn_val = get_int_overflow_fn(g, operand_type, op);
        LLVMValueRef params[] = {
            val1,
            val2,
        };
        LLVMValueRef result_struct = LLVMBuildCall(g->builder, fn_val, params, 2, "");
        result = LLVMBuildExtractValue(g->builder, result_struct, 0, "");
        overflow_bit = LLVMBuildExtractValue(g->builder, result_struct, 1, "");
    }

    LLVMBasicBlockRef fail_block = LLVMAppendBasicBlock(g->cur_fn_val, "OverflowFail");
    LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "OverflowOk");
    LLVMBuildCondBr(g->builder, overflow_bit, fail_block, ok_block);

    LLVMPositionBuilderAtEnd(g->builder, fail_block);
    gen_safety_crash(g, PanicMsgIdIntegerOverflow);

    LLVMPositionBuilderAtEnd(g->builder, ok_block);
    return result;
}

static LLVMIntPredicate cmp_op_to_int_predicate(IrBinOp cmp_op, bool is_signed) {
    switch (cmp_op) {
        case IrBinOpCmpEq:
            return LLVMIntEQ;
        case IrBinOpCmpNotEq:
            return LLVMIntNE;
        case IrBinOpCmpLessThan:
            return is_signed ? LLVMIntSLT : LLVMIntULT;
        case IrBinOpCmpGreaterThan:
            return is_signed ? LLVMIntSGT : LLVMIntUGT;
        case IrBinOpCmpLessOrEq:
            return is_signed ? LLVMIntSLE : LLVMIntULE;
        case IrBinOpCmpGreaterOrEq:
            return is_signed ? LLVMIntSGE : LLVMIntUGE;
        default:
            zig_unreachable();
    }
}

static LLVMRealPredicate cmp_op_to_real_predicate(IrBinOp cmp_op) {
    switch (cmp_op) {
        case IrBinOpCmpEq:
            return LLVMRealOEQ;
        case IrBinOpCmpNotEq:
            return LLVMRealUNE;
        case IrBinOpCmpLessThan:
            return LLVMRealOLT;
        case IrBinOpCmpGreaterThan:
            return LLVMRealOGT;
        case IrBinOpCmpLessOrEq:
            return LLVMRealOLE;
        case IrBinOpCmpGreaterOrEq:
            return LLVMRealOGE;
        default:
            zig_unreachable();
    }
}

static void gen_assign_raw(CodeGen *g, LLVMValueRef ptr, ZigType *ptr_type,
        LLVMValueRef value)
{
    assert(ptr_type->id == ZigTypeIdPointer);
    ZigType *child_type = ptr_type->data.pointer.child_type;

    if (!type_has_bits(child_type))
        return;

    if (handle_is_ptr(child_type)) {
        assert(LLVMGetTypeKind(LLVMTypeOf(value)) == LLVMPointerTypeKind);
        assert(LLVMGetTypeKind(LLVMTypeOf(ptr)) == LLVMPointerTypeKind);

        LLVMTypeRef ptr_u8 = LLVMPointerType(LLVMInt8Type(), 0);

        LLVMValueRef src_ptr = LLVMBuildBitCast(g->builder, value, ptr_u8, "");
        LLVMValueRef dest_ptr = LLVMBuildBitCast(g->builder, ptr, ptr_u8, "");

        ZigType *usize = g->builtin_types.entry_usize;
        uint64_t size_bytes = LLVMStoreSizeOfType(g->target_data_ref, get_llvm_type(g, child_type));
        uint64_t align_bytes = get_ptr_align(g, ptr_type);
        assert(size_bytes > 0);
        assert(align_bytes > 0);

        ZigLLVMBuildMemCpy(g->builder, dest_ptr, align_bytes, src_ptr, align_bytes,
                LLVMConstInt(usize->llvm_type, size_bytes, false),
                ptr_type->data.pointer.is_volatile);
        return;
    }

    assert(ptr_type->data.pointer.vector_index != VECTOR_INDEX_RUNTIME);
    if (ptr_type->data.pointer.vector_index != VECTOR_INDEX_NONE) {
        LLVMValueRef index_val = LLVMConstInt(LLVMInt32Type(),
                ptr_type->data.pointer.vector_index, false);
        LLVMValueRef loaded_vector = gen_load(g, ptr, ptr_type, "");
        LLVMValueRef new_vector = LLVMBuildInsertElement(g->builder, loaded_vector, value,
                index_val, "");
        gen_store(g, new_vector, ptr, ptr_type);
        return;
    }

    uint32_t host_int_bytes = ptr_type->data.pointer.host_int_bytes;
    if (host_int_bytes == 0) {
        gen_store(g, value, ptr, ptr_type);
        return;
    }

    bool big_endian = g->is_big_endian;

    LLVMTypeRef int_ptr_ty = LLVMPointerType(LLVMIntType(host_int_bytes * 8), 0);
    LLVMValueRef int_ptr = LLVMBuildBitCast(g->builder, ptr, int_ptr_ty, "");
    LLVMValueRef containing_int = gen_load(g, int_ptr, ptr_type, "");
    uint32_t host_bit_count = LLVMGetIntTypeWidth(LLVMTypeOf(containing_int));
    assert(host_bit_count == host_int_bytes * 8);
    uint32_t size_in_bits = type_size_bits(g, child_type);

    uint32_t bit_offset = ptr_type->data.pointer.bit_offset_in_host;
    uint32_t shift_amt = big_endian ? host_bit_count - bit_offset - size_in_bits : bit_offset;
    LLVMValueRef shift_amt_val = LLVMConstInt(LLVMTypeOf(containing_int), shift_amt, false);

    // Convert to equally-sized integer type in order to perform the bit
    // operations on the value to store
    LLVMTypeRef value_bits_type = LLVMIntType(size_in_bits);
    LLVMValueRef value_bits = LLVMBuildBitCast(g->builder, value, value_bits_type, "");

    LLVMValueRef mask_val = LLVMConstAllOnes(value_bits_type);
    mask_val = LLVMConstZExt(mask_val, LLVMTypeOf(containing_int));
    mask_val = LLVMConstShl(mask_val, shift_amt_val);
    mask_val = LLVMConstNot(mask_val);

    LLVMValueRef anded_containing_int = LLVMBuildAnd(g->builder, containing_int, mask_val, "");
    LLVMValueRef extended_value = LLVMBuildZExt(g->builder, value_bits, LLVMTypeOf(containing_int), "");
    LLVMValueRef shifted_value = LLVMBuildShl(g->builder, extended_value, shift_amt_val, "");
    LLVMValueRef ored_value = LLVMBuildOr(g->builder, shifted_value, anded_containing_int, "");

    gen_store(g, ored_value, int_ptr, ptr_type);
}

static void gen_var_debug_decl(CodeGen *g, ZigVar *var) {
    if (g->strip_debug_symbols) return;
    assert(var->di_loc_var != nullptr);
    AstNode *source_node = var->decl_node;
    ZigLLVMDILocation *debug_loc = ZigLLVMGetDebugLoc((unsigned)source_node->line + 1,
            (unsigned)source_node->column + 1, get_di_scope(g, var->parent_scope));
    ZigLLVMInsertDeclareAtEnd(g->dbuilder, var->value_ref, var->di_loc_var, debug_loc,
            LLVMGetInsertBlock(g->builder));
}

static LLVMValueRef ir_llvm_value(CodeGen *g, IrInstruction *instruction) {
    Error err;

    bool value_has_bits;
    if ((err = type_has_bits2(g, instruction->value->type, &value_has_bits)))
        codegen_report_errors_and_exit(g);

    if (!value_has_bits)
        return nullptr;

    if (!instruction->llvm_value) {
        if (instruction->id == IrInstructionIdAwaitGen) {
            IrInstructionAwaitGen *await = reinterpret_cast<IrInstructionAwaitGen*>(instruction);
            if (await->result_loc != nullptr) {
                return get_handle_value(g, ir_llvm_value(g, await->result_loc),
                    await->result_loc->value->type->data.pointer.child_type, await->result_loc->value->type);
            }
        }
        if (instruction->spill != nullptr) {
            ZigType *ptr_type = instruction->spill->value->type;
            ir_assert(ptr_type->id == ZigTypeIdPointer, instruction);
            return get_handle_value(g, ir_llvm_value(g, instruction->spill),
                ptr_type->data.pointer.child_type, instruction->spill->value->type);
        }
        ir_assert(instruction->value->special != ConstValSpecialRuntime, instruction);
        assert(instruction->value->type);
        render_const_val(g, instruction->value, "");
        // we might have to do some pointer casting here due to the way union
        // values are rendered with a type other than the one we expect
        if (handle_is_ptr(instruction->value->type)) {
            render_const_val_global(g, instruction->value, "");
            ZigType *ptr_type = get_pointer_to_type(g, instruction->value->type, true);
            instruction->llvm_value = LLVMBuildBitCast(g->builder, instruction->value->llvm_global, get_llvm_type(g, ptr_type), "");
        } else {
            instruction->llvm_value = LLVMBuildBitCast(g->builder, instruction->value->llvm_value,
                    get_llvm_type(g, instruction->value->type), "");
        }
        assert(instruction->llvm_value);
    }
    return instruction->llvm_value;
}

void codegen_report_errors_and_exit(CodeGen *g) {
    assert(g->errors.length != 0);
    for (size_t i = 0; i < g->errors.length; i += 1) {
        ErrorMsg *err = g->errors.at(i);
        print_err_msg(err, g->err_color);
    }
    exit(1);
}

static void report_errors_and_maybe_exit(CodeGen *g) {
    if (g->errors.length != 0) {
        codegen_report_errors_and_exit(g);
    }
}

ATTRIBUTE_NORETURN
static void give_up_with_c_abi_error(CodeGen *g, AstNode *source_node) {
    ErrorMsg *msg = add_node_error(g, source_node,
            buf_sprintf("TODO: support C ABI for more targets. https://github.com/ziglang/zig/issues/1481"));
    add_error_note(g, msg, source_node,
        buf_sprintf("pointers, integers, floats, bools, and enums work on all targets"));
    codegen_report_errors_and_exit(g);
}

static LLVMValueRef build_alloca(CodeGen *g, ZigType *type_entry, const char *name, uint32_t alignment) {
    LLVMValueRef result = LLVMBuildAlloca(g->builder, get_llvm_type(g, type_entry), name);
    LLVMSetAlignment(result, (alignment == 0) ? get_abi_alignment(g, type_entry) : alignment);
    return result;
}

static bool iter_function_params_c_abi(CodeGen *g, ZigType *fn_type, FnWalk *fn_walk, size_t src_i) {
    // Initialized from the type for some walks, but because of C var args,
    // initialized based on callsite instructions for that one.
    FnTypeParamInfo *param_info = nullptr;
    ZigType *ty;
    ZigType *dest_ty = nullptr;
    AstNode *source_node = nullptr;
    LLVMValueRef val;
    LLVMValueRef llvm_fn;
    unsigned di_arg_index;
    ZigVar *var;
    switch (fn_walk->id) {
        case FnWalkIdAttrs:
            if (src_i >= fn_type->data.fn.fn_type_id.param_count)
                return false;
            param_info = &fn_type->data.fn.fn_type_id.param_info[src_i];
            ty = param_info->type;
            source_node = fn_walk->data.attrs.fn->proto_node;
            llvm_fn = fn_walk->data.attrs.llvm_fn;
            break;
        case FnWalkIdCall: {
            if (src_i >= fn_walk->data.call.inst->arg_count)
                return false;
            IrInstruction *arg = fn_walk->data.call.inst->args[src_i];
            ty = arg->value->type;
            source_node = arg->source_node;
            val = ir_llvm_value(g, arg);
            break;
        }
        case FnWalkIdTypes:
            if (src_i >= fn_type->data.fn.fn_type_id.param_count)
                return false;
            param_info = &fn_type->data.fn.fn_type_id.param_info[src_i];
            ty = param_info->type;
            break;
        case FnWalkIdVars:
            assert(src_i < fn_type->data.fn.fn_type_id.param_count);
            param_info = &fn_type->data.fn.fn_type_id.param_info[src_i];
            ty = param_info->type;
            var = fn_walk->data.vars.var;
            source_node = var->decl_node;
            llvm_fn = fn_walk->data.vars.llvm_fn;
            break;
        case FnWalkIdInits:
            if (src_i >= fn_type->data.fn.fn_type_id.param_count)
                return false;
            param_info = &fn_type->data.fn.fn_type_id.param_info[src_i];
            ty = param_info->type;
            var = fn_walk->data.inits.fn->variable_list.at(src_i);
            source_node = fn_walk->data.inits.fn->proto_node;
            llvm_fn = fn_walk->data.inits.llvm_fn;
            break;
    }

    if (type_is_c_abi_int(g, ty) || ty->id == ZigTypeIdFloat || ty->id == ZigTypeIdVector ||
        ty->id == ZigTypeIdInt // TODO investigate if we need to change this
    ) {
        switch (fn_walk->id) {
            case FnWalkIdAttrs: {
                ZigType *ptr_type = get_codegen_ptr_type(ty);
                if (ptr_type != nullptr) {
                    if (type_is_nonnull_ptr(ty)) {
                        addLLVMArgAttr(llvm_fn, fn_walk->data.attrs.gen_i, "nonnull");
                    }
                    if (ptr_type->data.pointer.is_const) {
                        addLLVMArgAttr(llvm_fn, fn_walk->data.attrs.gen_i, "readonly");
                    }
                    if (param_info->is_noalias) {
                        addLLVMArgAttr(llvm_fn, fn_walk->data.attrs.gen_i, "noalias");
                    }
                }
                fn_walk->data.attrs.gen_i += 1;
                break;
            }
            case FnWalkIdCall:
                fn_walk->data.call.gen_param_values->append(val);
                break;
            case FnWalkIdTypes:
                fn_walk->data.types.gen_param_types->append(get_llvm_type(g, ty));
                fn_walk->data.types.param_di_types->append(get_llvm_di_type(g, ty));
                break;
            case FnWalkIdVars: {
                var->value_ref = build_alloca(g, ty, var->name, var->align_bytes);
                di_arg_index = fn_walk->data.vars.gen_i;
                fn_walk->data.vars.gen_i += 1;
                dest_ty = ty;
                goto var_ok;
            }
            case FnWalkIdInits:
                clear_debug_source_node(g);
                gen_store_untyped(g, LLVMGetParam(llvm_fn, fn_walk->data.inits.gen_i), var->value_ref, var->align_bytes, false);
                if (var->decl_node) {
                    gen_var_debug_decl(g, var);
                }
                fn_walk->data.inits.gen_i += 1;
                break;
        }
        return true;
    }

    {
        // Arrays are just pointers
        if (ty->id == ZigTypeIdArray) {
            assert(handle_is_ptr(ty));
            switch (fn_walk->id) {
                case FnWalkIdAttrs:
                    // arrays passed to C ABI functions may not be at address 0
                    addLLVMArgAttr(llvm_fn, fn_walk->data.attrs.gen_i, "nonnull");
                    addLLVMArgAttrInt(llvm_fn, fn_walk->data.attrs.gen_i, "align", get_abi_alignment(g, ty));
                    fn_walk->data.attrs.gen_i += 1;
                    break;
                case FnWalkIdCall:
                    fn_walk->data.call.gen_param_values->append(val);
                    break;
                case FnWalkIdTypes: {
                    ZigType *gen_type = get_pointer_to_type(g, ty, true);
                    fn_walk->data.types.gen_param_types->append(get_llvm_type(g, gen_type));
                    fn_walk->data.types.param_di_types->append(get_llvm_di_type(g, gen_type));
                    break;
                }
                case FnWalkIdVars: {
                    var->value_ref = LLVMGetParam(llvm_fn,  fn_walk->data.vars.gen_i);
                    di_arg_index = fn_walk->data.vars.gen_i;
                    dest_ty = get_pointer_to_type(g, ty, false);
                    fn_walk->data.vars.gen_i += 1;
                    goto var_ok;
                }
                case FnWalkIdInits:
                    if (var->decl_node) {
                        gen_var_debug_decl(g, var);
                    }
                    fn_walk->data.inits.gen_i += 1;
                    break;
            }
            return true;
        }

        X64CABIClass abi_class = type_c_abi_x86_64_class(g, ty);
        size_t ty_size = type_size(g, ty);
        if (abi_class == X64CABIClass_MEMORY || abi_class == X64CABIClass_MEMORY_nobyval) {
            assert(handle_is_ptr(ty));
            switch (fn_walk->id) {
                case FnWalkIdAttrs:
                    if (abi_class != X64CABIClass_MEMORY_nobyval) {
                        ZigLLVMAddByValAttr(llvm_fn, fn_walk->data.attrs.gen_i + 1, get_llvm_type(g, ty));
                        addLLVMArgAttrInt(llvm_fn, fn_walk->data.attrs.gen_i, "align", get_abi_alignment(g, ty));
                    } else if (g->zig_target->arch == ZigLLVM_aarch64 ||
                            g->zig_target->arch == ZigLLVM_aarch64_be)
                    {
                        // no attrs needed
                    } else {
                        if (source_node != nullptr) {
                            give_up_with_c_abi_error(g, source_node);
                        }
                        // otherwise allow codegen code to report a compile error
                        return false;
                    }

                    // Byvalue parameters must not have address 0
                    addLLVMArgAttr(llvm_fn, fn_walk->data.attrs.gen_i, "nonnull");
                    fn_walk->data.attrs.gen_i += 1;
                    break;
                case FnWalkIdCall:
                    fn_walk->data.call.gen_param_values->append(val);
                    break;
                case FnWalkIdTypes: {
                    ZigType *gen_type = get_pointer_to_type(g, ty, true);
                    fn_walk->data.types.gen_param_types->append(get_llvm_type(g, gen_type));
                    fn_walk->data.types.param_di_types->append(get_llvm_di_type(g, gen_type));
                    break;
                }
                case FnWalkIdVars: {
                    di_arg_index = fn_walk->data.vars.gen_i;
                    var->value_ref = LLVMGetParam(llvm_fn,  fn_walk->data.vars.gen_i);
                    dest_ty = get_pointer_to_type(g, ty, false);
                    fn_walk->data.vars.gen_i += 1;
                    goto var_ok;
                }
                case FnWalkIdInits:
                    if (var->decl_node) {
                        gen_var_debug_decl(g, var);
                    }
                    fn_walk->data.inits.gen_i += 1;
                    break;
            }
            return true;
        } else if (abi_class == X64CABIClass_INTEGER) {
            switch (fn_walk->id) {
                case FnWalkIdAttrs:
                    fn_walk->data.attrs.gen_i += 1;
                    break;
                case FnWalkIdCall: {
                    LLVMTypeRef ptr_to_int_type_ref = LLVMPointerType(LLVMIntType((unsigned)ty_size * 8), 0);
                    LLVMValueRef bitcasted = LLVMBuildBitCast(g->builder, val, ptr_to_int_type_ref, "");
                    LLVMValueRef loaded = LLVMBuildLoad(g->builder, bitcasted, "");
                    fn_walk->data.call.gen_param_values->append(loaded);
                    break;
                }
                case FnWalkIdTypes: {
                    ZigType *gen_type = get_int_type(g, false, ty_size * 8);
                    fn_walk->data.types.gen_param_types->append(get_llvm_type(g, gen_type));
                    fn_walk->data.types.param_di_types->append(get_llvm_di_type(g, gen_type));
                    break;
                }
                case FnWalkIdVars: {
                    di_arg_index = fn_walk->data.vars.gen_i;
                    var->value_ref = build_alloca(g, ty, var->name, var->align_bytes);
                    fn_walk->data.vars.gen_i += 1;
                    dest_ty = ty;
                    goto var_ok;
                }
                case FnWalkIdInits: {
                    clear_debug_source_node(g);
                    if (!fn_is_async(fn_walk->data.inits.fn)) {
                        LLVMValueRef arg = LLVMGetParam(llvm_fn, fn_walk->data.inits.gen_i);
                        LLVMTypeRef ptr_to_int_type_ref = LLVMPointerType(LLVMIntType((unsigned)ty_size * 8), 0);
                        LLVMValueRef bitcasted = LLVMBuildBitCast(g->builder, var->value_ref, ptr_to_int_type_ref, "");
                        gen_store_untyped(g, arg, bitcasted, var->align_bytes, false);
                    }
                    if (var->decl_node) {
                        gen_var_debug_decl(g, var);
                    }
                    fn_walk->data.inits.gen_i += 1;
                    break;
                }
            }
            return true;
        }
    }
    if (source_node != nullptr) {
        give_up_with_c_abi_error(g, source_node);
    }
    // otherwise allow codegen code to report a compile error
    return false;

var_ok:
    if (dest_ty != nullptr && var->decl_node) {
        // arg index + 1 because the 0 index is return value
        var->di_loc_var = ZigLLVMCreateParameterVariable(g->dbuilder, get_di_scope(g, var->parent_scope),
                var->name, fn_walk->data.vars.import->data.structure.root_struct->di_file,
                (unsigned)(var->decl_node->line + 1),
                get_llvm_di_type(g, dest_ty), !g->strip_debug_symbols, 0, di_arg_index + 1);
    }
    return true;
}

void walk_function_params(CodeGen *g, ZigType *fn_type, FnWalk *fn_walk) {
    CallingConvention cc = fn_type->data.fn.fn_type_id.cc;
    if (cc == CallingConventionC) {
        size_t src_i = 0;
        for (;;) {
            if (!iter_function_params_c_abi(g, fn_type, fn_walk, src_i))
                break;
            src_i += 1;
        }
        return;
    }
    if (fn_walk->id == FnWalkIdCall) {
        IrInstructionCallGen *instruction = fn_walk->data.call.inst;
        bool is_var_args = fn_walk->data.call.is_var_args;
        for (size_t call_i = 0; call_i < instruction->arg_count; call_i += 1) {
            IrInstruction *param_instruction = instruction->args[call_i];
            ZigType *param_type = param_instruction->value->type;
            if (is_var_args || type_has_bits(param_type)) {
                LLVMValueRef param_value = ir_llvm_value(g, param_instruction);
                assert(param_value);
                fn_walk->data.call.gen_param_values->append(param_value);
                fn_walk->data.call.gen_param_types->append(param_type);
            }
        }
        return;
    }
    size_t next_var_i = 0;
    for (size_t param_i = 0; param_i < fn_type->data.fn.fn_type_id.param_count; param_i += 1) {
        FnGenParamInfo *gen_info = &fn_type->data.fn.gen_param_info[param_i];
        size_t gen_index = gen_info->gen_index;

        if (gen_index == SIZE_MAX) {
            continue;
        }

        switch (fn_walk->id) {
            case FnWalkIdAttrs: {
                LLVMValueRef llvm_fn = fn_walk->data.attrs.llvm_fn;
                bool is_byval = gen_info->is_byval;
                FnTypeParamInfo *param_info = &fn_type->data.fn.fn_type_id.param_info[param_i];

                ZigType *param_type = gen_info->type;
                if (param_info->is_noalias) {
                    addLLVMArgAttr(llvm_fn, (unsigned)gen_index, "noalias");
                }
                if ((param_type->id == ZigTypeIdPointer && param_type->data.pointer.is_const) || is_byval) {
                    addLLVMArgAttr(llvm_fn, (unsigned)gen_index, "readonly");
                }
                if (get_codegen_ptr_type(param_type) != nullptr) {
                    addLLVMArgAttrInt(llvm_fn, (unsigned)gen_index, "align", get_ptr_align(g, param_type));
                }
                if (type_is_nonnull_ptr(param_type)) {
                    addLLVMArgAttr(llvm_fn, (unsigned)gen_index, "nonnull");
                }
                break;
            }
            case FnWalkIdInits: {
                ZigFn *fn_table_entry = fn_walk->data.inits.fn;
                LLVMValueRef llvm_fn = fn_table_entry->llvm_value;
                ZigVar *variable = fn_table_entry->variable_list.at(next_var_i);
                assert(variable->src_arg_index != SIZE_MAX);
                next_var_i += 1;

                assert(variable);
                assert(variable->value_ref);

                if (!handle_is_ptr(variable->var_type) && !fn_is_async(fn_walk->data.inits.fn)) {
                    clear_debug_source_node(g);
                    ZigType *fn_type = fn_table_entry->type_entry;
                    unsigned gen_arg_index = fn_type->data.fn.gen_param_info[variable->src_arg_index].gen_index;
                    gen_store_untyped(g, LLVMGetParam(llvm_fn, gen_arg_index),
                            variable->value_ref, variable->align_bytes, false);
                }

                if (variable->decl_node) {
                    gen_var_debug_decl(g, variable);
                }
                break;
            }
            case FnWalkIdCall:
                // handled before for loop
                zig_unreachable();
            case FnWalkIdTypes:
                // Not called for non-c-abi
                zig_unreachable();
            case FnWalkIdVars:
                // iter_function_params_c_abi is called directly for this one
                zig_unreachable();
        }
    }
}

static LLVMValueRef get_merge_err_ret_traces_fn_val(CodeGen *g) {
    if (g->merge_err_ret_traces_fn_val)
        return g->merge_err_ret_traces_fn_val;

    assert(g->stack_trace_type != nullptr);

    LLVMTypeRef param_types[] = {
        get_llvm_type(g, ptr_to_stack_trace_type(g)),
        get_llvm_type(g, ptr_to_stack_trace_type(g)),
    };
    LLVMTypeRef fn_type_ref = LLVMFunctionType(LLVMVoidType(), param_types, 2, false);

    const char *fn_name = get_mangled_name(g, "__zig_merge_error_return_traces", false);
    LLVMValueRef fn_val = LLVMAddFunction(g->module, fn_name, fn_type_ref);
    LLVMSetLinkage(fn_val, LLVMInternalLinkage);
    ZigLLVMFunctionSetCallingConv(fn_val, get_llvm_cc(g, CallingConventionUnspecified));
    addLLVMFnAttr(fn_val, "nounwind");
    add_uwtable_attr(g, fn_val);
    addLLVMArgAttr(fn_val, (unsigned)0, "noalias");
    addLLVMArgAttr(fn_val, (unsigned)0, "writeonly");

    addLLVMArgAttr(fn_val, (unsigned)1, "noalias");
    addLLVMArgAttr(fn_val, (unsigned)1, "readonly");
    if (g->build_mode == BuildModeDebug) {
        ZigLLVMAddFunctionAttr(fn_val, "no-frame-pointer-elim", "true");
        ZigLLVMAddFunctionAttr(fn_val, "no-frame-pointer-elim-non-leaf", nullptr);
    }

    // this is above the ZigLLVMClearCurrentDebugLocation
    LLVMValueRef add_error_return_trace_addr_fn_val = get_add_error_return_trace_addr_fn(g);

    LLVMBasicBlockRef entry_block = LLVMAppendBasicBlock(fn_val, "Entry");
    LLVMBasicBlockRef prev_block = LLVMGetInsertBlock(g->builder);
    LLVMValueRef prev_debug_location = LLVMGetCurrentDebugLocation(g->builder);
    LLVMPositionBuilderAtEnd(g->builder, entry_block);
    ZigLLVMClearCurrentDebugLocation(g->builder);

    // if (dest_stack_trace == null or src_stack_trace == null) return;
    // var frame_index: usize = undefined;
    // var frames_left: usize = undefined;
    // if (src_stack_trace.index < src_stack_trace.instruction_addresses.len) {
    //     frame_index = 0;
    //     frames_left = src_stack_trace.index;
    //     if (frames_left == 0) return;
    // } else {
    //     frame_index = (src_stack_trace.index + 1) % src_stack_trace.instruction_addresses.len;
    //     frames_left = src_stack_trace.instruction_addresses.len;
    // }
    // while (true) {
    //     __zig_add_err_ret_trace_addr(dest_stack_trace, src_stack_trace.instruction_addresses[frame_index]);
    //     frames_left -= 1;
    //     if (frames_left == 0) return;
    //     frame_index = (frame_index + 1) % src_stack_trace.instruction_addresses.len;
    // }
    LLVMBasicBlockRef return_block = LLVMAppendBasicBlock(fn_val, "Return");
    LLVMBasicBlockRef non_null_block = LLVMAppendBasicBlock(fn_val, "NonNull");

    LLVMValueRef frame_index_ptr = LLVMBuildAlloca(g->builder, g->builtin_types.entry_usize->llvm_type, "frame_index");
    LLVMValueRef frames_left_ptr = LLVMBuildAlloca(g->builder, g->builtin_types.entry_usize->llvm_type, "frames_left");

    LLVMValueRef dest_stack_trace_ptr = LLVMGetParam(fn_val, 0);
    LLVMValueRef src_stack_trace_ptr = LLVMGetParam(fn_val, 1);

    LLVMValueRef null_dest_bit = LLVMBuildICmp(g->builder, LLVMIntEQ, dest_stack_trace_ptr,
            LLVMConstNull(LLVMTypeOf(dest_stack_trace_ptr)), "");
    LLVMValueRef null_src_bit = LLVMBuildICmp(g->builder, LLVMIntEQ, src_stack_trace_ptr,
            LLVMConstNull(LLVMTypeOf(src_stack_trace_ptr)), "");
    LLVMValueRef null_bit = LLVMBuildOr(g->builder, null_dest_bit, null_src_bit, "");
    LLVMBuildCondBr(g->builder, null_bit, return_block, non_null_block);

    LLVMPositionBuilderAtEnd(g->builder, non_null_block);
    size_t src_index_field_index = g->stack_trace_type->data.structure.fields[0]->gen_index;
    size_t src_addresses_field_index = g->stack_trace_type->data.structure.fields[1]->gen_index;
    LLVMValueRef src_index_field_ptr = LLVMBuildStructGEP(g->builder, src_stack_trace_ptr,
            (unsigned)src_index_field_index, "");
    LLVMValueRef src_addresses_field_ptr = LLVMBuildStructGEP(g->builder, src_stack_trace_ptr,
            (unsigned)src_addresses_field_index, "");
    ZigType *slice_type = g->stack_trace_type->data.structure.fields[1]->type_entry;
    size_t ptr_field_index = slice_type->data.structure.fields[slice_ptr_index]->gen_index;
    LLVMValueRef src_ptr_field_ptr = LLVMBuildStructGEP(g->builder, src_addresses_field_ptr, (unsigned)ptr_field_index, "");
    size_t len_field_index = slice_type->data.structure.fields[slice_len_index]->gen_index;
    LLVMValueRef src_len_field_ptr = LLVMBuildStructGEP(g->builder, src_addresses_field_ptr, (unsigned)len_field_index, "");
    LLVMValueRef src_index_val = LLVMBuildLoad(g->builder, src_index_field_ptr, "");
    LLVMValueRef src_ptr_val = LLVMBuildLoad(g->builder, src_ptr_field_ptr, "");
    LLVMValueRef src_len_val = LLVMBuildLoad(g->builder, src_len_field_ptr, "");
    LLVMValueRef no_wrap_bit = LLVMBuildICmp(g->builder, LLVMIntULT, src_index_val, src_len_val, "");
    LLVMBasicBlockRef no_wrap_block = LLVMAppendBasicBlock(fn_val, "NoWrap");
    LLVMBasicBlockRef yes_wrap_block = LLVMAppendBasicBlock(fn_val, "YesWrap");
    LLVMBasicBlockRef loop_block = LLVMAppendBasicBlock(fn_val, "Loop");
    LLVMBuildCondBr(g->builder, no_wrap_bit, no_wrap_block, yes_wrap_block);

    LLVMPositionBuilderAtEnd(g->builder, no_wrap_block);
    LLVMValueRef usize_zero = LLVMConstNull(g->builtin_types.entry_usize->llvm_type);
    LLVMBuildStore(g->builder, usize_zero, frame_index_ptr);
    LLVMBuildStore(g->builder, src_index_val, frames_left_ptr);
    LLVMValueRef frames_left_eq_zero_bit = LLVMBuildICmp(g->builder, LLVMIntEQ, src_index_val, usize_zero, "");
    LLVMBuildCondBr(g->builder, frames_left_eq_zero_bit, return_block, loop_block);

    LLVMPositionBuilderAtEnd(g->builder, yes_wrap_block);
    LLVMValueRef usize_one = LLVMConstInt(g->builtin_types.entry_usize->llvm_type, 1, false);
    LLVMValueRef plus_one = LLVMBuildNUWAdd(g->builder, src_index_val, usize_one, "");
    LLVMValueRef mod_len = LLVMBuildURem(g->builder, plus_one, src_len_val, "");
    LLVMBuildStore(g->builder, mod_len, frame_index_ptr);
    LLVMBuildStore(g->builder, src_len_val, frames_left_ptr);
    LLVMBuildBr(g->builder, loop_block);

    LLVMPositionBuilderAtEnd(g->builder, loop_block);
    LLVMValueRef ptr_index = LLVMBuildLoad(g->builder, frame_index_ptr, "");
    LLVMValueRef addr_ptr = LLVMBuildInBoundsGEP(g->builder, src_ptr_val, &ptr_index, 1, "");
    LLVMValueRef this_addr_val = LLVMBuildLoad(g->builder, addr_ptr, "");
    LLVMValueRef args[] = {dest_stack_trace_ptr, this_addr_val};
    ZigLLVMBuildCall(g->builder, add_error_return_trace_addr_fn_val, args, 2, get_llvm_cc(g, CallingConventionUnspecified), ZigLLVM_CallAttrAlwaysInline, "");
    LLVMValueRef prev_frames_left = LLVMBuildLoad(g->builder, frames_left_ptr, "");
    LLVMValueRef new_frames_left = LLVMBuildNUWSub(g->builder, prev_frames_left, usize_one, "");
    LLVMValueRef done_bit = LLVMBuildICmp(g->builder, LLVMIntEQ, new_frames_left, usize_zero, "");
    LLVMBasicBlockRef continue_block = LLVMAppendBasicBlock(fn_val, "Continue");
    LLVMBuildCondBr(g->builder, done_bit, return_block, continue_block);

    LLVMPositionBuilderAtEnd(g->builder, return_block);
    LLVMBuildRetVoid(g->builder);

    LLVMPositionBuilderAtEnd(g->builder, continue_block);
    LLVMBuildStore(g->builder, new_frames_left, frames_left_ptr);
    LLVMValueRef prev_index = LLVMBuildLoad(g->builder, frame_index_ptr, "");
    LLVMValueRef index_plus_one = LLVMBuildNUWAdd(g->builder, prev_index, usize_one, "");
    LLVMValueRef index_mod_len = LLVMBuildURem(g->builder, index_plus_one, src_len_val, "");
    LLVMBuildStore(g->builder, index_mod_len, frame_index_ptr);
    LLVMBuildBr(g->builder, loop_block);

    LLVMPositionBuilderAtEnd(g->builder, prev_block);
    if (!g->strip_debug_symbols) {
        LLVMSetCurrentDebugLocation(g->builder, prev_debug_location);
    }

    g->merge_err_ret_traces_fn_val = fn_val;
    return fn_val;

}
static LLVMValueRef ir_render_save_err_ret_addr(CodeGen *g, IrExecutable *executable,
        IrInstructionSaveErrRetAddr *save_err_ret_addr_instruction)
{
    assert(g->have_err_ret_tracing);

    LLVMValueRef return_err_fn = get_return_err_fn(g);
    bool is_llvm_alloca;
    LLVMValueRef my_err_trace_val = get_cur_err_ret_trace_val(g, save_err_ret_addr_instruction->base.scope,
            &is_llvm_alloca);
    ZigLLVMBuildCall(g->builder, return_err_fn, &my_err_trace_val, 1,
            get_llvm_cc(g, CallingConventionUnspecified), ZigLLVM_CallAttrAuto, "");

    ZigType *ret_type = g->cur_fn->type_entry->data.fn.fn_type_id.return_type;
    if (fn_is_async(g->cur_fn) && codegen_fn_has_err_ret_tracing_arg(g, ret_type)) {
        LLVMValueRef trace_ptr_ptr = LLVMBuildStructGEP(g->builder, g->cur_frame_ptr,
                frame_index_trace_arg(g, ret_type), "");
        LLVMBuildStore(g->builder, my_err_trace_val, trace_ptr_ptr);
    }

    return nullptr;
}

static void gen_assert_resume_id(CodeGen *g, IrInstruction *source_instr, ResumeId resume_id, PanicMsgId msg_id,
        LLVMBasicBlockRef end_bb)
{
    LLVMTypeRef usize_type_ref = g->builtin_types.entry_usize->llvm_type;

    if (ir_want_runtime_safety(g, source_instr)) {
        // Write a value to the resume index which indicates the function was resumed while not suspended.
        LLVMBuildStore(g->builder, g->cur_bad_not_suspended_index, g->cur_async_resume_index_ptr);
    }

    LLVMBasicBlockRef bad_resume_block = LLVMAppendBasicBlock(g->cur_fn_val, "BadResume");
    if (end_bb == nullptr) end_bb = LLVMAppendBasicBlock(g->cur_fn_val, "OkResume");
    LLVMValueRef expected_value = LLVMConstSub(LLVMConstAllOnes(usize_type_ref),
            LLVMConstInt(usize_type_ref, resume_id, false));
    LLVMValueRef ok_bit = LLVMBuildICmp(g->builder, LLVMIntEQ, LLVMGetParam(g->cur_fn_val, 1), expected_value, "");
    LLVMBuildCondBr(g->builder, ok_bit, end_bb, bad_resume_block);

    LLVMPositionBuilderAtEnd(g->builder, bad_resume_block);
    gen_assertion(g, msg_id, source_instr);

    LLVMPositionBuilderAtEnd(g->builder, end_bb);
}

static LLVMValueRef gen_resume(CodeGen *g, LLVMValueRef fn_val, LLVMValueRef target_frame_ptr, ResumeId resume_id) {
    LLVMTypeRef usize_type_ref = g->builtin_types.entry_usize->llvm_type;
    if (fn_val == nullptr) {
        LLVMValueRef fn_ptr_ptr = LLVMBuildStructGEP(g->builder, target_frame_ptr, frame_fn_ptr_index, "");
        fn_val = LLVMBuildLoad(g->builder, fn_ptr_ptr, "");
    }
    LLVMValueRef arg_val = LLVMConstSub(LLVMConstAllOnes(usize_type_ref),
            LLVMConstInt(usize_type_ref, resume_id, false));
    LLVMValueRef args[] = {target_frame_ptr, arg_val};
    return ZigLLVMBuildCall(g->builder, fn_val, args, 2, ZigLLVM_Fast, ZigLLVM_CallAttrAuto, "");
}

static LLVMBasicBlockRef gen_suspend_begin(CodeGen *g, const char *name_hint) {
    LLVMTypeRef usize_type_ref = g->builtin_types.entry_usize->llvm_type;
    LLVMBasicBlockRef resume_bb = LLVMAppendBasicBlock(g->cur_fn_val, name_hint);
    size_t new_block_index = g->cur_resume_block_count;
    g->cur_resume_block_count += 1;
    LLVMValueRef new_block_index_val = LLVMConstInt(usize_type_ref, new_block_index, false);
    LLVMAddCase(g->cur_async_switch_instr, new_block_index_val, resume_bb);
    LLVMBuildStore(g->builder, new_block_index_val, g->cur_async_resume_index_ptr);
    return resume_bb;
}

// Be careful setting tail call. According to LLVM lang ref,
// tail and musttail imply that the callee does not access allocas from the caller.
// This works for async functions since the locals are spilled.
// http://llvm.org/docs/LangRef.html#id320
static void set_tail_call_if_appropriate(CodeGen *g, LLVMValueRef call_inst) {
    LLVMSetTailCall(call_inst, true);
}

static LLVMValueRef gen_maybe_atomic_op(CodeGen *g, LLVMAtomicRMWBinOp op, LLVMValueRef ptr, LLVMValueRef val,
        LLVMAtomicOrdering order)
{
    if (g->is_single_threaded) {
        LLVMValueRef loaded = LLVMBuildLoad(g->builder, ptr, "");
        LLVMValueRef modified;
        switch (op) {
            case LLVMAtomicRMWBinOpXchg:
                modified = val;
                break;
            case LLVMAtomicRMWBinOpXor:
                modified = LLVMBuildXor(g->builder, loaded, val, "");
                break;
            default:
                zig_unreachable();
        }
        LLVMBuildStore(g->builder, modified, ptr);
        return loaded;
    } else {
        return LLVMBuildAtomicRMW(g->builder, op, ptr, val, order, false);
    }
}

static void gen_async_return(CodeGen *g, IrInstructionReturn *instruction) {
    LLVMTypeRef usize_type_ref = g->builtin_types.entry_usize->llvm_type;

    ZigType *operand_type = (instruction->operand != nullptr) ? instruction->operand->value->type : nullptr;
    bool operand_has_bits = (operand_type != nullptr) && type_has_bits(operand_type);
    ZigType *ret_type = g->cur_fn->type_entry->data.fn.fn_type_id.return_type;
    bool ret_type_has_bits = type_has_bits(ret_type);

    if (operand_has_bits && instruction->operand != nullptr) {
        bool need_store = instruction->operand->value->special != ConstValSpecialRuntime || !handle_is_ptr(ret_type);
        if (need_store) {
            // It didn't get written to the result ptr. We do that now.
            ZigType *ret_ptr_type = get_pointer_to_type(g, ret_type, true);
            gen_assign_raw(g, g->cur_ret_ptr, ret_ptr_type, ir_llvm_value(g, instruction->operand));
        }
    }

    // Whether we tail resume the awaiter, or do an early return, we are done and will not be resumed.
    if (ir_want_runtime_safety(g, &instruction->base)) {
        LLVMValueRef new_resume_index = LLVMConstAllOnes(usize_type_ref);
        LLVMBuildStore(g->builder, new_resume_index, g->cur_async_resume_index_ptr);
    }

    LLVMValueRef zero = LLVMConstNull(usize_type_ref);
    LLVMValueRef all_ones = LLVMConstAllOnes(usize_type_ref);

    LLVMValueRef prev_val = gen_maybe_atomic_op(g, LLVMAtomicRMWBinOpXor, g->cur_async_awaiter_ptr,
            all_ones, LLVMAtomicOrderingAcquire);

    LLVMBasicBlockRef bad_return_block = LLVMAppendBasicBlock(g->cur_fn_val, "BadReturn");
    LLVMBasicBlockRef early_return_block = LLVMAppendBasicBlock(g->cur_fn_val, "EarlyReturn");
    LLVMBasicBlockRef resume_them_block = LLVMAppendBasicBlock(g->cur_fn_val, "ResumeThem");

    LLVMValueRef switch_instr = LLVMBuildSwitch(g->builder, prev_val, resume_them_block, 2);

    LLVMAddCase(switch_instr, zero, early_return_block);
    LLVMAddCase(switch_instr, all_ones, bad_return_block);

    // Something has gone horribly wrong, and this is an invalid second return.
    LLVMPositionBuilderAtEnd(g->builder, bad_return_block);
    gen_assertion(g, PanicMsgIdBadReturn, &instruction->base);

    // There is no awaiter yet, but we're completely done.
    LLVMPositionBuilderAtEnd(g->builder, early_return_block);
    LLVMBuildRetVoid(g->builder);

    // We need to resume the caller by tail calling them,
    // but first write through the result pointer and possibly
    // error return trace pointer.
    LLVMPositionBuilderAtEnd(g->builder, resume_them_block);

    if (ret_type_has_bits) {
        // If the awaiter result pointer is non-null, we need to copy the result to there.
        LLVMBasicBlockRef copy_block = LLVMAppendBasicBlock(g->cur_fn_val, "CopyResult");
        LLVMBasicBlockRef copy_end_block = LLVMAppendBasicBlock(g->cur_fn_val, "CopyResultEnd");
        LLVMValueRef awaiter_ret_ptr_ptr = LLVMBuildStructGEP(g->builder, g->cur_frame_ptr, frame_ret_start + 1, "");
        LLVMValueRef awaiter_ret_ptr = LLVMBuildLoad(g->builder, awaiter_ret_ptr_ptr, "");
        LLVMValueRef zero_ptr = LLVMConstNull(LLVMTypeOf(awaiter_ret_ptr));
        LLVMValueRef need_copy_bit = LLVMBuildICmp(g->builder, LLVMIntNE, awaiter_ret_ptr, zero_ptr, "");
        LLVMBuildCondBr(g->builder, need_copy_bit, copy_block, copy_end_block);

        LLVMPositionBuilderAtEnd(g->builder, copy_block);
        LLVMTypeRef ptr_u8 = LLVMPointerType(LLVMInt8Type(), 0);
        LLVMValueRef dest_ptr_casted = LLVMBuildBitCast(g->builder, awaiter_ret_ptr, ptr_u8, "");
        LLVMValueRef src_ptr_casted = LLVMBuildBitCast(g->builder, g->cur_ret_ptr, ptr_u8, "");
        bool is_volatile = false;
        uint32_t abi_align = get_abi_alignment(g, ret_type);
        LLVMValueRef byte_count_val = LLVMConstInt(usize_type_ref, type_size(g, ret_type), false);
        ZigLLVMBuildMemCpy(g->builder,
                dest_ptr_casted, abi_align,
                src_ptr_casted, abi_align, byte_count_val, is_volatile);
        LLVMBuildBr(g->builder, copy_end_block);

        LLVMPositionBuilderAtEnd(g->builder, copy_end_block);
        if (codegen_fn_has_err_ret_tracing_arg(g, ret_type)) {
            LLVMValueRef awaiter_trace_ptr_ptr = LLVMBuildStructGEP(g->builder, g->cur_frame_ptr,
                    frame_index_trace_arg(g, ret_type) + 1, "");
            LLVMValueRef dest_trace_ptr = LLVMBuildLoad(g->builder, awaiter_trace_ptr_ptr, "");
            bool is_llvm_alloca;
            LLVMValueRef my_err_trace_val = get_cur_err_ret_trace_val(g, instruction->base.scope, &is_llvm_alloca);
            LLVMValueRef args[] = { dest_trace_ptr, my_err_trace_val };
            ZigLLVMBuildCall(g->builder, get_merge_err_ret_traces_fn_val(g), args, 2,
                    get_llvm_cc(g, CallingConventionUnspecified), ZigLLVM_CallAttrAuto, "");
        }
    }

    // Resume the caller by tail calling them.
    ZigType *any_frame_type = get_any_frame_type(g, ret_type);
    LLVMValueRef their_frame_ptr = LLVMBuildIntToPtr(g->builder, prev_val, get_llvm_type(g, any_frame_type), "");
    LLVMValueRef call_inst = gen_resume(g, nullptr, their_frame_ptr, ResumeIdReturn);
    set_tail_call_if_appropriate(g, call_inst);
    LLVMBuildRetVoid(g->builder);
}

static LLVMValueRef ir_render_return(CodeGen *g, IrExecutable *executable, IrInstructionReturn *instruction) {
    if (fn_is_async(g->cur_fn)) {
        gen_async_return(g, instruction);
        return nullptr;
    }

    if (want_first_arg_sret(g, &g->cur_fn->type_entry->data.fn.fn_type_id)) {
        if (instruction->operand == nullptr) {
            LLVMBuildRetVoid(g->builder);
            return nullptr;
        }
        assert(g->cur_ret_ptr);
        ir_assert(instruction->operand->value->special != ConstValSpecialRuntime, &instruction->base);
        LLVMValueRef value = ir_llvm_value(g, instruction->operand);
        ZigType *return_type = instruction->operand->value->type;
        gen_assign_raw(g, g->cur_ret_ptr, get_pointer_to_type(g, return_type, false), value);
        LLVMBuildRetVoid(g->builder);
    } else if (g->cur_fn->type_entry->data.fn.fn_type_id.cc != CallingConventionAsync &&
            handle_is_ptr(g->cur_fn->type_entry->data.fn.fn_type_id.return_type))
    {
        if (instruction->operand == nullptr) {
            LLVMValueRef by_val_value = gen_load_untyped(g, g->cur_ret_ptr, 0, false, "");
            LLVMBuildRet(g->builder, by_val_value);
        } else {
            LLVMValueRef value = ir_llvm_value(g, instruction->operand);
            LLVMValueRef by_val_value = gen_load_untyped(g, value, 0, false, "");
            LLVMBuildRet(g->builder, by_val_value);
        }
    } else if (instruction->operand == nullptr) {
        LLVMBuildRetVoid(g->builder);
    } else {
        LLVMValueRef value = ir_llvm_value(g, instruction->operand);
        LLVMBuildRet(g->builder, value);
    }
    return nullptr;
}

static LLVMValueRef gen_overflow_shl_op(CodeGen *g, ZigType *type_entry,
        LLVMValueRef val1, LLVMValueRef val2)
{
    // for unsigned left shifting, we do the lossy shift, then logically shift
    // right the same number of bits
    // if the values don't match, we have an overflow
    // for signed left shifting we do the same except arithmetic shift right

    assert(type_entry->id == ZigTypeIdInt);

    LLVMValueRef result = LLVMBuildShl(g->builder, val1, val2, "");
    LLVMValueRef orig_val;
    if (type_entry->data.integral.is_signed) {
        orig_val = LLVMBuildAShr(g->builder, result, val2, "");
    } else {
        orig_val = LLVMBuildLShr(g->builder, result, val2, "");
    }
    LLVMValueRef ok_bit = LLVMBuildICmp(g->builder, LLVMIntEQ, val1, orig_val, "");

    LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "OverflowOk");
    LLVMBasicBlockRef fail_block = LLVMAppendBasicBlock(g->cur_fn_val, "OverflowFail");
    LLVMBuildCondBr(g->builder, ok_bit, ok_block, fail_block);

    LLVMPositionBuilderAtEnd(g->builder, fail_block);
    gen_safety_crash(g, PanicMsgIdShlOverflowedBits);

    LLVMPositionBuilderAtEnd(g->builder, ok_block);
    return result;
}

static LLVMValueRef gen_overflow_shr_op(CodeGen *g, ZigType *type_entry,
        LLVMValueRef val1, LLVMValueRef val2)
{
    assert(type_entry->id == ZigTypeIdInt);

    LLVMValueRef result;
    if (type_entry->data.integral.is_signed) {
        result = LLVMBuildAShr(g->builder, val1, val2, "");
    } else {
        result = LLVMBuildLShr(g->builder, val1, val2, "");
    }
    LLVMValueRef orig_val = LLVMBuildShl(g->builder, result, val2, "");
    LLVMValueRef ok_bit = LLVMBuildICmp(g->builder, LLVMIntEQ, val1, orig_val, "");

    LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "OverflowOk");
    LLVMBasicBlockRef fail_block = LLVMAppendBasicBlock(g->cur_fn_val, "OverflowFail");
    LLVMBuildCondBr(g->builder, ok_bit, ok_block, fail_block);

    LLVMPositionBuilderAtEnd(g->builder, fail_block);
    gen_safety_crash(g, PanicMsgIdShrOverflowedBits);

    LLVMPositionBuilderAtEnd(g->builder, ok_block);
    return result;
}

static LLVMValueRef gen_float_op(CodeGen *g, LLVMValueRef val, ZigType *type_entry, BuiltinFnId op) {
    if ((op == BuiltinFnIdCeil ||
         op == BuiltinFnIdFloor) &&
        type_entry->id == ZigTypeIdInt)
        return val;
    assert(type_entry->id == ZigTypeIdFloat);

    LLVMValueRef floor_fn = get_float_fn(g, type_entry, ZigLLVMFnIdFloatOp, op);
    return LLVMBuildCall(g->builder, floor_fn, &val, 1, "");
}

enum DivKind {
    DivKindFloat,
    DivKindTrunc,
    DivKindFloor,
    DivKindExact,
};

static LLVMValueRef bigint_to_llvm_const(LLVMTypeRef type_ref, BigInt *bigint) {
    if (bigint->digit_count == 0) {
        return LLVMConstNull(type_ref);
    }
    LLVMValueRef unsigned_val;
    if (bigint->digit_count == 1) {
        unsigned_val = LLVMConstInt(type_ref, bigint_ptr(bigint)[0], false);
    } else {
        unsigned_val = LLVMConstIntOfArbitraryPrecision(type_ref, bigint->digit_count, bigint_ptr(bigint));
    }
    if (bigint->is_negative) {
        return LLVMConstNeg(unsigned_val);
    } else {
        return unsigned_val;
    }
}

static LLVMValueRef gen_div(CodeGen *g, bool want_runtime_safety, bool want_fast_math,
        LLVMValueRef val1, LLVMValueRef val2,
        ZigType *type_entry, DivKind div_kind)
{
    ZigLLVMSetFastMath(g->builder, want_fast_math);

    LLVMValueRef zero = LLVMConstNull(get_llvm_type(g, type_entry));
    if (want_runtime_safety && (want_fast_math || type_entry->id != ZigTypeIdFloat)) {
        LLVMValueRef is_zero_bit;
        if (type_entry->id == ZigTypeIdInt) {
            is_zero_bit = LLVMBuildICmp(g->builder, LLVMIntEQ, val2, zero, "");
        } else if (type_entry->id == ZigTypeIdFloat) {
            is_zero_bit = LLVMBuildFCmp(g->builder, LLVMRealOEQ, val2, zero, "");
        } else {
            zig_unreachable();
        }
        LLVMBasicBlockRef div_zero_fail_block = LLVMAppendBasicBlock(g->cur_fn_val, "DivZeroFail");
        LLVMBasicBlockRef div_zero_ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "DivZeroOk");
        LLVMBuildCondBr(g->builder, is_zero_bit, div_zero_fail_block, div_zero_ok_block);

        LLVMPositionBuilderAtEnd(g->builder, div_zero_fail_block);
        gen_safety_crash(g, PanicMsgIdDivisionByZero);

        LLVMPositionBuilderAtEnd(g->builder, div_zero_ok_block);

        if (type_entry->id == ZigTypeIdInt && type_entry->data.integral.is_signed) {
            LLVMValueRef neg_1_value = LLVMConstInt(get_llvm_type(g, type_entry), -1, true);
            BigInt int_min_bi = {0};
            eval_min_max_value_int(g, type_entry, &int_min_bi, false);
            LLVMValueRef int_min_value = bigint_to_llvm_const(get_llvm_type(g, type_entry), &int_min_bi);
            LLVMBasicBlockRef overflow_fail_block = LLVMAppendBasicBlock(g->cur_fn_val, "DivOverflowFail");
            LLVMBasicBlockRef overflow_ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "DivOverflowOk");
            LLVMValueRef num_is_int_min = LLVMBuildICmp(g->builder, LLVMIntEQ, val1, int_min_value, "");
            LLVMValueRef den_is_neg_1 = LLVMBuildICmp(g->builder, LLVMIntEQ, val2, neg_1_value, "");
            LLVMValueRef overflow_fail_bit = LLVMBuildAnd(g->builder, num_is_int_min, den_is_neg_1, "");
            LLVMBuildCondBr(g->builder, overflow_fail_bit, overflow_fail_block, overflow_ok_block);

            LLVMPositionBuilderAtEnd(g->builder, overflow_fail_block);
            gen_safety_crash(g, PanicMsgIdIntegerOverflow);

            LLVMPositionBuilderAtEnd(g->builder, overflow_ok_block);
        }
    }

    if (type_entry->id == ZigTypeIdFloat) {
        LLVMValueRef result = LLVMBuildFDiv(g->builder, val1, val2, "");
        switch (div_kind) {
            case DivKindFloat:
                return result;
            case DivKindExact:
                if (want_runtime_safety) {
                    LLVMValueRef floored = gen_float_op(g, result, type_entry, BuiltinFnIdFloor);
                    LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "DivExactOk");
                    LLVMBasicBlockRef fail_block = LLVMAppendBasicBlock(g->cur_fn_val, "DivExactFail");
                    LLVMValueRef ok_bit = LLVMBuildFCmp(g->builder, LLVMRealOEQ, floored, result, "");

                    LLVMBuildCondBr(g->builder, ok_bit, ok_block, fail_block);

                    LLVMPositionBuilderAtEnd(g->builder, fail_block);
                    gen_safety_crash(g, PanicMsgIdExactDivisionRemainder);

                    LLVMPositionBuilderAtEnd(g->builder, ok_block);
                }
                return result;
            case DivKindTrunc:
                {
                    LLVMBasicBlockRef ltz_block = LLVMAppendBasicBlock(g->cur_fn_val, "DivTruncLTZero");
                    LLVMBasicBlockRef gez_block = LLVMAppendBasicBlock(g->cur_fn_val, "DivTruncGEZero");
                    LLVMBasicBlockRef end_block = LLVMAppendBasicBlock(g->cur_fn_val, "DivTruncEnd");
                    LLVMValueRef ltz = LLVMBuildFCmp(g->builder, LLVMRealOLT, val1, zero, "");
                    LLVMBuildCondBr(g->builder, ltz, ltz_block, gez_block);

                    LLVMPositionBuilderAtEnd(g->builder, ltz_block);
                    LLVMValueRef ceiled = gen_float_op(g, result, type_entry, BuiltinFnIdCeil);
                    LLVMBasicBlockRef ceiled_end_block = LLVMGetInsertBlock(g->builder);
                    LLVMBuildBr(g->builder, end_block);

                    LLVMPositionBuilderAtEnd(g->builder, gez_block);
                    LLVMValueRef floored = gen_float_op(g, result, type_entry, BuiltinFnIdFloor);
                    LLVMBasicBlockRef floored_end_block = LLVMGetInsertBlock(g->builder);
                    LLVMBuildBr(g->builder, end_block);

                    LLVMPositionBuilderAtEnd(g->builder, end_block);
                    LLVMValueRef phi = LLVMBuildPhi(g->builder, get_llvm_type(g, type_entry), "");
                    LLVMValueRef incoming_values[] = { ceiled, floored };
                    LLVMBasicBlockRef incoming_blocks[] = { ceiled_end_block, floored_end_block };
                    LLVMAddIncoming(phi, incoming_values, incoming_blocks, 2);
                    return phi;
                }
            case DivKindFloor:
                return gen_float_op(g, result, type_entry, BuiltinFnIdFloor);
        }
        zig_unreachable();
    }

    assert(type_entry->id == ZigTypeIdInt);

    switch (div_kind) {
        case DivKindFloat:
            zig_unreachable();
        case DivKindTrunc:
            if (type_entry->data.integral.is_signed) {
                return LLVMBuildSDiv(g->builder, val1, val2, "");
            } else {
                return LLVMBuildUDiv(g->builder, val1, val2, "");
            }
        case DivKindExact:
            if (want_runtime_safety) {
                LLVMValueRef remainder_val;
                if (type_entry->data.integral.is_signed) {
                    remainder_val = LLVMBuildSRem(g->builder, val1, val2, "");
                } else {
                    remainder_val = LLVMBuildURem(g->builder, val1, val2, "");
                }
                LLVMValueRef ok_bit = LLVMBuildICmp(g->builder, LLVMIntEQ, remainder_val, zero, "");

                LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "DivExactOk");
                LLVMBasicBlockRef fail_block = LLVMAppendBasicBlock(g->cur_fn_val, "DivExactFail");
                LLVMBuildCondBr(g->builder, ok_bit, ok_block, fail_block);

                LLVMPositionBuilderAtEnd(g->builder, fail_block);
                gen_safety_crash(g, PanicMsgIdExactDivisionRemainder);

                LLVMPositionBuilderAtEnd(g->builder, ok_block);
            }
            if (type_entry->data.integral.is_signed) {
                return LLVMBuildExactSDiv(g->builder, val1, val2, "");
            } else {
                return LLVMBuildExactUDiv(g->builder, val1, val2, "");
            }
        case DivKindFloor:
            {
                if (!type_entry->data.integral.is_signed) {
                    return LLVMBuildUDiv(g->builder, val1, val2, "");
                }
                // const d = @divTrunc(a, b);
                // const r = @rem(a, b);
                // return if (r == 0) d else d - ((a < 0) ^ (b < 0));

                LLVMValueRef div_trunc = LLVMBuildSDiv(g->builder, val1, val2, "");
                LLVMValueRef rem = LLVMBuildSRem(g->builder, val1, val2, "");
                LLVMValueRef rem_eq_0 = LLVMBuildICmp(g->builder, LLVMIntEQ, rem, zero, "");
                LLVMValueRef a_lt_0 = LLVMBuildICmp(g->builder, LLVMIntSLT, val1, zero, "");
                LLVMValueRef b_lt_0 = LLVMBuildICmp(g->builder, LLVMIntSLT, val2, zero, "");
                LLVMValueRef a_b_xor = LLVMBuildXor(g->builder, a_lt_0, b_lt_0, "");
                LLVMValueRef a_b_xor_ext = LLVMBuildZExt(g->builder, a_b_xor, LLVMTypeOf(div_trunc), "");
                LLVMValueRef d_sub_xor = LLVMBuildSub(g->builder, div_trunc, a_b_xor_ext, "");
                return LLVMBuildSelect(g->builder, rem_eq_0, div_trunc, d_sub_xor, "");
            }
    }
    zig_unreachable();
}

enum RemKind {
    RemKindRem,
    RemKindMod,
};

static LLVMValueRef gen_rem(CodeGen *g, bool want_runtime_safety, bool want_fast_math,
        LLVMValueRef val1, LLVMValueRef val2,
        ZigType *type_entry, RemKind rem_kind)
{
    ZigLLVMSetFastMath(g->builder, want_fast_math);

    LLVMValueRef zero = LLVMConstNull(get_llvm_type(g, type_entry));
    if (want_runtime_safety) {
        LLVMValueRef is_zero_bit;
        if (type_entry->id == ZigTypeIdInt) {
            LLVMIntPredicate pred = type_entry->data.integral.is_signed ? LLVMIntSLE : LLVMIntEQ;
            is_zero_bit = LLVMBuildICmp(g->builder, pred, val2, zero, "");
        } else if (type_entry->id == ZigTypeIdFloat) {
            is_zero_bit = LLVMBuildFCmp(g->builder, LLVMRealOEQ, val2, zero, "");
        } else {
            zig_unreachable();
        }
        LLVMBasicBlockRef rem_zero_ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "RemZeroOk");
        LLVMBasicBlockRef rem_zero_fail_block = LLVMAppendBasicBlock(g->cur_fn_val, "RemZeroFail");
        LLVMBuildCondBr(g->builder, is_zero_bit, rem_zero_fail_block, rem_zero_ok_block);

        LLVMPositionBuilderAtEnd(g->builder, rem_zero_fail_block);
        gen_safety_crash(g, PanicMsgIdRemainderDivisionByZero);

        LLVMPositionBuilderAtEnd(g->builder, rem_zero_ok_block);
    }

    if (type_entry->id == ZigTypeIdFloat) {
        if (rem_kind == RemKindRem) {
            return LLVMBuildFRem(g->builder, val1, val2, "");
        } else {
            LLVMValueRef a = LLVMBuildFRem(g->builder, val1, val2, "");
            LLVMValueRef b = LLVMBuildFAdd(g->builder, a, val2, "");
            LLVMValueRef c = LLVMBuildFRem(g->builder, b, val2, "");
            LLVMValueRef ltz = LLVMBuildFCmp(g->builder, LLVMRealOLT, val1, zero, "");
            return LLVMBuildSelect(g->builder, ltz, c, a, "");
        }
    } else {
        assert(type_entry->id == ZigTypeIdInt);
        if (type_entry->data.integral.is_signed) {
            if (rem_kind == RemKindRem) {
                return LLVMBuildSRem(g->builder, val1, val2, "");
            } else {
                LLVMValueRef a = LLVMBuildSRem(g->builder, val1, val2, "");
                LLVMValueRef b = LLVMBuildNSWAdd(g->builder, a, val2, "");
                LLVMValueRef c = LLVMBuildSRem(g->builder, b, val2, "");
                LLVMValueRef ltz = LLVMBuildICmp(g->builder, LLVMIntSLT, val1, zero, "");
                return LLVMBuildSelect(g->builder, ltz, c, a, "");
            }
        } else {
            return LLVMBuildURem(g->builder, val1, val2, "");
        }
    }

}

static LLVMValueRef ir_render_bin_op(CodeGen *g, IrExecutable *executable,
        IrInstructionBinOp *bin_op_instruction)
{
    IrBinOp op_id = bin_op_instruction->op_id;
    IrInstruction *op1 = bin_op_instruction->op1;
    IrInstruction *op2 = bin_op_instruction->op2;

    ZigType *operand_type = op1->value->type;
    ZigType *scalar_type = (operand_type->id == ZigTypeIdVector) ? operand_type->data.vector.elem_type : operand_type;

    bool want_runtime_safety = bin_op_instruction->safety_check_on &&
        ir_want_runtime_safety(g, &bin_op_instruction->base);

    LLVMValueRef op1_value = ir_llvm_value(g, op1);
    LLVMValueRef op2_value = ir_llvm_value(g, op2);


    switch (op_id) {
        case IrBinOpInvalid:
        case IrBinOpArrayCat:
        case IrBinOpArrayMult:
        case IrBinOpRemUnspecified:
            zig_unreachable();
        case IrBinOpBoolOr:
            return LLVMBuildOr(g->builder, op1_value, op2_value, "");
        case IrBinOpBoolAnd:
            return LLVMBuildAnd(g->builder, op1_value, op2_value, "");
        case IrBinOpCmpEq:
        case IrBinOpCmpNotEq:
        case IrBinOpCmpLessThan:
        case IrBinOpCmpGreaterThan:
        case IrBinOpCmpLessOrEq:
        case IrBinOpCmpGreaterOrEq:
            if (scalar_type->id == ZigTypeIdFloat) {
                ZigLLVMSetFastMath(g->builder, ir_want_fast_math(g, &bin_op_instruction->base));
                LLVMRealPredicate pred = cmp_op_to_real_predicate(op_id);
                return LLVMBuildFCmp(g->builder, pred, op1_value, op2_value, "");
            } else if (scalar_type->id == ZigTypeIdInt) {
                LLVMIntPredicate pred = cmp_op_to_int_predicate(op_id, scalar_type->data.integral.is_signed);
                return LLVMBuildICmp(g->builder, pred, op1_value, op2_value, "");
            } else if (scalar_type->id == ZigTypeIdEnum ||
                    scalar_type->id == ZigTypeIdErrorSet ||
                    scalar_type->id == ZigTypeIdBool ||
                    get_codegen_ptr_type(scalar_type) != nullptr)
            {
                LLVMIntPredicate pred = cmp_op_to_int_predicate(op_id, false);
                return LLVMBuildICmp(g->builder, pred, op1_value, op2_value, "");
            } else {
                zig_unreachable();
            }
        case IrBinOpMult:
        case IrBinOpMultWrap:
        case IrBinOpAdd:
        case IrBinOpAddWrap:
        case IrBinOpSub:
        case IrBinOpSubWrap: {
            bool is_wrapping = (op_id == IrBinOpSubWrap || op_id == IrBinOpAddWrap || op_id == IrBinOpMultWrap);
            AddSubMul add_sub_mul =
                op_id == IrBinOpAdd || op_id == IrBinOpAddWrap ? AddSubMulAdd :
                op_id == IrBinOpSub || op_id == IrBinOpSubWrap ? AddSubMulSub :
                AddSubMulMul;

            if (scalar_type->id == ZigTypeIdPointer) {
                LLVMValueRef subscript_value;
                if (operand_type->id == ZigTypeIdVector)
                    zig_panic("TODO: Implement vector operations on pointers.");

                switch (add_sub_mul) {
                    case AddSubMulAdd:
                        subscript_value = op2_value;
                        break;
                    case AddSubMulSub:
                        subscript_value = LLVMBuildNeg(g->builder, op2_value, "");
                        break;
                    case AddSubMulMul:
                        zig_unreachable();
                }

                // TODO runtime safety
                return LLVMBuildInBoundsGEP(g->builder, op1_value, &subscript_value, 1, "");
            } else if (scalar_type->id == ZigTypeIdFloat) {
                ZigLLVMSetFastMath(g->builder, ir_want_fast_math(g, &bin_op_instruction->base));
                return float_op[add_sub_mul](g->builder, op1_value, op2_value, "");
            } else if (scalar_type->id == ZigTypeIdInt) {
                if (is_wrapping) {
                    return wrap_op[add_sub_mul](g->builder, op1_value, op2_value, "");
                } else if (want_runtime_safety) {
                    return gen_overflow_op(g, operand_type, add_sub_mul, op1_value, op2_value);
                } else if (scalar_type->data.integral.is_signed) {
                    return signed_op[add_sub_mul](g->builder, op1_value, op2_value, "");
                } else {
                    return unsigned_op[add_sub_mul](g->builder, op1_value, op2_value, "");
                }
            } else {
                zig_unreachable();
            }
        }
        case IrBinOpBinOr:
            return LLVMBuildOr(g->builder, op1_value, op2_value, "");
        case IrBinOpBinXor:
            return LLVMBuildXor(g->builder, op1_value, op2_value, "");
        case IrBinOpBinAnd:
            return LLVMBuildAnd(g->builder, op1_value, op2_value, "");
        case IrBinOpBitShiftLeftLossy:
        case IrBinOpBitShiftLeftExact:
            {
                assert(scalar_type->id == ZigTypeIdInt);
                LLVMValueRef op2_casted = gen_widen_or_shorten(g, false, op2->value->type, scalar_type, op2_value);
                bool is_sloppy = (op_id == IrBinOpBitShiftLeftLossy);
                if (is_sloppy) {
                    return LLVMBuildShl(g->builder, op1_value, op2_casted, "");
                } else if (want_runtime_safety) {
                    return gen_overflow_shl_op(g, scalar_type, op1_value, op2_casted);
                } else if (scalar_type->data.integral.is_signed) {
                    return ZigLLVMBuildNSWShl(g->builder, op1_value, op2_casted, "");
                } else {
                    return ZigLLVMBuildNUWShl(g->builder, op1_value, op2_casted, "");
                }
            }
        case IrBinOpBitShiftRightLossy:
        case IrBinOpBitShiftRightExact:
            {
                assert(scalar_type->id == ZigTypeIdInt);
                LLVMValueRef op2_casted = gen_widen_or_shorten(g, false, op2->value->type, scalar_type, op2_value);
                bool is_sloppy = (op_id == IrBinOpBitShiftRightLossy);
                if (is_sloppy) {
                    if (scalar_type->data.integral.is_signed) {
                        return LLVMBuildAShr(g->builder, op1_value, op2_casted, "");
                    } else {
                        return LLVMBuildLShr(g->builder, op1_value, op2_casted, "");
                    }
                } else if (want_runtime_safety) {
                    return gen_overflow_shr_op(g, scalar_type, op1_value, op2_casted);
                } else if (scalar_type->data.integral.is_signed) {
                    return ZigLLVMBuildAShrExact(g->builder, op1_value, op2_casted, "");
                } else {
                    return ZigLLVMBuildLShrExact(g->builder, op1_value, op2_casted, "");
                }
            }
        case IrBinOpDivUnspecified:
            return gen_div(g, want_runtime_safety, ir_want_fast_math(g, &bin_op_instruction->base),
                    op1_value, op2_value, scalar_type, DivKindFloat);
        case IrBinOpDivExact:
            return gen_div(g, want_runtime_safety, ir_want_fast_math(g, &bin_op_instruction->base),
                    op1_value, op2_value, scalar_type, DivKindExact);
        case IrBinOpDivTrunc:
            return gen_div(g, want_runtime_safety, ir_want_fast_math(g, &bin_op_instruction->base),
                    op1_value, op2_value, scalar_type, DivKindTrunc);
        case IrBinOpDivFloor:
            return gen_div(g, want_runtime_safety, ir_want_fast_math(g, &bin_op_instruction->base),
                    op1_value, op2_value, scalar_type, DivKindFloor);
        case IrBinOpRemRem:
            return gen_rem(g, want_runtime_safety, ir_want_fast_math(g, &bin_op_instruction->base),
                    op1_value, op2_value, scalar_type, RemKindRem);
        case IrBinOpRemMod:
            return gen_rem(g, want_runtime_safety, ir_want_fast_math(g, &bin_op_instruction->base),
                    op1_value, op2_value, scalar_type, RemKindMod);
    }
    zig_unreachable();
}

static void add_error_range_check(CodeGen *g, ZigType *err_set_type, ZigType *int_type, LLVMValueRef target_val) {
    assert(err_set_type->id == ZigTypeIdErrorSet);

    if (type_is_global_error_set(err_set_type)) {
        LLVMValueRef zero = LLVMConstNull(get_llvm_type(g, int_type));
        LLVMValueRef neq_zero_bit = LLVMBuildICmp(g->builder, LLVMIntNE, target_val, zero, "");
        LLVMValueRef ok_bit;

        BigInt biggest_possible_err_val = {0};
        eval_min_max_value_int(g, int_type, &biggest_possible_err_val, true);

        if (bigint_fits_in_bits(&biggest_possible_err_val, 64, false) &&
            bigint_as_usize(&biggest_possible_err_val) < g->errors_by_index.length)
        {
            ok_bit = neq_zero_bit;
        } else {
            LLVMValueRef error_value_count = LLVMConstInt(get_llvm_type(g, int_type), g->errors_by_index.length, false);
            LLVMValueRef in_bounds_bit = LLVMBuildICmp(g->builder, LLVMIntULT, target_val, error_value_count, "");
            ok_bit = LLVMBuildAnd(g->builder, neq_zero_bit, in_bounds_bit, "");
        }

        LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "IntToErrOk");
        LLVMBasicBlockRef fail_block = LLVMAppendBasicBlock(g->cur_fn_val, "IntToErrFail");

        LLVMBuildCondBr(g->builder, ok_bit, ok_block, fail_block);

        LLVMPositionBuilderAtEnd(g->builder, fail_block);
        gen_safety_crash(g, PanicMsgIdInvalidErrorCode);

        LLVMPositionBuilderAtEnd(g->builder, ok_block);
    } else {
        LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "IntToErrOk");
        LLVMBasicBlockRef fail_block = LLVMAppendBasicBlock(g->cur_fn_val, "IntToErrFail");

        uint32_t err_count = err_set_type->data.error_set.err_count;
        LLVMValueRef switch_instr = LLVMBuildSwitch(g->builder, target_val, fail_block, err_count);
        for (uint32_t i = 0; i < err_count; i += 1) {
            LLVMValueRef case_value = LLVMConstInt(get_llvm_type(g, g->err_tag_type),
                    err_set_type->data.error_set.errors[i]->value, false);
            LLVMAddCase(switch_instr, case_value, ok_block);
        }

        LLVMPositionBuilderAtEnd(g->builder, fail_block);
        gen_safety_crash(g, PanicMsgIdInvalidErrorCode);

        LLVMPositionBuilderAtEnd(g->builder, ok_block);
    }
}

static LLVMValueRef ir_render_resize_slice(CodeGen *g, IrExecutable *executable,
        IrInstructionResizeSlice *instruction)
{
    ZigType *actual_type = instruction->operand->value->type;
    ZigType *wanted_type = instruction->base.value->type;
    LLVMValueRef expr_val = ir_llvm_value(g, instruction->operand);
    assert(expr_val);

    LLVMValueRef result_loc = ir_llvm_value(g, instruction->result_loc);
    assert(wanted_type->id == ZigTypeIdStruct);
    assert(wanted_type->data.structure.special == StructSpecialSlice);
    assert(actual_type->id == ZigTypeIdStruct);
    assert(actual_type->data.structure.special == StructSpecialSlice);

    ZigType *actual_pointer_type = actual_type->data.structure.fields[0]->type_entry;
    ZigType *actual_child_type = actual_pointer_type->data.pointer.child_type;
    ZigType *wanted_pointer_type = wanted_type->data.structure.fields[0]->type_entry;
    ZigType *wanted_child_type = wanted_pointer_type->data.pointer.child_type;


    size_t actual_ptr_index = actual_type->data.structure.fields[slice_ptr_index]->gen_index;
    size_t actual_len_index = actual_type->data.structure.fields[slice_len_index]->gen_index;
    size_t wanted_ptr_index = wanted_type->data.structure.fields[slice_ptr_index]->gen_index;
    size_t wanted_len_index = wanted_type->data.structure.fields[slice_len_index]->gen_index;

    LLVMValueRef src_ptr_ptr = LLVMBuildStructGEP(g->builder, expr_val, (unsigned)actual_ptr_index, "");
    LLVMValueRef src_ptr = gen_load_untyped(g, src_ptr_ptr, 0, false, "");
    LLVMValueRef src_ptr_casted = LLVMBuildBitCast(g->builder, src_ptr,
            get_llvm_type(g, wanted_type->data.structure.fields[0]->type_entry), "");
    LLVMValueRef dest_ptr_ptr = LLVMBuildStructGEP(g->builder, result_loc,
            (unsigned)wanted_ptr_index, "");
    gen_store_untyped(g, src_ptr_casted, dest_ptr_ptr, 0, false);

    LLVMValueRef src_len_ptr = LLVMBuildStructGEP(g->builder, expr_val, (unsigned)actual_len_index, "");
    LLVMValueRef src_len = gen_load_untyped(g, src_len_ptr, 0, false, "");
    uint64_t src_size = type_size(g, actual_child_type);
    uint64_t dest_size = type_size(g, wanted_child_type);

    LLVMValueRef new_len;
    if (dest_size == 1) {
        LLVMValueRef src_size_val = LLVMConstInt(g->builtin_types.entry_usize->llvm_type, src_size, false);
        new_len = LLVMBuildMul(g->builder, src_len, src_size_val, "");
    } else if (src_size == 1) {
        LLVMValueRef dest_size_val = LLVMConstInt(g->builtin_types.entry_usize->llvm_type, dest_size, false);
        if (ir_want_runtime_safety(g, &instruction->base)) {
            LLVMValueRef remainder_val = LLVMBuildURem(g->builder, src_len, dest_size_val, "");
            LLVMValueRef zero = LLVMConstNull(g->builtin_types.entry_usize->llvm_type);
            LLVMValueRef ok_bit = LLVMBuildICmp(g->builder, LLVMIntEQ, remainder_val, zero, "");
            LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "SliceWidenOk");
            LLVMBasicBlockRef fail_block = LLVMAppendBasicBlock(g->cur_fn_val, "SliceWidenFail");
            LLVMBuildCondBr(g->builder, ok_bit, ok_block, fail_block);

            LLVMPositionBuilderAtEnd(g->builder, fail_block);
            gen_safety_crash(g, PanicMsgIdSliceWidenRemainder);

            LLVMPositionBuilderAtEnd(g->builder, ok_block);
        }
        new_len = LLVMBuildExactUDiv(g->builder, src_len, dest_size_val, "");
    } else {
        zig_unreachable();
    }

    LLVMValueRef dest_len_ptr = LLVMBuildStructGEP(g->builder, result_loc, (unsigned)wanted_len_index, "");
    gen_store_untyped(g, new_len, dest_len_ptr, 0, false);

    return result_loc;
}

static LLVMValueRef ir_render_cast(CodeGen *g, IrExecutable *executable,
        IrInstructionCast *cast_instruction)
{
    ZigType *actual_type = cast_instruction->value->value->type;
    ZigType *wanted_type = cast_instruction->base.value->type;
    LLVMValueRef expr_val = ir_llvm_value(g, cast_instruction->value);
    ir_assert(expr_val, &cast_instruction->base);

    switch (cast_instruction->cast_op) {
        case CastOpNoCast:
        case CastOpNumLitToConcrete:
            zig_unreachable();
        case CastOpNoop:
            if (actual_type->id == ZigTypeIdPointer && wanted_type->id == ZigTypeIdPointer &&
                actual_type->data.pointer.child_type->id == ZigTypeIdArray &&
                wanted_type->data.pointer.child_type->id == ZigTypeIdArray)
            {
                return LLVMBuildBitCast(g->builder, expr_val, get_llvm_type(g, wanted_type), "");
            } else {
                return expr_val;
            }
        case CastOpIntToFloat:
            assert(actual_type->id == ZigTypeIdInt);
            if (actual_type->data.integral.is_signed) {
                return LLVMBuildSIToFP(g->builder, expr_val, get_llvm_type(g, wanted_type), "");
            } else {
                return LLVMBuildUIToFP(g->builder, expr_val, get_llvm_type(g, wanted_type), "");
            }
        case CastOpFloatToInt: {
            assert(wanted_type->id == ZigTypeIdInt);
            ZigLLVMSetFastMath(g->builder, ir_want_fast_math(g, &cast_instruction->base));

            bool want_safety = ir_want_runtime_safety(g, &cast_instruction->base);

            LLVMValueRef result;
            if (wanted_type->data.integral.is_signed) {
                result = LLVMBuildFPToSI(g->builder, expr_val, get_llvm_type(g, wanted_type), "");
            } else {
                result = LLVMBuildFPToUI(g->builder, expr_val, get_llvm_type(g, wanted_type), "");
            }

            if (want_safety) {
                LLVMValueRef back_to_float;
                if (wanted_type->data.integral.is_signed) {
                    back_to_float = LLVMBuildSIToFP(g->builder, result, LLVMTypeOf(expr_val), "");
                } else {
                    back_to_float = LLVMBuildUIToFP(g->builder, result, LLVMTypeOf(expr_val), "");
                }
                LLVMValueRef difference = LLVMBuildFSub(g->builder, expr_val, back_to_float, "");
                LLVMValueRef one_pos = LLVMConstReal(LLVMTypeOf(expr_val), 1.0f);
                LLVMValueRef one_neg = LLVMConstReal(LLVMTypeOf(expr_val), -1.0f);
                LLVMValueRef ok_bit_pos = LLVMBuildFCmp(g->builder, LLVMRealOLT, difference, one_pos, "");
                LLVMValueRef ok_bit_neg = LLVMBuildFCmp(g->builder, LLVMRealOGT, difference, one_neg, "");
                LLVMValueRef ok_bit = LLVMBuildAnd(g->builder, ok_bit_pos, ok_bit_neg, "");
                LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "FloatCheckOk");
                LLVMBasicBlockRef bad_block = LLVMAppendBasicBlock(g->cur_fn_val, "FloatCheckFail");
                LLVMBuildCondBr(g->builder, ok_bit, ok_block, bad_block);
                LLVMPositionBuilderAtEnd(g->builder, bad_block);
                gen_safety_crash(g, PanicMsgIdFloatToInt);
                LLVMPositionBuilderAtEnd(g->builder, ok_block);
            }
            return result;
        }
        case CastOpBoolToInt:
            assert(wanted_type->id == ZigTypeIdInt);
            assert(actual_type->id == ZigTypeIdBool);
            return LLVMBuildZExt(g->builder, expr_val, get_llvm_type(g, wanted_type), "");
        case CastOpErrSet:
            if (ir_want_runtime_safety(g, &cast_instruction->base)) {
                add_error_range_check(g, wanted_type, g->err_tag_type, expr_val);
            }
            return expr_val;
        case CastOpBitCast:
            return LLVMBuildBitCast(g->builder, expr_val, get_llvm_type(g, wanted_type), "");
    }
    zig_unreachable();
}

static LLVMValueRef ir_render_ptr_of_array_to_slice(CodeGen *g, IrExecutable *executable,
        IrInstructionPtrOfArrayToSlice *instruction)
{
    ZigType *actual_type = instruction->operand->value->type;
    ZigType *slice_type = instruction->base.value->type;
    ZigType *slice_ptr_type = slice_type->data.structure.fields[slice_ptr_index]->type_entry;
    size_t ptr_index = slice_type->data.structure.fields[slice_ptr_index]->gen_index;
    size_t len_index = slice_type->data.structure.fields[slice_len_index]->gen_index;

    LLVMValueRef result_loc = ir_llvm_value(g, instruction->result_loc);

    assert(actual_type->id == ZigTypeIdPointer);
    ZigType *array_type = actual_type->data.pointer.child_type;
    assert(array_type->id == ZigTypeIdArray);

    if (type_has_bits(actual_type)) {
        LLVMValueRef ptr_field_ptr = LLVMBuildStructGEP(g->builder, result_loc, ptr_index, "");
        LLVMValueRef indices[] = {
            LLVMConstNull(g->builtin_types.entry_usize->llvm_type),
            LLVMConstInt(g->builtin_types.entry_usize->llvm_type, 0, false),
        };
        LLVMValueRef expr_val = ir_llvm_value(g, instruction->operand);
        LLVMValueRef slice_start_ptr = LLVMBuildInBoundsGEP(g->builder, expr_val, indices, 2, "");
        gen_store_untyped(g, slice_start_ptr, ptr_field_ptr, 0, false);
    } else if (ir_want_runtime_safety(g, &instruction->base)) {
        LLVMValueRef ptr_field_ptr = LLVMBuildStructGEP(g->builder, result_loc, ptr_index, "");
        gen_undef_init(g, slice_ptr_type->abi_align, slice_ptr_type, ptr_field_ptr);
    }

    LLVMValueRef len_field_ptr = LLVMBuildStructGEP(g->builder, result_loc, len_index, "");
    LLVMValueRef len_value = LLVMConstInt(g->builtin_types.entry_usize->llvm_type,
            array_type->data.array.len, false);
    gen_store_untyped(g, len_value, len_field_ptr, 0, false);

    return result_loc;
}

static LLVMValueRef ir_render_ptr_cast(CodeGen *g, IrExecutable *executable,
        IrInstructionPtrCastGen *instruction)
{
    ZigType *wanted_type = instruction->base.value->type;
    if (!type_has_bits(wanted_type)) {
        return nullptr;
    }
    LLVMValueRef ptr = ir_llvm_value(g, instruction->ptr);
    LLVMValueRef result_ptr = LLVMBuildBitCast(g->builder, ptr, get_llvm_type(g, wanted_type), "");
    bool want_safety_check = instruction->safety_check_on && ir_want_runtime_safety(g, &instruction->base);
    if (!want_safety_check || ptr_allows_addr_zero(wanted_type))
        return result_ptr;

    LLVMValueRef zero = LLVMConstNull(LLVMTypeOf(result_ptr));
    LLVMValueRef ok_bit = LLVMBuildICmp(g->builder, LLVMIntNE, result_ptr, zero, "");
    LLVMBasicBlockRef fail_block = LLVMAppendBasicBlock(g->cur_fn_val, "PtrCastFail");
    LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "PtrCastOk");
    LLVMBuildCondBr(g->builder, ok_bit, ok_block, fail_block);

    LLVMPositionBuilderAtEnd(g->builder, fail_block);
    gen_safety_crash(g, PanicMsgIdPtrCastNull);

    LLVMPositionBuilderAtEnd(g->builder, ok_block);
    return result_ptr;
}

static LLVMValueRef ir_render_bit_cast(CodeGen *g, IrExecutable *executable,
        IrInstructionBitCastGen *instruction)
{
    ZigType *wanted_type = instruction->base.value->type;
    ZigType *actual_type = instruction->operand->value->type;
    LLVMValueRef value = ir_llvm_value(g, instruction->operand);

    bool wanted_is_ptr = handle_is_ptr(wanted_type);
    bool actual_is_ptr = handle_is_ptr(actual_type);
    if (wanted_is_ptr == actual_is_ptr) {
        // We either bitcast the value directly or bitcast the pointer which does a pointer cast
        LLVMTypeRef wanted_type_ref = wanted_is_ptr ?
            LLVMPointerType(get_llvm_type(g, wanted_type), 0) : get_llvm_type(g, wanted_type);
        return LLVMBuildBitCast(g->builder, value, wanted_type_ref, "");
    } else if (actual_is_ptr) {
        LLVMTypeRef wanted_ptr_type_ref = LLVMPointerType(get_llvm_type(g, wanted_type), 0);
        LLVMValueRef bitcasted_ptr = LLVMBuildBitCast(g->builder, value, wanted_ptr_type_ref, "");
        uint32_t alignment = get_abi_alignment(g, actual_type);
        return gen_load_untyped(g, bitcasted_ptr, alignment, false, "");
    } else {
        zig_unreachable();
    }
}

static LLVMValueRef ir_render_widen_or_shorten(CodeGen *g, IrExecutable *executable,
        IrInstructionWidenOrShorten *instruction)
{
    ZigType *actual_type = instruction->target->value->type;
    // TODO instead of this logic, use the Noop instruction to change the type from
    // enum_tag to the underlying int type
    ZigType *int_type;
    if (actual_type->id == ZigTypeIdEnum) {
        int_type = actual_type->data.enumeration.tag_int_type;
    } else {
        int_type = actual_type;
    }
    LLVMValueRef target_val = ir_llvm_value(g, instruction->target);
    return gen_widen_or_shorten(g, ir_want_runtime_safety(g, &instruction->base), int_type,
            instruction->base.value->type, target_val);
}

static LLVMValueRef ir_render_int_to_ptr(CodeGen *g, IrExecutable *executable, IrInstructionIntToPtr *instruction) {
    ZigType *wanted_type = instruction->base.value->type;
    LLVMValueRef target_val = ir_llvm_value(g, instruction->target);

    if (ir_want_runtime_safety(g, &instruction->base)) {
        ZigType *usize = g->builtin_types.entry_usize;
        LLVMValueRef zero = LLVMConstNull(usize->llvm_type);

        if (!ptr_allows_addr_zero(wanted_type)) {
            LLVMValueRef is_zero_bit = LLVMBuildICmp(g->builder, LLVMIntEQ, target_val, zero, "");
            LLVMBasicBlockRef bad_block = LLVMAppendBasicBlock(g->cur_fn_val, "PtrToIntBad");
            LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "PtrToIntOk");
            LLVMBuildCondBr(g->builder, is_zero_bit, bad_block, ok_block);

            LLVMPositionBuilderAtEnd(g->builder, bad_block);
            gen_safety_crash(g, PanicMsgIdPtrCastNull);

            LLVMPositionBuilderAtEnd(g->builder, ok_block);
        }

        {
            const uint32_t align_bytes = get_ptr_align(g, wanted_type);
            LLVMValueRef alignment_minus_1 = LLVMConstInt(usize->llvm_type, align_bytes - 1, false);
            LLVMValueRef anded_val = LLVMBuildAnd(g->builder, target_val, alignment_minus_1, "");
            LLVMValueRef is_ok_bit = LLVMBuildICmp(g->builder, LLVMIntEQ, anded_val, zero, "");
            LLVMBasicBlockRef bad_block = LLVMAppendBasicBlock(g->cur_fn_val, "PtrToIntAlignBad");
            LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "PtrToIntAlignOk");
            LLVMBuildCondBr(g->builder, is_ok_bit, ok_block, bad_block);

            LLVMPositionBuilderAtEnd(g->builder, bad_block);
            gen_safety_crash(g, PanicMsgIdIncorrectAlignment);

            LLVMPositionBuilderAtEnd(g->builder, ok_block);
        }
    }
    return LLVMBuildIntToPtr(g->builder, target_val, get_llvm_type(g, wanted_type), "");
}

static LLVMValueRef ir_render_ptr_to_int(CodeGen *g, IrExecutable *executable, IrInstructionPtrToInt *instruction) {
    ZigType *wanted_type = instruction->base.value->type;
    LLVMValueRef target_val = ir_llvm_value(g, instruction->target);
    return LLVMBuildPtrToInt(g->builder, target_val, get_llvm_type(g, wanted_type), "");
}

static LLVMValueRef ir_render_int_to_enum(CodeGen *g, IrExecutable *executable, IrInstructionIntToEnum *instruction) {
    ZigType *wanted_type = instruction->base.value->type;
    assert(wanted_type->id == ZigTypeIdEnum);
    ZigType *tag_int_type = wanted_type->data.enumeration.tag_int_type;

    LLVMValueRef target_val = ir_llvm_value(g, instruction->target);
    LLVMValueRef tag_int_value = gen_widen_or_shorten(g, ir_want_runtime_safety(g, &instruction->base),
            instruction->target->value->type, tag_int_type, target_val);

    if (ir_want_runtime_safety(g, &instruction->base) && !wanted_type->data.enumeration.non_exhaustive) {
        LLVMBasicBlockRef bad_value_block = LLVMAppendBasicBlock(g->cur_fn_val, "BadValue");
        LLVMBasicBlockRef ok_value_block = LLVMAppendBasicBlock(g->cur_fn_val, "OkValue");
        size_t field_count = wanted_type->data.enumeration.src_field_count;
        LLVMValueRef switch_instr = LLVMBuildSwitch(g->builder, tag_int_value, bad_value_block, field_count);
        for (size_t field_i = 0; field_i < field_count; field_i += 1) {
            LLVMValueRef this_tag_int_value = bigint_to_llvm_const(get_llvm_type(g, tag_int_type),
                    &wanted_type->data.enumeration.fields[field_i].value);
            LLVMAddCase(switch_instr, this_tag_int_value, ok_value_block);
        }
        LLVMPositionBuilderAtEnd(g->builder, bad_value_block);
        gen_safety_crash(g, PanicMsgIdBadEnumValue);

        LLVMPositionBuilderAtEnd(g->builder, ok_value_block);
    }
    return tag_int_value;
}

static LLVMValueRef ir_render_int_to_err(CodeGen *g, IrExecutable *executable, IrInstructionIntToErr *instruction) {
    ZigType *wanted_type = instruction->base.value->type;
    assert(wanted_type->id == ZigTypeIdErrorSet);

    ZigType *actual_type = instruction->target->value->type;
    assert(actual_type->id == ZigTypeIdInt);
    assert(!actual_type->data.integral.is_signed);

    LLVMValueRef target_val = ir_llvm_value(g, instruction->target);

    if (ir_want_runtime_safety(g, &instruction->base)) {
        add_error_range_check(g, wanted_type, actual_type, target_val);
    }

    return gen_widen_or_shorten(g, false, actual_type, g->err_tag_type, target_val);
}

static LLVMValueRef ir_render_err_to_int(CodeGen *g, IrExecutable *executable, IrInstructionErrToInt *instruction) {
    ZigType *wanted_type = instruction->base.value->type;
    assert(wanted_type->id == ZigTypeIdInt);
    assert(!wanted_type->data.integral.is_signed);

    ZigType *actual_type = instruction->target->value->type;
    LLVMValueRef target_val = ir_llvm_value(g, instruction->target);

    if (actual_type->id == ZigTypeIdErrorSet) {
        return gen_widen_or_shorten(g, ir_want_runtime_safety(g, &instruction->base),
            g->err_tag_type, wanted_type, target_val);
    } else if (actual_type->id == ZigTypeIdErrorUnion) {
        // this should have been a compile time constant
        assert(type_has_bits(actual_type->data.error_union.err_set_type));

        if (!type_has_bits(actual_type->data.error_union.payload_type)) {
            return gen_widen_or_shorten(g, ir_want_runtime_safety(g, &instruction->base),
                g->err_tag_type, wanted_type, target_val);
        } else {
            zig_panic("TODO err to int when error union payload type not void");
        }
    } else {
        zig_unreachable();
    }
}

static LLVMValueRef ir_render_unreachable(CodeGen *g, IrExecutable *executable,
        IrInstructionUnreachable *unreachable_instruction)
{
    if (ir_want_runtime_safety(g, &unreachable_instruction->base)) {
        gen_safety_crash(g, PanicMsgIdUnreachable);
    } else {
        LLVMBuildUnreachable(g->builder);
    }
    return nullptr;
}

static LLVMValueRef ir_render_cond_br(CodeGen *g, IrExecutable *executable,
        IrInstructionCondBr *cond_br_instruction)
{
    LLVMBuildCondBr(g->builder,
            ir_llvm_value(g, cond_br_instruction->condition),
            cond_br_instruction->then_block->llvm_block,
            cond_br_instruction->else_block->llvm_block);
    return nullptr;
}

static LLVMValueRef ir_render_br(CodeGen *g, IrExecutable *executable, IrInstructionBr *br_instruction) {
    LLVMBuildBr(g->builder, br_instruction->dest_block->llvm_block);
    return nullptr;
}

static LLVMValueRef ir_render_un_op(CodeGen *g, IrExecutable *executable, IrInstructionUnOp *un_op_instruction) {
    IrUnOp op_id = un_op_instruction->op_id;
    LLVMValueRef expr = ir_llvm_value(g, un_op_instruction->value);
    ZigType *operand_type = un_op_instruction->value->value->type;
    ZigType *scalar_type = (operand_type->id == ZigTypeIdVector) ? operand_type->data.vector.elem_type : operand_type;

    switch (op_id) {
        case IrUnOpInvalid:
        case IrUnOpOptional:
        case IrUnOpDereference:
            zig_unreachable();
        case IrUnOpNegation:
        case IrUnOpNegationWrap:
            {
                if (scalar_type->id == ZigTypeIdFloat) {
                    ZigLLVMSetFastMath(g->builder, ir_want_fast_math(g, &un_op_instruction->base));
                    return LLVMBuildFNeg(g->builder, expr, "");
                } else if (scalar_type->id == ZigTypeIdInt) {
                    if (op_id == IrUnOpNegationWrap) {
                        return LLVMBuildNeg(g->builder, expr, "");
                    } else if (ir_want_runtime_safety(g, &un_op_instruction->base)) {
                        LLVMValueRef zero = LLVMConstNull(LLVMTypeOf(expr));
                        return gen_overflow_op(g, operand_type, AddSubMulSub, zero, expr);
                    } else if (scalar_type->data.integral.is_signed) {
                        return LLVMBuildNSWNeg(g->builder, expr, "");
                    } else {
                        return LLVMBuildNUWNeg(g->builder, expr, "");
                    }
                } else {
                    zig_unreachable();
                }
            }
        case IrUnOpBinNot:
            return LLVMBuildNot(g->builder, expr, "");
    }

    zig_unreachable();
}

static LLVMValueRef ir_render_bool_not(CodeGen *g, IrExecutable *executable, IrInstructionBoolNot *instruction) {
    LLVMValueRef value = ir_llvm_value(g, instruction->value);
    LLVMValueRef zero = LLVMConstNull(LLVMTypeOf(value));
    return LLVMBuildICmp(g->builder, LLVMIntEQ, value, zero, "");
}

static void render_decl_var(CodeGen *g, ZigVar *var) {
    if (!type_has_bits(var->var_type))
        return;

    var->value_ref = ir_llvm_value(g, var->ptr_instruction);
    gen_var_debug_decl(g, var);
}

static LLVMValueRef ir_render_decl_var(CodeGen *g, IrExecutable *executable, IrInstructionDeclVarGen *instruction) {
    instruction->var->ptr_instruction = instruction->var_ptr;
    render_decl_var(g, instruction->var);
    return nullptr;
}

static LLVMValueRef ir_render_load_ptr(CodeGen *g, IrExecutable *executable,
        IrInstructionLoadPtrGen *instruction)
{
    ZigType *child_type = instruction->base.value->type;
    if (!type_has_bits(child_type))
        return nullptr;

    LLVMValueRef ptr = ir_llvm_value(g, instruction->ptr);
    ZigType *ptr_type = instruction->ptr->value->type;
    assert(ptr_type->id == ZigTypeIdPointer);

    ir_assert(ptr_type->data.pointer.vector_index != VECTOR_INDEX_RUNTIME, &instruction->base);
    if (ptr_type->data.pointer.vector_index != VECTOR_INDEX_NONE) {
        LLVMValueRef index_val = LLVMConstInt(LLVMInt32Type(),
                ptr_type->data.pointer.vector_index, false);
        LLVMValueRef loaded_vector = LLVMBuildLoad(g->builder, ptr, "");
        return LLVMBuildExtractElement(g->builder, loaded_vector, index_val, "");
    }

    uint32_t host_int_bytes = ptr_type->data.pointer.host_int_bytes;
    if (host_int_bytes == 0)
        return get_handle_value(g, ptr, child_type, ptr_type);

    bool big_endian = g->is_big_endian;

    LLVMTypeRef int_ptr_ty = LLVMPointerType(LLVMIntType(host_int_bytes * 8), 0);
    LLVMValueRef int_ptr = LLVMBuildBitCast(g->builder, ptr, int_ptr_ty, "");
    LLVMValueRef containing_int = gen_load(g, int_ptr, ptr_type, "");

    uint32_t host_bit_count = LLVMGetIntTypeWidth(LLVMTypeOf(containing_int));
    assert(host_bit_count == host_int_bytes * 8);
    uint32_t size_in_bits = type_size_bits(g, child_type);

    uint32_t bit_offset = ptr_type->data.pointer.bit_offset_in_host;
    uint32_t shift_amt = big_endian ? host_bit_count - bit_offset - size_in_bits : bit_offset;

    LLVMValueRef shift_amt_val = LLVMConstInt(LLVMTypeOf(containing_int), shift_amt, false);
    LLVMValueRef shifted_value = LLVMBuildLShr(g->builder, containing_int, shift_amt_val, "");

    if (handle_is_ptr(child_type)) {
        LLVMValueRef result_loc = ir_llvm_value(g, instruction->result_loc);
        LLVMTypeRef same_size_int = LLVMIntType(size_in_bits);
        LLVMValueRef truncated_int = LLVMBuildTrunc(g->builder, shifted_value, same_size_int, "");
        LLVMValueRef bitcasted_ptr = LLVMBuildBitCast(g->builder, result_loc,
                                                      LLVMPointerType(same_size_int, 0), "");
        LLVMBuildStore(g->builder, truncated_int, bitcasted_ptr);
        return result_loc;
    }

    if (child_type->id == ZigTypeIdFloat) {
        LLVMTypeRef same_size_int = LLVMIntType(size_in_bits);
        LLVMValueRef truncated_int = LLVMBuildTrunc(g->builder, shifted_value, same_size_int, "");
        return LLVMBuildBitCast(g->builder, truncated_int, get_llvm_type(g, child_type), "");
    }

    return LLVMBuildTrunc(g->builder, shifted_value, get_llvm_type(g, child_type), "");
}

static bool value_is_all_undef_array(CodeGen *g, ZigValue *const_val, size_t len) {
    switch (const_val->data.x_array.special) {
        case ConstArraySpecialUndef:
            return true;
        case ConstArraySpecialBuf:
            return false;
        case ConstArraySpecialNone:
            for (size_t i = 0; i < len; i += 1) {
                if (!value_is_all_undef(g, &const_val->data.x_array.data.s_none.elements[i]))
                    return false;
            }
            return true;
    }
    zig_unreachable();
}

static bool value_is_all_undef(CodeGen *g, ZigValue *const_val) {
    Error err;
    if (const_val->special == ConstValSpecialLazy &&
        (err = ir_resolve_lazy(g, nullptr, const_val)))
        codegen_report_errors_and_exit(g);

    switch (const_val->special) {
        case ConstValSpecialLazy:
            zig_unreachable();
        case ConstValSpecialRuntime:
            return false;
        case ConstValSpecialUndef:
            return true;
        case ConstValSpecialStatic:
            if (const_val->type->id == ZigTypeIdStruct) {
                for (size_t i = 0; i < const_val->type->data.structure.src_field_count; i += 1) {
                    if (!value_is_all_undef(g, const_val->data.x_struct.fields[i]))
                        return false;
                }
                return true;
            } else if (const_val->type->id == ZigTypeIdArray) {
                return value_is_all_undef_array(g, const_val, const_val->type->data.array.len);
            } else if (const_val->type->id == ZigTypeIdVector) {
                return value_is_all_undef_array(g, const_val, const_val->type->data.vector.len);
            } else {
                return false;
            }
    }
    zig_unreachable();
}

static LLVMValueRef gen_valgrind_client_request(CodeGen *g, LLVMValueRef default_value, LLVMValueRef request,
        LLVMValueRef a1, LLVMValueRef a2, LLVMValueRef a3, LLVMValueRef a4, LLVMValueRef a5)
{
    if (!target_has_valgrind_support(g->zig_target)) {
        return default_value;
    }
    LLVMTypeRef usize_type_ref = g->builtin_types.entry_usize->llvm_type;
    bool asm_has_side_effects = true;
    bool asm_is_alignstack = false;
    if (g->zig_target->arch == ZigLLVM_x86_64) {
        if (g->zig_target->os == OsLinux || target_os_is_darwin(g->zig_target->os) || g->zig_target->os == OsSolaris ||
            (g->zig_target->os == OsWindows && g->zig_target->abi != ZigLLVM_MSVC))
        {
            if (g->cur_fn->valgrind_client_request_array == nullptr) {
                LLVMBasicBlockRef prev_block = LLVMGetInsertBlock(g->builder);
                LLVMBasicBlockRef entry_block = LLVMGetEntryBasicBlock(g->cur_fn->llvm_value);
                LLVMValueRef first_inst = LLVMGetFirstInstruction(entry_block);
                LLVMPositionBuilderBefore(g->builder, first_inst);
                LLVMTypeRef array_type_ref = LLVMArrayType(usize_type_ref, 6);
                g->cur_fn->valgrind_client_request_array = LLVMBuildAlloca(g->builder, array_type_ref, "");
                LLVMPositionBuilderAtEnd(g->builder, prev_block);
            }
            LLVMValueRef array_ptr = g->cur_fn->valgrind_client_request_array;
            LLVMValueRef array_elements[] = {request, a1, a2, a3, a4, a5};
            LLVMValueRef zero = LLVMConstInt(usize_type_ref, 0, false);
            for (unsigned i = 0; i < 6; i += 1) {
                LLVMValueRef indexes[] = {
                    zero,
                    LLVMConstInt(usize_type_ref, i, false),
                };
                LLVMValueRef elem_ptr = LLVMBuildInBoundsGEP(g->builder, array_ptr, indexes, 2, "");
                LLVMBuildStore(g->builder, array_elements[i], elem_ptr);
            }

            Buf *asm_template = buf_create_from_str(
                "rolq $$3,  %rdi ; rolq $$13, %rdi\n"
                "rolq $$61, %rdi ; rolq $$51, %rdi\n"
                "xchgq %rbx,%rbx\n"
            );
            Buf *asm_constraints = buf_create_from_str(
                "={rdx},{rax},0,~{cc},~{memory}"
            );
            unsigned input_and_output_count = 2;
            LLVMValueRef array_ptr_as_usize = LLVMBuildPtrToInt(g->builder, array_ptr, usize_type_ref, "");
            LLVMValueRef param_values[] = { array_ptr_as_usize, default_value };
            LLVMTypeRef param_types[] = {usize_type_ref, usize_type_ref};
            LLVMTypeRef function_type = LLVMFunctionType(usize_type_ref, param_types,
                    input_and_output_count, false);
            LLVMValueRef asm_fn = LLVMGetInlineAsm(function_type, buf_ptr(asm_template), buf_len(asm_template),
                    buf_ptr(asm_constraints), buf_len(asm_constraints), asm_has_side_effects, asm_is_alignstack,
                    LLVMInlineAsmDialectATT);
            return LLVMBuildCall(g->builder, asm_fn, param_values, input_and_output_count, "");
        }
    }
    zig_unreachable();
}

static bool want_valgrind_support(CodeGen *g) {
    if (!target_has_valgrind_support(g->zig_target))
        return false;
    switch (g->valgrind_support) {
        case ValgrindSupportDisabled:
            return false;
        case ValgrindSupportEnabled:
            return true;
        case ValgrindSupportAuto:
            return g->build_mode == BuildModeDebug;
    }
    zig_unreachable();
}

static void gen_valgrind_undef(CodeGen *g, LLVMValueRef dest_ptr, LLVMValueRef byte_count) {
    static const uint32_t VG_USERREQ__MAKE_MEM_UNDEFINED = 1296236545;
    ZigType *usize = g->builtin_types.entry_usize;
    LLVMValueRef zero = LLVMConstInt(usize->llvm_type, 0, false);
    LLVMValueRef req = LLVMConstInt(usize->llvm_type, VG_USERREQ__MAKE_MEM_UNDEFINED, false);
    LLVMValueRef ptr_as_usize = LLVMBuildPtrToInt(g->builder, dest_ptr, usize->llvm_type, "");
    gen_valgrind_client_request(g, zero, req, ptr_as_usize, byte_count, zero, zero, zero);
}

static void gen_undef_init(CodeGen *g, uint32_t ptr_align_bytes, ZigType *value_type, LLVMValueRef ptr) {
    assert(type_has_bits(value_type));
    uint64_t size_bytes = LLVMStoreSizeOfType(g->target_data_ref, get_llvm_type(g, value_type));
    assert(size_bytes > 0);
    assert(ptr_align_bytes > 0);
    // memset uninitialized memory to 0xaa
    LLVMTypeRef ptr_u8 = LLVMPointerType(LLVMInt8Type(), 0);
    LLVMValueRef fill_char = LLVMConstInt(LLVMInt8Type(), 0xaa, false);
    LLVMValueRef dest_ptr = LLVMBuildBitCast(g->builder, ptr, ptr_u8, "");
    ZigType *usize = g->builtin_types.entry_usize;
    LLVMValueRef byte_count = LLVMConstInt(usize->llvm_type, size_bytes, false);
    ZigLLVMBuildMemSet(g->builder, dest_ptr, fill_char, byte_count, ptr_align_bytes, false);
    // then tell valgrind that the memory is undefined even though we just memset it
    if (want_valgrind_support(g)) {
        gen_valgrind_undef(g, dest_ptr, byte_count);
    }
}

static LLVMValueRef ir_render_store_ptr(CodeGen *g, IrExecutable *executable, IrInstructionStorePtr *instruction) {
    Error err;

    ZigType *ptr_type = instruction->ptr->value->type;
    assert(ptr_type->id == ZigTypeIdPointer);
    bool ptr_type_has_bits;
    if ((err = type_has_bits2(g, ptr_type, &ptr_type_has_bits)))
        codegen_report_errors_and_exit(g);
    if (!ptr_type_has_bits)
        return nullptr;
    if (instruction->ptr->ref_count == 0) {
        // In this case, this StorePtr instruction should be elided. Something happened like this:
        //     var t = true;
        //     const x = if (t) Num.Two else unreachable;
        // The if condition is a runtime value, so the StorePtr for `x = Num.Two` got generated
        // (this instruction being rendered) but because of `else unreachable` the result ended
        // up being a comptime const value.
        return nullptr;
    }

    bool have_init_expr = !value_is_all_undef(g, instruction->value->value);
    if (have_init_expr) {
        LLVMValueRef ptr = ir_llvm_value(g, instruction->ptr);
        LLVMValueRef value = ir_llvm_value(g, instruction->value);
        gen_assign_raw(g, ptr, ptr_type, value);
    } else if (ir_want_runtime_safety(g, &instruction->base)) {
        gen_undef_init(g, get_ptr_align(g, ptr_type), instruction->value->value->type,
            ir_llvm_value(g, instruction->ptr));
    }
    return nullptr;
}

static LLVMValueRef ir_render_vector_store_elem(CodeGen *g, IrExecutable *executable,
        IrInstructionVectorStoreElem *instruction)
{
    LLVMValueRef vector_ptr = ir_llvm_value(g, instruction->vector_ptr);
    LLVMValueRef index = ir_llvm_value(g, instruction->index);
    LLVMValueRef value = ir_llvm_value(g, instruction->value);

    LLVMValueRef loaded_vector = gen_load(g, vector_ptr, instruction->vector_ptr->value->type, "");
    LLVMValueRef modified_vector = LLVMBuildInsertElement(g->builder, loaded_vector, value, index, "");
    gen_store(g, modified_vector, vector_ptr, instruction->vector_ptr->value->type);
    return nullptr;
}

static LLVMValueRef ir_render_var_ptr(CodeGen *g, IrExecutable *executable, IrInstructionVarPtr *instruction) {
    if (instruction->base.value->special != ConstValSpecialRuntime)
        return ir_llvm_value(g, &instruction->base);
    ZigVar *var = instruction->var;
    if (type_has_bits(var->var_type)) {
        assert(var->value_ref);
        return var->value_ref;
    } else {
        return nullptr;
    }
}

static LLVMValueRef ir_render_return_ptr(CodeGen *g, IrExecutable *executable,
        IrInstructionReturnPtr *instruction)
{
    if (!type_has_bits(instruction->base.value->type))
        return nullptr;
    ir_assert(g->cur_ret_ptr != nullptr, &instruction->base);
    return g->cur_ret_ptr;
}

static LLVMValueRef ir_render_elem_ptr(CodeGen *g, IrExecutable *executable, IrInstructionElemPtr *instruction) {
    LLVMValueRef array_ptr_ptr = ir_llvm_value(g, instruction->array_ptr);
    ZigType *array_ptr_type = instruction->array_ptr->value->type;
    assert(array_ptr_type->id == ZigTypeIdPointer);
    ZigType *array_type = array_ptr_type->data.pointer.child_type;
    LLVMValueRef subscript_value = ir_llvm_value(g, instruction->elem_index);
    assert(subscript_value);

    if (!type_has_bits(array_type))
        return nullptr;

    bool safety_check_on = ir_want_runtime_safety(g, &instruction->base) && instruction->safety_check_on;

    if (array_type->id == ZigTypeIdArray ||
        (array_type->id == ZigTypeIdPointer && array_type->data.pointer.ptr_len == PtrLenSingle))
    {
        LLVMValueRef array_ptr = get_handle_value(g, array_ptr_ptr, array_type, array_ptr_type);
        if (array_type->id == ZigTypeIdPointer) {
            assert(array_type->data.pointer.child_type->id == ZigTypeIdArray);
            array_type = array_type->data.pointer.child_type;
        }
        if (safety_check_on) {
            uint64_t extra_len_from_sentinel = (array_type->data.array.sentinel != nullptr) ? 1 : 0;
            uint64_t full_len = array_type->data.array.len + extra_len_from_sentinel;
            LLVMValueRef end = LLVMConstInt(g->builtin_types.entry_usize->llvm_type, full_len, false);
            add_bounds_check(g, subscript_value, LLVMIntEQ, nullptr, LLVMIntULT, end);
        }
        if (array_ptr_type->data.pointer.host_int_bytes != 0) {
            return array_ptr_ptr;
        }
        ZigType *child_type = array_type->data.array.child_type;
        if (child_type->id == ZigTypeIdStruct &&
            child_type->data.structure.layout == ContainerLayoutPacked)
        {
            ZigType *ptr_type = instruction->base.value->type;
            size_t host_int_bytes = ptr_type->data.pointer.host_int_bytes;
            if (host_int_bytes != 0) {
                uint32_t size_in_bits = type_size_bits(g, ptr_type->data.pointer.child_type);
                LLVMTypeRef ptr_u8_type_ref = LLVMPointerType(LLVMInt8Type(), 0);
                LLVMValueRef u8_array_ptr = LLVMBuildBitCast(g->builder, array_ptr, ptr_u8_type_ref, "");
                assert(size_in_bits % 8 == 0);
                LLVMValueRef elem_size_bytes = LLVMConstInt(g->builtin_types.entry_usize->llvm_type,
                        size_in_bits / 8, false);
                LLVMValueRef byte_offset = LLVMBuildNUWMul(g->builder, subscript_value, elem_size_bytes, "");
                LLVMValueRef indices[] = {
                    byte_offset
                };
                LLVMValueRef elem_byte_ptr = LLVMBuildInBoundsGEP(g->builder, u8_array_ptr, indices, 1, "");
                return LLVMBuildBitCast(g->builder, elem_byte_ptr, LLVMPointerType(get_llvm_type(g, child_type), 0), "");
            }
        }
        LLVMValueRef indices[] = {
            LLVMConstNull(g->builtin_types.entry_usize->llvm_type),
            subscript_value
        };
        return LLVMBuildInBoundsGEP(g->builder, array_ptr, indices, 2, "");
    } else if (array_type->id == ZigTypeIdPointer) {
        LLVMValueRef array_ptr = get_handle_value(g, array_ptr_ptr, array_type, array_ptr_type);
        assert(LLVMGetTypeKind(LLVMTypeOf(array_ptr)) == LLVMPointerTypeKind);
        LLVMValueRef indices[] = {
            subscript_value
        };
        return LLVMBuildInBoundsGEP(g->builder, array_ptr, indices, 1, "");
    } else if (array_type->id == ZigTypeIdStruct) {
        LLVMValueRef array_ptr = get_handle_value(g, array_ptr_ptr, array_type, array_ptr_type);
        assert(array_type->data.structure.special == StructSpecialSlice);

        ZigType *ptr_type = array_type->data.structure.fields[slice_ptr_index]->type_entry;
        if (!type_has_bits(ptr_type)) {
            if (safety_check_on) {
                assert(LLVMGetTypeKind(LLVMTypeOf(array_ptr)) == LLVMIntegerTypeKind);
                add_bounds_check(g, subscript_value, LLVMIntEQ, nullptr, LLVMIntULT, array_ptr);
            }
            return nullptr;
        }

        assert(LLVMGetTypeKind(LLVMTypeOf(array_ptr)) == LLVMPointerTypeKind);
        assert(LLVMGetTypeKind(LLVMGetElementType(LLVMTypeOf(array_ptr))) == LLVMStructTypeKind);

        if (safety_check_on) {
            size_t len_index = array_type->data.structure.fields[slice_len_index]->gen_index;
            assert(len_index != SIZE_MAX);
            LLVMValueRef len_ptr = LLVMBuildStructGEP(g->builder, array_ptr, (unsigned)len_index, "");
            LLVMValueRef len = gen_load_untyped(g, len_ptr, 0, false, "");
            LLVMIntPredicate upper_op = (ptr_type->data.pointer.sentinel != nullptr) ? LLVMIntULE : LLVMIntULT;
            add_bounds_check(g, subscript_value, LLVMIntEQ, nullptr, upper_op, len);
        }

        size_t ptr_index = array_type->data.structure.fields[slice_ptr_index]->gen_index;
        assert(ptr_index != SIZE_MAX);
        LLVMValueRef ptr_ptr = LLVMBuildStructGEP(g->builder, array_ptr, (unsigned)ptr_index, "");
        LLVMValueRef ptr = gen_load_untyped(g, ptr_ptr, 0, false, "");
        return LLVMBuildInBoundsGEP(g->builder, ptr, &subscript_value, 1, "");
    } else if (array_type->id == ZigTypeIdVector) {
        return array_ptr_ptr;
    } else {
        zig_unreachable();
    }
}

static LLVMValueRef get_new_stack_addr(CodeGen *g, LLVMValueRef new_stack) {
    LLVMValueRef ptr_field_ptr = LLVMBuildStructGEP(g->builder, new_stack, (unsigned)slice_ptr_index, "");
    LLVMValueRef len_field_ptr = LLVMBuildStructGEP(g->builder, new_stack, (unsigned)slice_len_index, "");

    LLVMValueRef ptr_value = gen_load_untyped(g, ptr_field_ptr, 0, false, "");
    LLVMValueRef len_value = gen_load_untyped(g, len_field_ptr, 0, false, "");

    LLVMValueRef ptr_addr = LLVMBuildPtrToInt(g->builder, ptr_value, LLVMTypeOf(len_value), "");
    LLVMValueRef end_addr = LLVMBuildNUWAdd(g->builder, ptr_addr, len_value, "");
    const unsigned alignment_factor = ZigLLVMDataLayoutGetStackAlignment(g->target_data_ref);
    LLVMValueRef align_amt = LLVMConstInt(LLVMTypeOf(end_addr), alignment_factor, false);
    LLVMValueRef align_adj = LLVMBuildURem(g->builder, end_addr, align_amt, "");
    return LLVMBuildNUWSub(g->builder, end_addr, align_adj, "");
}

static void gen_set_stack_pointer(CodeGen *g, LLVMValueRef aligned_end_addr) {
    LLVMValueRef write_register_fn_val = get_write_register_fn_val(g);

    if (g->sp_md_node == nullptr) {
        Buf *sp_reg_name = buf_create_from_str(arch_stack_pointer_register_name(g->zig_target->arch));
        LLVMValueRef str_node = LLVMMDString(buf_ptr(sp_reg_name), buf_len(sp_reg_name) + 1);
        g->sp_md_node = LLVMMDNode(&str_node, 1);
    }

    LLVMValueRef params[] = {
        g->sp_md_node,
        aligned_end_addr,
    };

    LLVMBuildCall(g->builder, write_register_fn_val, params, 2, "");
}

static void set_call_instr_sret(CodeGen *g, LLVMValueRef call_instr) {
    unsigned attr_kind_id = LLVMGetEnumAttributeKindForName("sret", 4);
    LLVMAttributeRef sret_attr = LLVMCreateEnumAttribute(LLVMGetGlobalContext(), attr_kind_id, 0);
    LLVMAddCallSiteAttribute(call_instr, 1, sret_attr);
}

static void render_async_spills(CodeGen *g) {
    ZigType *fn_type = g->cur_fn->type_entry;
    ZigType *import = get_scope_import(&g->cur_fn->fndef_scope->base);
    uint32_t async_var_index = frame_index_arg(g, fn_type->data.fn.fn_type_id.return_type);
    for (size_t var_i = 0; var_i < g->cur_fn->variable_list.length; var_i += 1) {
        ZigVar *var = g->cur_fn->variable_list.at(var_i);

        if (!type_has_bits(var->var_type)) {
            continue;
        }
        if (ir_get_var_is_comptime(var))
            continue;
        switch (type_requires_comptime(g, var->var_type)) {
            case ReqCompTimeInvalid:
                zig_unreachable();
            case ReqCompTimeYes:
                continue;
            case ReqCompTimeNo:
                break;
        }
        if (var->src_arg_index == SIZE_MAX) {
            continue;
        }

        var->value_ref = LLVMBuildStructGEP(g->builder, g->cur_frame_ptr, async_var_index, var->name);
        async_var_index += 1;
        if (var->decl_node) {
            var->di_loc_var = ZigLLVMCreateAutoVariable(g->dbuilder, get_di_scope(g, var->parent_scope),
                var->name, import->data.structure.root_struct->di_file,
                (unsigned)(var->decl_node->line + 1),
                get_llvm_di_type(g, var->var_type), !g->strip_debug_symbols, 0);
            gen_var_debug_decl(g, var);
        }
    }

    ZigType *frame_type = g->cur_fn->frame_type->data.frame.locals_struct;

    for (size_t alloca_i = 0; alloca_i < g->cur_fn->alloca_gen_list.length; alloca_i += 1) {
        IrInstructionAllocaGen *instruction = g->cur_fn->alloca_gen_list.at(alloca_i);
        if (instruction->field_index == SIZE_MAX)
            continue;

        size_t gen_index = frame_type->data.structure.fields[instruction->field_index]->gen_index;
        instruction->base.llvm_value = LLVMBuildStructGEP(g->builder, g->cur_frame_ptr, gen_index,
                instruction->name_hint);
    }
}

static void render_async_var_decls(CodeGen *g, Scope *scope) {
    for (;;) {
        switch (scope->id) {
            case ScopeIdCImport:
                zig_unreachable();
            case ScopeIdFnDef:
                return;
            case ScopeIdVarDecl: {
                ZigVar *var = reinterpret_cast<ScopeVarDecl *>(scope)->var;
                if (var->ptr_instruction != nullptr) {
                    render_decl_var(g, var);
                }
                // fallthrough
            }
            case ScopeIdDecls:
            case ScopeIdBlock:
            case ScopeIdDefer:
            case ScopeIdDeferExpr:
            case ScopeIdLoop:
            case ScopeIdSuspend:
            case ScopeIdCompTime:
            case ScopeIdRuntime:
            case ScopeIdTypeOf:
            case ScopeIdExpr:
                scope = scope->parent;
                continue;
        }
    }
}

static LLVMValueRef gen_frame_size(CodeGen *g, LLVMValueRef fn_val) {
    assert(g->need_frame_size_prefix_data);
    LLVMTypeRef usize_llvm_type = g->builtin_types.entry_usize->llvm_type;
    LLVMTypeRef ptr_usize_llvm_type = LLVMPointerType(usize_llvm_type, 0);
    LLVMValueRef casted_fn_val = LLVMBuildBitCast(g->builder, fn_val, ptr_usize_llvm_type, "");
    LLVMValueRef negative_one = LLVMConstInt(LLVMInt32Type(), -1, true);
    LLVMValueRef prefix_ptr = LLVMBuildInBoundsGEP(g->builder, casted_fn_val, &negative_one, 1, "");
    return LLVMBuildLoad(g->builder, prefix_ptr, "");
}

static void gen_init_stack_trace(CodeGen *g, LLVMValueRef trace_field_ptr, LLVMValueRef addrs_field_ptr) {
    LLVMTypeRef usize_type_ref = g->builtin_types.entry_usize->llvm_type;
    LLVMValueRef zero = LLVMConstNull(usize_type_ref);

    LLVMValueRef index_ptr = LLVMBuildStructGEP(g->builder, trace_field_ptr, 0, "");
    LLVMBuildStore(g->builder, zero, index_ptr);

    LLVMValueRef addrs_slice_ptr = LLVMBuildStructGEP(g->builder, trace_field_ptr, 1, "");
    LLVMValueRef addrs_ptr_ptr = LLVMBuildStructGEP(g->builder, addrs_slice_ptr, slice_ptr_index, "");
    LLVMValueRef indices[] = { LLVMConstNull(usize_type_ref), LLVMConstNull(usize_type_ref) };
    LLVMValueRef trace_field_addrs_as_ptr = LLVMBuildInBoundsGEP(g->builder, addrs_field_ptr, indices, 2, "");
    LLVMBuildStore(g->builder, trace_field_addrs_as_ptr, addrs_ptr_ptr);

    LLVMValueRef addrs_len_ptr = LLVMBuildStructGEP(g->builder, addrs_slice_ptr, slice_len_index, "");
    LLVMBuildStore(g->builder, LLVMConstInt(usize_type_ref, stack_trace_ptr_count, false), addrs_len_ptr);
}

static LLVMValueRef ir_render_call(CodeGen *g, IrExecutable *executable, IrInstructionCallGen *instruction) {
    LLVMTypeRef usize_type_ref = g->builtin_types.entry_usize->llvm_type;

    LLVMValueRef fn_val;
    ZigType *fn_type;
    bool callee_is_async;
    if (instruction->fn_entry) {
        fn_val = fn_llvm_value(g, instruction->fn_entry);
        fn_type = instruction->fn_entry->type_entry;
        callee_is_async = fn_is_async(instruction->fn_entry);
    } else {
        assert(instruction->fn_ref);
        fn_val = ir_llvm_value(g, instruction->fn_ref);
        fn_type = instruction->fn_ref->value->type;
        callee_is_async = fn_type->data.fn.fn_type_id.cc == CallingConventionAsync;
    }

    FnTypeId *fn_type_id = &fn_type->data.fn.fn_type_id;

    ZigType *src_return_type = fn_type_id->return_type;
    bool ret_has_bits = type_has_bits(src_return_type);

    CallingConvention cc = fn_type->data.fn.fn_type_id.cc;

    bool first_arg_ret = ret_has_bits && want_first_arg_sret(g, fn_type_id);
    bool prefix_arg_err_ret_stack = codegen_fn_has_err_ret_tracing_arg(g, fn_type_id->return_type);
    bool is_var_args = fn_type_id->is_var_args;
    ZigList<LLVMValueRef> gen_param_values = {};
    ZigList<ZigType *> gen_param_types = {};
    LLVMValueRef result_loc = instruction->result_loc ? ir_llvm_value(g, instruction->result_loc) : nullptr;
    LLVMValueRef zero = LLVMConstNull(usize_type_ref);
    LLVMValueRef frame_result_loc_uncasted = nullptr;
    LLVMValueRef frame_result_loc;
    LLVMValueRef awaiter_init_val;
    LLVMValueRef ret_ptr;
    if (callee_is_async) {
        if (instruction->new_stack == nullptr) {
            if (instruction->modifier == CallModifierAsync) {
                frame_result_loc = result_loc;
            } else {
                ir_assert(instruction->frame_result_loc != nullptr, &instruction->base);
                frame_result_loc_uncasted = ir_llvm_value(g, instruction->frame_result_loc);
                ir_assert(instruction->fn_entry != nullptr, &instruction->base);
                frame_result_loc = LLVMBuildBitCast(g->builder, frame_result_loc_uncasted,
                        LLVMPointerType(get_llvm_type(g, instruction->fn_entry->frame_type), 0), "");
            }
        } else {
            if (instruction->new_stack->value->type->id == ZigTypeIdPointer &&
                instruction->new_stack->value->type->data.pointer.child_type->id == ZigTypeIdFnFrame)
            {
                frame_result_loc = ir_llvm_value(g, instruction->new_stack);
            } else {
                LLVMValueRef frame_slice_ptr = ir_llvm_value(g, instruction->new_stack);
                if (ir_want_runtime_safety(g, &instruction->base)) {
                    LLVMValueRef given_len_ptr = LLVMBuildStructGEP(g->builder, frame_slice_ptr, slice_len_index, "");
                    LLVMValueRef given_frame_len = LLVMBuildLoad(g->builder, given_len_ptr, "");
                    LLVMValueRef actual_frame_len = gen_frame_size(g, fn_val);

                    LLVMBasicBlockRef fail_block = LLVMAppendBasicBlock(g->cur_fn_val, "FrameSizeCheckFail");
                    LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "FrameSizeCheckOk");

                    LLVMValueRef ok_bit = LLVMBuildICmp(g->builder, LLVMIntUGE, given_frame_len, actual_frame_len, "");
                    LLVMBuildCondBr(g->builder, ok_bit, ok_block, fail_block);

                    LLVMPositionBuilderAtEnd(g->builder, fail_block);
                    gen_safety_crash(g, PanicMsgIdFrameTooSmall);

                    LLVMPositionBuilderAtEnd(g->builder, ok_block);
                }
                LLVMValueRef frame_ptr_ptr = LLVMBuildStructGEP(g->builder, frame_slice_ptr, slice_ptr_index, "");
                LLVMValueRef frame_ptr = LLVMBuildLoad(g->builder, frame_ptr_ptr, "");
                if (instruction->fn_entry == nullptr) {
                    ZigType *anyframe_type = get_any_frame_type(g, src_return_type);
                    frame_result_loc = LLVMBuildBitCast(g->builder, frame_ptr, get_llvm_type(g, anyframe_type), "");
                } else {
                    ZigType *ptr_frame_type = get_pointer_to_type(g,
                            get_fn_frame_type(g, instruction->fn_entry), false);
                    frame_result_loc = LLVMBuildBitCast(g->builder, frame_ptr,
                            get_llvm_type(g, ptr_frame_type), "");
                }
            }
        }
        if (instruction->modifier == CallModifierAsync) {
            if (instruction->new_stack == nullptr) {
                awaiter_init_val = zero;

                if (ret_has_bits) {
                    // Use the result location which is inside the frame if this is an async call.
                    ret_ptr = LLVMBuildStructGEP(g->builder, frame_result_loc, frame_ret_start + 2, "");
                }
            } else {
                awaiter_init_val = zero;

                if (ret_has_bits) {
                    if (result_loc != nullptr) {
                        // Use the result location provided to the @asyncCall builtin
                        ret_ptr = result_loc;
                    } else {
                        // no result location provided to @asyncCall - use the one inside the frame.
                        ret_ptr = LLVMBuildStructGEP(g->builder, frame_result_loc, frame_ret_start + 2, "");
                    }
                }
            }

            // even if prefix_arg_err_ret_stack is true, let the async function do its own
            // initialization.
        } else {
            if (instruction->modifier == CallModifierNoAsync && !fn_is_async(g->cur_fn)) {
                // Async function called as a normal function, and calling function is not async.
                // This is allowed because it was called with `noasync` which asserts that it will
                // never suspend.
                awaiter_init_val = zero;
            } else {
                // async function called as a normal function
                awaiter_init_val = LLVMBuildPtrToInt(g->builder, g->cur_frame_ptr, usize_type_ref, ""); // caller's own frame pointer
            }
            if (ret_has_bits) {
                if (result_loc == nullptr) {
                    // return type is a scalar, but we still need a pointer to it. Use the async fn frame.
                    ret_ptr = LLVMBuildStructGEP(g->builder, frame_result_loc, frame_ret_start + 2, "");
                } else {
                    // Use the call instruction's result location.
                    ret_ptr = result_loc;
                }

                // Store a zero in the awaiter's result ptr to indicate we do not need a copy made.
                LLVMValueRef awaiter_ret_ptr = LLVMBuildStructGEP(g->builder, frame_result_loc, frame_ret_start + 1, "");
                LLVMValueRef zero_ptr = LLVMConstNull(LLVMGetElementType(LLVMTypeOf(awaiter_ret_ptr)));
                LLVMBuildStore(g->builder, zero_ptr, awaiter_ret_ptr);
            }

            if (prefix_arg_err_ret_stack) {
                LLVMValueRef err_ret_trace_ptr_ptr = LLVMBuildStructGEP(g->builder, frame_result_loc,
                        frame_index_trace_arg(g, src_return_type) + 1, "");
                bool is_llvm_alloca;
                LLVMValueRef my_err_ret_trace_val = get_cur_err_ret_trace_val(g, instruction->base.scope,
                        &is_llvm_alloca);
                LLVMBuildStore(g->builder, my_err_ret_trace_val, err_ret_trace_ptr_ptr);
            }
        }

        assert(frame_result_loc != nullptr);

        LLVMValueRef fn_ptr_ptr = LLVMBuildStructGEP(g->builder, frame_result_loc, frame_fn_ptr_index, "");
        LLVMValueRef bitcasted_fn_val = LLVMBuildBitCast(g->builder, fn_val,
                LLVMGetElementType(LLVMTypeOf(fn_ptr_ptr)), "");
        LLVMBuildStore(g->builder, bitcasted_fn_val, fn_ptr_ptr);

        LLVMValueRef resume_index_ptr = LLVMBuildStructGEP(g->builder, frame_result_loc, frame_resume_index, "");
        LLVMBuildStore(g->builder, zero, resume_index_ptr);

        LLVMValueRef awaiter_ptr = LLVMBuildStructGEP(g->builder, frame_result_loc, frame_awaiter_index, "");
        LLVMBuildStore(g->builder, awaiter_init_val, awaiter_ptr);

        if (ret_has_bits) {
            LLVMValueRef ret_ptr_ptr = LLVMBuildStructGEP(g->builder, frame_result_loc, frame_ret_start, "");
            LLVMBuildStore(g->builder, ret_ptr, ret_ptr_ptr);
        }
    } else if (instruction->modifier == CallModifierAsync) {
        // Async call of blocking function
        if (instruction->new_stack != nullptr) {
            zig_panic("TODO @asyncCall of non-async function");
        }
        frame_result_loc = result_loc;
        awaiter_init_val = LLVMConstAllOnes(usize_type_ref);

        LLVMValueRef awaiter_ptr = LLVMBuildStructGEP(g->builder, frame_result_loc, frame_awaiter_index, "");
        LLVMBuildStore(g->builder, awaiter_init_val, awaiter_ptr);

        if (ret_has_bits) {
            ret_ptr = LLVMBuildStructGEP(g->builder, frame_result_loc, frame_ret_start + 2, "");
            LLVMValueRef ret_ptr_ptr = LLVMBuildStructGEP(g->builder, frame_result_loc, frame_ret_start, "");
            LLVMBuildStore(g->builder, ret_ptr, ret_ptr_ptr);

            if (first_arg_ret) {
                gen_param_values.append(ret_ptr);
            }
            if (prefix_arg_err_ret_stack) {
                // Set up the callee stack trace pointer pointing into the frame.
                // Then we have to wire up the StackTrace pointers.
                // Await is responsible for merging error return traces.
                uint32_t trace_field_index_start = frame_index_trace_arg(g, src_return_type);
                LLVMValueRef callee_trace_ptr_ptr = LLVMBuildStructGEP(g->builder, frame_result_loc,
                        trace_field_index_start, "");
                LLVMValueRef trace_field_ptr = LLVMBuildStructGEP(g->builder, frame_result_loc,
                        trace_field_index_start + 2, "");
                LLVMValueRef addrs_field_ptr = LLVMBuildStructGEP(g->builder, frame_result_loc,
                        trace_field_index_start + 3, "");

                LLVMBuildStore(g->builder, trace_field_ptr, callee_trace_ptr_ptr);

                gen_init_stack_trace(g, trace_field_ptr, addrs_field_ptr);

                bool is_llvm_alloca;
                gen_param_values.append(get_cur_err_ret_trace_val(g, instruction->base.scope, &is_llvm_alloca));
            }
        }
    } else {
        if (first_arg_ret) {
            gen_param_values.append(result_loc);
        }
        if (prefix_arg_err_ret_stack) {
            bool is_llvm_alloca;
            gen_param_values.append(get_cur_err_ret_trace_val(g, instruction->base.scope, &is_llvm_alloca));
        }
    }
    FnWalk fn_walk = {};
    fn_walk.id = FnWalkIdCall;
    fn_walk.data.call.inst = instruction;
    fn_walk.data.call.is_var_args = is_var_args;
    fn_walk.data.call.gen_param_values = &gen_param_values;
    fn_walk.data.call.gen_param_types = &gen_param_types;
    walk_function_params(g, fn_type, &fn_walk);

    ZigLLVM_CallAttr call_attr;
    switch (instruction->modifier) {
        case CallModifierBuiltin:
        case CallModifierCompileTime:
            zig_unreachable();
        case CallModifierNone:
        case CallModifierNoAsync:
        case CallModifierAsync:
            call_attr = ZigLLVM_CallAttrAuto;
            break;
        case CallModifierNeverTail:
            call_attr = ZigLLVM_CallAttrNeverTail;
            break;
        case CallModifierNeverInline:
            call_attr = ZigLLVM_CallAttrNeverInline;
            break;
        case CallModifierAlwaysTail:
            call_attr = ZigLLVM_CallAttrAlwaysTail;
            break;
        case CallModifierAlwaysInline:
            ir_assert(instruction->fn_entry != nullptr, &instruction->base);
            call_attr = ZigLLVM_CallAttrAlwaysInline;
            break;
    }

    ZigLLVM_CallingConv llvm_cc = get_llvm_cc(g, cc);
    LLVMValueRef result;

    if (callee_is_async) {
        uint32_t arg_start_i = frame_index_arg(g, fn_type->data.fn.fn_type_id.return_type);

        LLVMValueRef casted_frame;
        if (instruction->new_stack != nullptr && instruction->fn_entry == nullptr) {
            // We need the frame type to be a pointer to a struct that includes the args
            size_t field_count = arg_start_i + gen_param_values.length;
            LLVMTypeRef *field_types = allocate_nonzero<LLVMTypeRef>(field_count);
            LLVMGetStructElementTypes(LLVMGetElementType(LLVMTypeOf(frame_result_loc)), field_types);
            assert(LLVMCountStructElementTypes(LLVMGetElementType(LLVMTypeOf(frame_result_loc))) == arg_start_i);
            for (size_t arg_i = 0; arg_i < gen_param_values.length; arg_i += 1) {
                field_types[arg_start_i + arg_i] = LLVMTypeOf(gen_param_values.at(arg_i));
            }
            LLVMTypeRef frame_with_args_type = LLVMStructType(field_types, field_count, false);
            LLVMTypeRef ptr_frame_with_args_type = LLVMPointerType(frame_with_args_type, 0);

            casted_frame = LLVMBuildBitCast(g->builder, frame_result_loc, ptr_frame_with_args_type, "");
        } else {
            casted_frame = frame_result_loc;
        }

        for (size_t arg_i = 0; arg_i < gen_param_values.length; arg_i += 1) {
            LLVMValueRef arg_ptr = LLVMBuildStructGEP(g->builder, casted_frame, arg_start_i + arg_i, "");
            gen_assign_raw(g, arg_ptr, get_pointer_to_type(g, gen_param_types.at(arg_i), true),
                    gen_param_values.at(arg_i));
        }

        if (instruction->modifier == CallModifierAsync) {
            gen_resume(g, fn_val, frame_result_loc, ResumeIdCall);
            if (instruction->new_stack != nullptr) {
                return LLVMBuildBitCast(g->builder, frame_result_loc,
                        get_llvm_type(g, instruction->base.value->type), "");
            }
            return nullptr;
        } else if (instruction->modifier == CallModifierNoAsync && !fn_is_async(g->cur_fn)) {
            gen_resume(g, fn_val, frame_result_loc, ResumeIdCall);

            if (ir_want_runtime_safety(g, &instruction->base)) {
                LLVMValueRef awaiter_ptr = LLVMBuildStructGEP(g->builder, frame_result_loc,
                        frame_awaiter_index, "");
                LLVMValueRef all_ones = LLVMConstAllOnes(usize_type_ref);
                LLVMValueRef prev_val = gen_maybe_atomic_op(g, LLVMAtomicRMWBinOpXchg, awaiter_ptr,
                        all_ones, LLVMAtomicOrderingRelease);
                LLVMValueRef ok_val = LLVMBuildICmp(g->builder, LLVMIntEQ, prev_val, all_ones, "");

                LLVMBasicBlockRef bad_block = LLVMAppendBasicBlock(g->cur_fn_val, "NoAsyncPanic");
                LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "NoAsyncOk");
                LLVMBuildCondBr(g->builder, ok_val, ok_block, bad_block);

                // The async function suspended, but this noasync call asserted it wouldn't.
                LLVMPositionBuilderAtEnd(g->builder, bad_block);
                gen_safety_crash(g, PanicMsgIdBadNoAsyncCall);

                LLVMPositionBuilderAtEnd(g->builder, ok_block);
            }

            ZigType *result_type = instruction->base.value->type;
            ZigType *ptr_result_type = get_pointer_to_type(g, result_type, true);
            return gen_await_early_return(g, &instruction->base, frame_result_loc,
                    result_type, ptr_result_type, result_loc, true);
        } else {
            ZigType *ptr_result_type = get_pointer_to_type(g, src_return_type, true);

            LLVMBasicBlockRef call_bb = gen_suspend_begin(g, "CallResume");

            LLVMValueRef call_inst = gen_resume(g, fn_val, frame_result_loc, ResumeIdCall);
            set_tail_call_if_appropriate(g, call_inst);
            LLVMBuildRetVoid(g->builder);

            LLVMPositionBuilderAtEnd(g->builder, call_bb);
            gen_assert_resume_id(g, &instruction->base, ResumeIdReturn, PanicMsgIdResumedAnAwaitingFn, nullptr);
            render_async_var_decls(g, instruction->base.scope);

            if (!type_has_bits(src_return_type))
                return nullptr;

            if (result_loc != nullptr) {
                if (instruction->result_loc->id == IrInstructionIdReturnPtr) {
                    instruction->base.spill = nullptr;
                    return g->cur_ret_ptr;
                } else {
                    return get_handle_value(g, result_loc, src_return_type, ptr_result_type);
                }
            }

            if (frame_result_loc_uncasted != nullptr && instruction->fn_entry != nullptr) {
                // Instead of a spill, we do the bitcast again. The uncasted LLVM IR instruction will
                // be an Alloca from the entry block, so it does not need to be spilled.
                frame_result_loc = LLVMBuildBitCast(g->builder, frame_result_loc_uncasted,
                        LLVMPointerType(get_llvm_type(g, instruction->fn_entry->frame_type), 0), "");
            }

            LLVMValueRef result_ptr = LLVMBuildStructGEP(g->builder, frame_result_loc, frame_ret_start + 2, "");
            return LLVMBuildLoad(g->builder, result_ptr, "");
        }
    }

    if (instruction->new_stack == nullptr || instruction->is_async_call_builtin) {
        result = ZigLLVMBuildCall(g->builder, fn_val,
                gen_param_values.items, (unsigned)gen_param_values.length, llvm_cc, call_attr, "");
    } else if (instruction->modifier == CallModifierAsync) {
        zig_panic("TODO @asyncCall of non-async function");
    } else {
        LLVMValueRef new_stack_addr = get_new_stack_addr(g, ir_llvm_value(g, instruction->new_stack));
        LLVMValueRef old_stack_ref;
        if (src_return_type->id != ZigTypeIdUnreachable) {
            LLVMValueRef stacksave_fn_val = get_stacksave_fn_val(g);
            old_stack_ref = LLVMBuildCall(g->builder, stacksave_fn_val, nullptr, 0, "");
        }
        gen_set_stack_pointer(g, new_stack_addr);
        result = ZigLLVMBuildCall(g->builder, fn_val,
                gen_param_values.items, (unsigned)gen_param_values.length, llvm_cc, call_attr, "");
        if (src_return_type->id != ZigTypeIdUnreachable) {
            LLVMValueRef stackrestore_fn_val = get_stackrestore_fn_val(g);
            LLVMBuildCall(g->builder, stackrestore_fn_val, &old_stack_ref, 1, "");
        }
    }

    if (src_return_type->id == ZigTypeIdUnreachable) {
        return LLVMBuildUnreachable(g->builder);
    } else if (!ret_has_bits) {
        return nullptr;
    } else if (first_arg_ret) {
        set_call_instr_sret(g, result);
        return result_loc;
    } else if (handle_is_ptr(src_return_type)) {
        LLVMValueRef store_instr = LLVMBuildStore(g->builder, result, result_loc);
        LLVMSetAlignment(store_instr, get_ptr_align(g, instruction->result_loc->value->type));
        return result_loc;
    } else if (!callee_is_async && instruction->modifier == CallModifierAsync) {
        LLVMBuildStore(g->builder, result, ret_ptr);
        return result_loc;
    } else {
        return result;
    }
}

static LLVMValueRef ir_render_struct_field_ptr(CodeGen *g, IrExecutable *executable,
    IrInstructionStructFieldPtr *instruction)
{
    Error err;

    if (instruction->base.value->special != ConstValSpecialRuntime)
        return nullptr;

    LLVMValueRef struct_ptr = ir_llvm_value(g, instruction->struct_ptr);
    // not necessarily a pointer. could be ZigTypeIdStruct
    ZigType *struct_ptr_type = instruction->struct_ptr->value->type;
    TypeStructField *field = instruction->field;

    if (!type_has_bits(field->type_entry))
        return nullptr;

    if (struct_ptr_type->id == ZigTypeIdPointer &&
        struct_ptr_type->data.pointer.host_int_bytes != 0)
    {
        return struct_ptr;
    }

    ZigType *struct_type;
    if (struct_ptr_type->id == ZigTypeIdPointer) {
        if (struct_ptr_type->data.pointer.inferred_struct_field != nullptr) {
            struct_type = struct_ptr_type->data.pointer.inferred_struct_field->inferred_struct_type;
        } else {
            struct_type = struct_ptr_type->data.pointer.child_type;
        }
    } else {
        struct_type = struct_ptr_type;
    }

    if ((err = type_resolve(g, struct_type, ResolveStatusLLVMFull)))
        codegen_report_errors_and_exit(g);

    ir_assert(field->gen_index != SIZE_MAX, &instruction->base);
    LLVMValueRef field_ptr_val = LLVMBuildStructGEP(g->builder, struct_ptr, (unsigned)field->gen_index, "");
    ZigType *res_type = instruction->base.value->type;
    ir_assert(res_type->id == ZigTypeIdPointer, &instruction->base);
    if (res_type->data.pointer.host_int_bytes != 0) {
        // We generate packed structs with get_llvm_type_of_n_bytes, which is
        // u8 for 1 byte or [n]u8 for multiple bytes. But the pointer to the type
        // is supposed to be a pointer to the integer. So we bitcast it here.
        LLVMTypeRef int_elem_type = LLVMIntType(8*res_type->data.pointer.host_int_bytes);
        LLVMTypeRef integer_ptr_type = LLVMPointerType(int_elem_type, 0);
        return LLVMBuildBitCast(g->builder, field_ptr_val, integer_ptr_type, "");
    }
    return field_ptr_val;
}

static LLVMValueRef ir_render_union_field_ptr(CodeGen *g, IrExecutable *executable,
    IrInstructionUnionFieldPtr *instruction)
{
    if (instruction->base.value->special != ConstValSpecialRuntime)
        return nullptr;

    ZigType *union_ptr_type = instruction->union_ptr->value->type;
    assert(union_ptr_type->id == ZigTypeIdPointer);
    ZigType *union_type = union_ptr_type->data.pointer.child_type;
    assert(union_type->id == ZigTypeIdUnion);

    TypeUnionField *field = instruction->field;

    if (!type_has_bits(field->type_entry)) {
        ZigType *tag_type = union_type->data.unionation.tag_type;
        if (!instruction->initializing || !type_has_bits(tag_type))
            return nullptr;

        // The field has no bits but we still have to change the discriminant
        // value here
        LLVMValueRef union_ptr = ir_llvm_value(g, instruction->union_ptr);

        LLVMTypeRef tag_type_ref = get_llvm_type(g, tag_type);
        LLVMValueRef tag_field_ptr = nullptr;
        if (union_type->data.unionation.gen_field_count == 0) {
            assert(union_type->data.unionation.gen_tag_index == SIZE_MAX);
            // The whole union is collapsed into the discriminant
            tag_field_ptr = LLVMBuildBitCast(g->builder, union_ptr,
                LLVMPointerType(tag_type_ref, 0), "");
        } else {
            assert(union_type->data.unionation.gen_tag_index != SIZE_MAX);
            tag_field_ptr = LLVMBuildStructGEP(g->builder, union_ptr,
                union_type->data.unionation.gen_tag_index, "");
        }

        LLVMValueRef tag_value = bigint_to_llvm_const(tag_type_ref,
            &field->enum_field->value);
        assert(tag_field_ptr != nullptr);
        gen_store_untyped(g, tag_value, tag_field_ptr, 0, false);

        return nullptr;
    }

    LLVMValueRef union_ptr = ir_llvm_value(g, instruction->union_ptr);
    LLVMTypeRef field_type_ref = LLVMPointerType(get_llvm_type(g, field->type_entry), 0);

    if (union_type->data.unionation.gen_tag_index == SIZE_MAX) {
        LLVMValueRef union_field_ptr = LLVMBuildStructGEP(g->builder, union_ptr, 0, "");
        LLVMValueRef bitcasted_union_field_ptr = LLVMBuildBitCast(g->builder, union_field_ptr, field_type_ref, "");
        return bitcasted_union_field_ptr;
    }

    if (instruction->initializing) {
        LLVMValueRef tag_field_ptr = LLVMBuildStructGEP(g->builder, union_ptr, union_type->data.unionation.gen_tag_index, "");
        LLVMValueRef tag_value = bigint_to_llvm_const(get_llvm_type(g, union_type->data.unionation.tag_type),
                &field->enum_field->value);
        gen_store_untyped(g, tag_value, tag_field_ptr, 0, false);
    } else if (instruction->safety_check_on && ir_want_runtime_safety(g, &instruction->base)) {
        LLVMValueRef tag_field_ptr = LLVMBuildStructGEP(g->builder, union_ptr, union_type->data.unionation.gen_tag_index, "");
        LLVMValueRef tag_value = gen_load_untyped(g, tag_field_ptr, 0, false, "");


        LLVMValueRef expected_tag_value = bigint_to_llvm_const(get_llvm_type(g, union_type->data.unionation.tag_type),
                &field->enum_field->value);
        LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "UnionCheckOk");
        LLVMBasicBlockRef bad_block = LLVMAppendBasicBlock(g->cur_fn_val, "UnionCheckFail");
        LLVMValueRef ok_val = LLVMBuildICmp(g->builder, LLVMIntEQ, tag_value, expected_tag_value, "");
        LLVMBuildCondBr(g->builder, ok_val, ok_block, bad_block);

        LLVMPositionBuilderAtEnd(g->builder, bad_block);
        gen_safety_crash(g, PanicMsgIdBadUnionField);

        LLVMPositionBuilderAtEnd(g->builder, ok_block);
    }

    LLVMValueRef union_field_ptr = LLVMBuildStructGEP(g->builder, union_ptr,
            union_type->data.unionation.gen_union_index, "");
    LLVMValueRef bitcasted_union_field_ptr = LLVMBuildBitCast(g->builder, union_field_ptr, field_type_ref, "");
    return bitcasted_union_field_ptr;
}

static size_t find_asm_index(CodeGen *g, AstNode *node, AsmToken *tok, Buf *src_template) {
    const char *ptr = buf_ptr(src_template) + tok->start + 2;
    size_t len = tok->end - tok->start - 2;
    size_t result = 0;
    for (size_t i = 0; i < node->data.asm_expr.output_list.length; i += 1, result += 1) {
        AsmOutput *asm_output = node->data.asm_expr.output_list.at(i);
        if (buf_eql_mem(asm_output->asm_symbolic_name, ptr, len)) {
            return result;
        }
    }
    for (size_t i = 0; i < node->data.asm_expr.input_list.length; i += 1, result += 1) {
        AsmInput *asm_input = node->data.asm_expr.input_list.at(i);
        if (buf_eql_mem(asm_input->asm_symbolic_name, ptr, len)) {
            return result;
        }
    }
    return SIZE_MAX;
}

static LLVMValueRef ir_render_asm_gen(CodeGen *g, IrExecutable *executable, IrInstructionAsmGen *instruction) {
    AstNode *asm_node = instruction->base.source_node;
    assert(asm_node->type == NodeTypeAsmExpr);
    AstNodeAsmExpr *asm_expr = &asm_node->data.asm_expr;

    Buf *src_template = instruction->asm_template;

    Buf llvm_template = BUF_INIT;
    buf_resize(&llvm_template, 0);

    for (size_t token_i = 0; token_i < instruction->token_list_len; token_i += 1) {
        AsmToken *asm_token = &instruction->token_list[token_i];
        switch (asm_token->id) {
            case AsmTokenIdTemplate:
                for (size_t offset = asm_token->start; offset < asm_token->end; offset += 1) {
                    uint8_t c = *((uint8_t*)(buf_ptr(src_template) + offset));
                    if (c == '$') {
                        buf_append_str(&llvm_template, "$$");
                    } else {
                        buf_append_char(&llvm_template, c);
                    }
                }
                break;
            case AsmTokenIdPercent:
                buf_append_char(&llvm_template, '%');
                break;
            case AsmTokenIdVar:
                {
                    size_t index = find_asm_index(g, asm_node, asm_token, src_template);
                    assert(index < SIZE_MAX);
                    buf_appendf(&llvm_template, "$%" ZIG_PRI_usize "", index);
                    break;
                }
            case AsmTokenIdUniqueId:
                buf_append_str(&llvm_template, "${:uid}");
                break;
        }
    }

    Buf constraint_buf = BUF_INIT;
    buf_resize(&constraint_buf, 0);

    assert(instruction->return_count == 0 || instruction->return_count == 1);

    size_t total_constraint_count = asm_expr->output_list.length +
                                 asm_expr->input_list.length +
                                 asm_expr->clobber_list.length;
    size_t input_and_output_count = asm_expr->output_list.length +
                                 asm_expr->input_list.length -
                                 instruction->return_count;
    size_t total_index = 0;
    size_t param_index = 0;
    LLVMTypeRef *param_types = allocate<LLVMTypeRef>(input_and_output_count);
    LLVMValueRef *param_values = allocate<LLVMValueRef>(input_and_output_count);
    for (size_t i = 0; i < asm_expr->output_list.length; i += 1, total_index += 1) {
        AsmOutput *asm_output = asm_expr->output_list.at(i);
        bool is_return = (asm_output->return_type != nullptr);
        assert(*buf_ptr(asm_output->constraint) == '=');
        // LLVM uses commas internally to separate different constraints,
        // alternative constraints are achieved with pipes.
        // We still allow the user to use commas in a way that is similar
        // to GCC's inline assembly.
        // http://llvm.org/docs/LangRef.html#constraint-codes
        buf_replace(asm_output->constraint, ',', '|');

        if (is_return) {
            buf_appendf(&constraint_buf, "=%s", buf_ptr(asm_output->constraint) + 1);
        } else {
            buf_appendf(&constraint_buf, "=*%s", buf_ptr(asm_output->constraint) + 1);
        }
        if (total_index + 1 < total_constraint_count) {
            buf_append_char(&constraint_buf, ',');
        }

        if (!is_return) {
            ZigVar *variable = instruction->output_vars[i];
            assert(variable);
            param_types[param_index] = LLVMTypeOf(variable->value_ref);
            param_values[param_index] = variable->value_ref;
            param_index += 1;
        }
    }
    for (size_t i = 0; i < asm_expr->input_list.length; i += 1, total_index += 1, param_index += 1) {
        AsmInput *asm_input = asm_expr->input_list.at(i);
        buf_replace(asm_input->constraint, ',', '|');
        IrInstruction *ir_input = instruction->input_list[i];
        buf_append_buf(&constraint_buf, asm_input->constraint);
        if (total_index + 1 < total_constraint_count) {
            buf_append_char(&constraint_buf, ',');
        }

        ZigType *const type = ir_input->value->type;
        LLVMTypeRef type_ref = get_llvm_type(g, type);
        LLVMValueRef value_ref = ir_llvm_value(g, ir_input);
        // Handle integers of non pot bitsize by widening them.
        if (type->id == ZigTypeIdInt) {
            const size_t bitsize = type->data.integral.bit_count;
            if (bitsize < 8 || !is_power_of_2(bitsize)) {
                const bool is_signed = type->data.integral.is_signed;
                const size_t wider_bitsize = bitsize < 8 ? 8 : round_to_next_power_of_2(bitsize);
                ZigType *const wider_type = get_int_type(g, is_signed, wider_bitsize);
                type_ref = get_llvm_type(g, wider_type);
                value_ref = gen_widen_or_shorten(g, false, type, wider_type, value_ref);
            }
        }

        param_types[param_index] = type_ref;
        param_values[param_index] = value_ref;
    }
    for (size_t i = 0; i < asm_expr->clobber_list.length; i += 1, total_index += 1) {
        Buf *clobber_buf = asm_expr->clobber_list.at(i);
        buf_appendf(&constraint_buf, "~{%s}", buf_ptr(clobber_buf));
        if (total_index + 1 < total_constraint_count) {
            buf_append_char(&constraint_buf, ',');
        }
    }

    LLVMTypeRef ret_type;
    if (instruction->return_count == 0) {
        ret_type = LLVMVoidType();
    } else {
        ret_type = get_llvm_type(g, instruction->base.value->type);
    }
    LLVMTypeRef function_type = LLVMFunctionType(ret_type, param_types, (unsigned)input_and_output_count, false);

    bool is_volatile = instruction->has_side_effects || (asm_expr->output_list.length == 0);
    LLVMValueRef asm_fn = LLVMGetInlineAsm(function_type, buf_ptr(&llvm_template), buf_len(&llvm_template),
            buf_ptr(&constraint_buf), buf_len(&constraint_buf), is_volatile, false, LLVMInlineAsmDialectATT);

    return LLVMBuildCall(g->builder, asm_fn, param_values, (unsigned)input_and_output_count, "");
}

static LLVMValueRef gen_non_null_bit(CodeGen *g, ZigType *maybe_type, LLVMValueRef maybe_handle) {
    assert(maybe_type->id == ZigTypeIdOptional ||
            (maybe_type->id == ZigTypeIdPointer && maybe_type->data.pointer.allow_zero));

    ZigType *child_type = maybe_type->data.maybe.child_type;
    if (!type_has_bits(child_type))
        return maybe_handle;

    bool is_scalar = !handle_is_ptr(maybe_type);
    if (is_scalar)
        return LLVMBuildICmp(g->builder, LLVMIntNE, maybe_handle, LLVMConstNull(get_llvm_type(g, maybe_type)), "");

    LLVMValueRef maybe_field_ptr = LLVMBuildStructGEP(g->builder, maybe_handle, maybe_null_index, "");
    return gen_load_untyped(g, maybe_field_ptr, 0, false, "");
}

static LLVMValueRef ir_render_test_non_null(CodeGen *g, IrExecutable *executable,
    IrInstructionTestNonNull *instruction)
{
    return gen_non_null_bit(g, instruction->value->value->type, ir_llvm_value(g, instruction->value));
}

static LLVMValueRef ir_render_optional_unwrap_ptr(CodeGen *g, IrExecutable *executable,
        IrInstructionOptionalUnwrapPtr *instruction)
{
    if (instruction->base.value->special != ConstValSpecialRuntime)
        return nullptr;

    ZigType *ptr_type = instruction->base_ptr->value->type;
    assert(ptr_type->id == ZigTypeIdPointer);
    ZigType *maybe_type = ptr_type->data.pointer.child_type;
    assert(maybe_type->id == ZigTypeIdOptional);
    ZigType *child_type = maybe_type->data.maybe.child_type;
    LLVMValueRef base_ptr = ir_llvm_value(g, instruction->base_ptr);
    if (instruction->safety_check_on && ir_want_runtime_safety(g, &instruction->base)) {
        LLVMValueRef maybe_handle = get_handle_value(g, base_ptr, maybe_type, ptr_type);
        LLVMValueRef non_null_bit = gen_non_null_bit(g, maybe_type, maybe_handle);
        LLVMBasicBlockRef fail_block = LLVMAppendBasicBlock(g->cur_fn_val, "UnwrapOptionalFail");
        LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "UnwrapOptionalOk");
        LLVMBuildCondBr(g->builder, non_null_bit, ok_block, fail_block);

        LLVMPositionBuilderAtEnd(g->builder, fail_block);
        gen_safety_crash(g, PanicMsgIdUnwrapOptionalFail);

        LLVMPositionBuilderAtEnd(g->builder, ok_block);
    }
    if (!type_has_bits(child_type)) {
        return nullptr;
    } else {
        bool is_scalar = !handle_is_ptr(maybe_type);
        if (is_scalar) {
            return base_ptr;
        } else {
            LLVMValueRef optional_struct_ref = get_handle_value(g, base_ptr, maybe_type, ptr_type);
            if (instruction->initializing) {
                LLVMValueRef non_null_bit_ptr = LLVMBuildStructGEP(g->builder, optional_struct_ref,
                        maybe_null_index, "");
                LLVMValueRef non_null_bit = LLVMConstInt(LLVMInt1Type(), 1, false);
                gen_store_untyped(g, non_null_bit, non_null_bit_ptr, 0, false);
            }
            return LLVMBuildStructGEP(g->builder, optional_struct_ref, maybe_child_index, "");
        }
    }
}

static LLVMValueRef get_int_builtin_fn(CodeGen *g, ZigType *expr_type, BuiltinFnId fn_id) {
    bool is_vector = expr_type->id == ZigTypeIdVector;
    ZigType *int_type = is_vector ? expr_type->data.vector.elem_type : expr_type;
    assert(int_type->id == ZigTypeIdInt);
    uint32_t vector_len = is_vector ? expr_type->data.vector.len : 0;
    ZigLLVMFnKey key = {};
    const char *fn_name;
    uint32_t n_args;
    if (fn_id == BuiltinFnIdCtz) {
        fn_name = "cttz";
        n_args = 2;
        key.id = ZigLLVMFnIdCtz;
        key.data.ctz.bit_count = (uint32_t)int_type->data.integral.bit_count;
    } else if (fn_id == BuiltinFnIdClz) {
        fn_name = "ctlz";
        n_args = 2;
        key.id = ZigLLVMFnIdClz;
        key.data.clz.bit_count = (uint32_t)int_type->data.integral.bit_count;
    } else if (fn_id == BuiltinFnIdPopCount) {
        fn_name = "ctpop";
        n_args = 1;
        key.id = ZigLLVMFnIdPopCount;
        key.data.pop_count.bit_count = (uint32_t)int_type->data.integral.bit_count;
    } else if (fn_id == BuiltinFnIdBswap) {
        fn_name = "bswap";
        n_args = 1;
        key.id = ZigLLVMFnIdBswap;
        key.data.bswap.bit_count = (uint32_t)int_type->data.integral.bit_count;
        key.data.bswap.vector_len = vector_len;
    } else if (fn_id == BuiltinFnIdBitReverse) {
        fn_name = "bitreverse";
        n_args = 1;
        key.id = ZigLLVMFnIdBitReverse;
        key.data.bit_reverse.bit_count = (uint32_t)int_type->data.integral.bit_count;
    } else {
        zig_unreachable();
    }

    auto existing_entry = g->llvm_fn_table.maybe_get(key);
    if (existing_entry)
        return existing_entry->value;

    char llvm_name[64];
    if (is_vector)
        sprintf(llvm_name, "llvm.%s.v%" PRIu32 "i%" PRIu32, fn_name, vector_len, int_type->data.integral.bit_count);
    else
        sprintf(llvm_name, "llvm.%s.i%" PRIu32, fn_name, int_type->data.integral.bit_count);
    LLVMTypeRef param_types[] = {
        get_llvm_type(g, expr_type),
        LLVMInt1Type(),
    };
    LLVMTypeRef fn_type = LLVMFunctionType(get_llvm_type(g, expr_type), param_types, n_args, false);
    LLVMValueRef fn_val = LLVMAddFunction(g->module, llvm_name, fn_type);
    assert(LLVMGetIntrinsicID(fn_val));

    g->llvm_fn_table.put(key, fn_val);

    return fn_val;
}

static LLVMValueRef ir_render_clz(CodeGen *g, IrExecutable *executable, IrInstructionClz *instruction) {
    ZigType *int_type = instruction->op->value->type;
    LLVMValueRef fn_val = get_int_builtin_fn(g, int_type, BuiltinFnIdClz);
    LLVMValueRef operand = ir_llvm_value(g, instruction->op);
    LLVMValueRef params[] {
        operand,
        LLVMConstNull(LLVMInt1Type()),
    };
    LLVMValueRef wrong_size_int = LLVMBuildCall(g->builder, fn_val, params, 2, "");
    return gen_widen_or_shorten(g, false, int_type, instruction->base.value->type, wrong_size_int);
}

static LLVMValueRef ir_render_ctz(CodeGen *g, IrExecutable *executable, IrInstructionCtz *instruction) {
    ZigType *int_type = instruction->op->value->type;
    LLVMValueRef fn_val = get_int_builtin_fn(g, int_type, BuiltinFnIdCtz);
    LLVMValueRef operand = ir_llvm_value(g, instruction->op);
    LLVMValueRef params[] {
        operand,
        LLVMConstNull(LLVMInt1Type()),
    };
    LLVMValueRef wrong_size_int = LLVMBuildCall(g->builder, fn_val, params, 2, "");
    return gen_widen_or_shorten(g, false, int_type, instruction->base.value->type, wrong_size_int);
}

static LLVMValueRef ir_render_shuffle_vector(CodeGen *g, IrExecutable *executable, IrInstructionShuffleVector *instruction) {
    uint64_t len_a = instruction->a->value->type->data.vector.len;
    uint64_t len_mask = instruction->mask->value->type->data.vector.len;

    // LLVM uses integers larger than the length of the first array to
    // index into the second array. This was deemed unnecessarily fragile
    // when changing code, so Zig uses negative numbers to index the
    // second vector. These start at -1 and go down, and are easiest to use
    // with the ~ operator. Here we convert between the two formats.
    IrInstruction *mask = instruction->mask;
    LLVMValueRef *values = allocate<LLVMValueRef>(len_mask);
    for (uint64_t i = 0; i < len_mask; i++) {
        if (mask->value->data.x_array.data.s_none.elements[i].special == ConstValSpecialUndef) {
            values[i] = LLVMGetUndef(LLVMInt32Type());
        } else {
            int32_t v = bigint_as_signed(&mask->value->data.x_array.data.s_none.elements[i].data.x_bigint);
            uint32_t index_val = (v >= 0) ? (uint32_t)v : (uint32_t)~v + (uint32_t)len_a;
            values[i] = LLVMConstInt(LLVMInt32Type(), index_val, false);
        }
    }

    LLVMValueRef llvm_mask_value = LLVMConstVector(values, len_mask);
    free(values);

    return LLVMBuildShuffleVector(g->builder,
        ir_llvm_value(g, instruction->a),
        ir_llvm_value(g, instruction->b),
        llvm_mask_value, "");
}

static LLVMValueRef ir_render_splat(CodeGen *g, IrExecutable *executable, IrInstructionSplatGen *instruction) {
    ZigType *result_type = instruction->base.value->type;
    ir_assert(result_type->id == ZigTypeIdVector, &instruction->base);
    uint32_t len = result_type->data.vector.len;
    LLVMTypeRef op_llvm_type = LLVMVectorType(get_llvm_type(g, instruction->scalar->value->type), 1);
    LLVMTypeRef mask_llvm_type = LLVMVectorType(LLVMInt32Type(), len);
    LLVMValueRef undef_vector = LLVMGetUndef(op_llvm_type);
    LLVMValueRef op_vector = LLVMBuildInsertElement(g->builder, undef_vector,
            ir_llvm_value(g, instruction->scalar), LLVMConstInt(LLVMInt32Type(), 0, false), "");
    return LLVMBuildShuffleVector(g->builder, op_vector, undef_vector, LLVMConstNull(mask_llvm_type), "");
}

static LLVMValueRef ir_render_pop_count(CodeGen *g, IrExecutable *executable, IrInstructionPopCount *instruction) {
    ZigType *int_type = instruction->op->value->type;
    LLVMValueRef fn_val = get_int_builtin_fn(g, int_type, BuiltinFnIdPopCount);
    LLVMValueRef operand = ir_llvm_value(g, instruction->op);
    LLVMValueRef wrong_size_int = LLVMBuildCall(g->builder, fn_val, &operand, 1, "");
    return gen_widen_or_shorten(g, false, int_type, instruction->base.value->type, wrong_size_int);
}

static LLVMValueRef ir_render_switch_br(CodeGen *g, IrExecutable *executable, IrInstructionSwitchBr *instruction) {
    ZigType *target_type = instruction->target_value->value->type;
    LLVMBasicBlockRef else_block = instruction->else_block->llvm_block;

    LLVMValueRef target_value = ir_llvm_value(g, instruction->target_value);
    if (target_type->id == ZigTypeIdPointer) {
        const ZigType *usize = g->builtin_types.entry_usize;
        target_value = LLVMBuildPtrToInt(g->builder, target_value, usize->llvm_type, "");
    }

    LLVMValueRef switch_instr = LLVMBuildSwitch(g->builder, target_value, else_block,
                                                (unsigned)instruction->case_count);

    for (size_t i = 0; i < instruction->case_count; i += 1) {
        IrInstructionSwitchBrCase *this_case = &instruction->cases[i];

        LLVMValueRef case_value = ir_llvm_value(g, this_case->value);
        if (target_type->id == ZigTypeIdPointer) {
            const ZigType *usize = g->builtin_types.entry_usize;
            case_value = LLVMBuildPtrToInt(g->builder, case_value, usize->llvm_type, "");
        }

        LLVMAddCase(switch_instr, case_value, this_case->block->llvm_block);
    }

    return nullptr;
}

static LLVMValueRef ir_render_phi(CodeGen *g, IrExecutable *executable, IrInstructionPhi *instruction) {
    if (!type_has_bits(instruction->base.value->type))
        return nullptr;

    LLVMTypeRef phi_type;
    if (handle_is_ptr(instruction->base.value->type)) {
        phi_type = LLVMPointerType(get_llvm_type(g,instruction->base.value->type), 0);
    } else {
        phi_type = get_llvm_type(g, instruction->base.value->type);
    }

    LLVMValueRef phi = LLVMBuildPhi(g->builder, phi_type, "");
    LLVMValueRef *incoming_values = allocate<LLVMValueRef>(instruction->incoming_count);
    LLVMBasicBlockRef *incoming_blocks = allocate<LLVMBasicBlockRef>(instruction->incoming_count);
    for (size_t i = 0; i < instruction->incoming_count; i += 1) {
        incoming_values[i] = ir_llvm_value(g, instruction->incoming_values[i]);
        incoming_blocks[i] = instruction->incoming_blocks[i]->llvm_exit_block;
    }
    LLVMAddIncoming(phi, incoming_values, incoming_blocks, (unsigned)instruction->incoming_count);
    return phi;
}

static LLVMValueRef ir_render_ref(CodeGen *g, IrExecutable *executable, IrInstructionRefGen *instruction) {
    if (!type_has_bits(instruction->base.value->type)) {
        return nullptr;
    }
    LLVMValueRef value = ir_llvm_value(g, instruction->operand);
    if (handle_is_ptr(instruction->operand->value->type)) {
        return value;
    } else {
        LLVMValueRef result_loc = ir_llvm_value(g, instruction->result_loc);
        gen_store_untyped(g, value, result_loc, 0, false);
        return result_loc;
    }
}

static LLVMValueRef ir_render_err_name(CodeGen *g, IrExecutable *executable, IrInstructionErrName *instruction) {
    assert(g->generate_error_name_table);

    if (g->errors_by_index.length == 1) {
        LLVMBuildUnreachable(g->builder);
        return nullptr;
    }

    LLVMValueRef err_val = ir_llvm_value(g, instruction->value);
    if (ir_want_runtime_safety(g, &instruction->base)) {
        LLVMValueRef zero = LLVMConstNull(LLVMTypeOf(err_val));
        LLVMValueRef end_val = LLVMConstInt(LLVMTypeOf(err_val), g->errors_by_index.length, false);
        add_bounds_check(g, err_val, LLVMIntNE, zero, LLVMIntULT, end_val);
    }

    LLVMValueRef indices[] = {
        LLVMConstNull(g->builtin_types.entry_usize->llvm_type),
        err_val,
    };
    return LLVMBuildInBoundsGEP(g->builder, g->err_name_table, indices, 2, "");
}

static LLVMValueRef get_enum_tag_name_function(CodeGen *g, ZigType *enum_type) {
    assert(enum_type->id == ZigTypeIdEnum);
    if (enum_type->data.enumeration.name_function)
        return enum_type->data.enumeration.name_function;

    ZigType *u8_ptr_type = get_pointer_to_type_extra(g, g->builtin_types.entry_u8, false, false,
            PtrLenUnknown, get_abi_alignment(g, g->builtin_types.entry_u8), 0, 0, false);
    ZigType *u8_slice_type = get_slice_type(g, u8_ptr_type);
    ZigType *tag_int_type = enum_type->data.enumeration.tag_int_type;

    LLVMTypeRef tag_int_llvm_type = get_llvm_type(g, tag_int_type);
    LLVMTypeRef fn_type_ref = LLVMFunctionType(LLVMPointerType(get_llvm_type(g, u8_slice_type), 0),
            &tag_int_llvm_type, 1, false);

    const char *fn_name = get_mangled_name(g,
            buf_ptr(buf_sprintf("__zig_tag_name_%s", buf_ptr(&enum_type->name))), false);
    LLVMValueRef fn_val = LLVMAddFunction(g->module, fn_name, fn_type_ref);
    LLVMSetLinkage(fn_val, LLVMInternalLinkage);
    ZigLLVMFunctionSetCallingConv(fn_val, get_llvm_cc(g, CallingConventionUnspecified));
    addLLVMFnAttr(fn_val, "nounwind");
    add_uwtable_attr(g, fn_val);
    if (codegen_have_frame_pointer(g)) {
        ZigLLVMAddFunctionAttr(fn_val, "no-frame-pointer-elim", "true");
        ZigLLVMAddFunctionAttr(fn_val, "no-frame-pointer-elim-non-leaf", nullptr);
    }

    LLVMBasicBlockRef prev_block = LLVMGetInsertBlock(g->builder);
    LLVMValueRef prev_debug_location = LLVMGetCurrentDebugLocation(g->builder);
    ZigFn *prev_cur_fn = g->cur_fn;
    LLVMValueRef prev_cur_fn_val = g->cur_fn_val;

    LLVMBasicBlockRef entry_block = LLVMAppendBasicBlock(fn_val, "Entry");
    LLVMPositionBuilderAtEnd(g->builder, entry_block);
    ZigLLVMClearCurrentDebugLocation(g->builder);
    g->cur_fn = nullptr;
    g->cur_fn_val = fn_val;

    size_t field_count = enum_type->data.enumeration.src_field_count;
    LLVMBasicBlockRef bad_value_block = LLVMAppendBasicBlock(g->cur_fn_val, "BadValue");
    LLVMValueRef tag_int_value = LLVMGetParam(fn_val, 0);
    LLVMValueRef switch_instr = LLVMBuildSwitch(g->builder, tag_int_value, bad_value_block, field_count);


    ZigType *usize = g->builtin_types.entry_usize;
    LLVMValueRef array_ptr_indices[] = {
        LLVMConstNull(usize->llvm_type),
        LLVMConstNull(usize->llvm_type),
    };

    for (size_t field_i = 0; field_i < field_count; field_i += 1) {
        Buf *name = enum_type->data.enumeration.fields[field_i].name;
        LLVMValueRef str_init = LLVMConstString(buf_ptr(name), (unsigned)buf_len(name), true);
        LLVMValueRef str_global = LLVMAddGlobal(g->module, LLVMTypeOf(str_init), "");
        LLVMSetInitializer(str_global, str_init);
        LLVMSetLinkage(str_global, LLVMPrivateLinkage);
        LLVMSetGlobalConstant(str_global, true);
        LLVMSetUnnamedAddr(str_global, true);
        LLVMSetAlignment(str_global, LLVMABIAlignmentOfType(g->target_data_ref, LLVMTypeOf(str_init)));

        LLVMValueRef fields[] = {
            LLVMConstGEP(str_global, array_ptr_indices, 2),
            LLVMConstInt(g->builtin_types.entry_usize->llvm_type, buf_len(name), false),
        };
        LLVMValueRef slice_init_value = LLVMConstNamedStruct(get_llvm_type(g, u8_slice_type), fields, 2);

        LLVMValueRef slice_global = LLVMAddGlobal(g->module, LLVMTypeOf(slice_init_value), "");
        LLVMSetInitializer(slice_global, slice_init_value);
        LLVMSetLinkage(slice_global, LLVMPrivateLinkage);
        LLVMSetGlobalConstant(slice_global, true);
        LLVMSetUnnamedAddr(slice_global, true);
        LLVMSetAlignment(slice_global, LLVMABIAlignmentOfType(g->target_data_ref, LLVMTypeOf(slice_init_value)));

        LLVMBasicBlockRef return_block = LLVMAppendBasicBlock(g->cur_fn_val, "Name");
        LLVMValueRef this_tag_int_value = bigint_to_llvm_const(get_llvm_type(g, tag_int_type),
                &enum_type->data.enumeration.fields[field_i].value);
        LLVMAddCase(switch_instr, this_tag_int_value, return_block);

        LLVMPositionBuilderAtEnd(g->builder, return_block);
        LLVMBuildRet(g->builder, slice_global);
    }

    LLVMPositionBuilderAtEnd(g->builder, bad_value_block);
    if (g->build_mode == BuildModeDebug || g->build_mode == BuildModeSafeRelease) {
        gen_safety_crash(g, PanicMsgIdBadEnumValue);
    } else {
        LLVMBuildUnreachable(g->builder);
    }

    g->cur_fn = prev_cur_fn;
    g->cur_fn_val = prev_cur_fn_val;
    LLVMPositionBuilderAtEnd(g->builder, prev_block);
    if (!g->strip_debug_symbols) {
        LLVMSetCurrentDebugLocation(g->builder, prev_debug_location);
    }

    enum_type->data.enumeration.name_function = fn_val;
    return fn_val;
}

static LLVMValueRef ir_render_enum_tag_name(CodeGen *g, IrExecutable *executable,
        IrInstructionTagName *instruction)
{
    ZigType *enum_type = instruction->target->value->type;
    assert(enum_type->id == ZigTypeIdEnum);
    if (enum_type->data.enumeration.non_exhaustive) {
        add_node_error(g, instruction->base.source_node,
            buf_sprintf("TODO @tagName on non-exhaustive enum https://github.com/ziglang/zig/issues/3991"));
        codegen_report_errors_and_exit(g);
    }

    LLVMValueRef enum_name_function = get_enum_tag_name_function(g, enum_type);

    LLVMValueRef enum_tag_value = ir_llvm_value(g, instruction->target);
    return ZigLLVMBuildCall(g->builder, enum_name_function, &enum_tag_value, 1,
            get_llvm_cc(g, CallingConventionUnspecified), ZigLLVM_CallAttrAuto, "");
}

static LLVMValueRef ir_render_field_parent_ptr(CodeGen *g, IrExecutable *executable,
        IrInstructionFieldParentPtr *instruction)
{
    ZigType *container_ptr_type = instruction->base.value->type;
    assert(container_ptr_type->id == ZigTypeIdPointer);

    ZigType *container_type = container_ptr_type->data.pointer.child_type;

    size_t byte_offset = LLVMOffsetOfElement(g->target_data_ref,
            get_llvm_type(g, container_type), instruction->field->gen_index);

    LLVMValueRef field_ptr_val = ir_llvm_value(g, instruction->field_ptr);

    if (byte_offset == 0) {
        return LLVMBuildBitCast(g->builder, field_ptr_val, get_llvm_type(g, container_ptr_type), "");
    } else {
        ZigType *usize = g->builtin_types.entry_usize;

        LLVMValueRef field_ptr_int = LLVMBuildPtrToInt(g->builder, field_ptr_val, usize->llvm_type, "");

        LLVMValueRef base_ptr_int = LLVMBuildNUWSub(g->builder, field_ptr_int,
                LLVMConstInt(usize->llvm_type, byte_offset, false), "");

        return LLVMBuildIntToPtr(g->builder, base_ptr_int, get_llvm_type(g, container_ptr_type), "");
    }
}

static LLVMValueRef ir_render_align_cast(CodeGen *g, IrExecutable *executable, IrInstructionAlignCast *instruction) {
    LLVMValueRef target_val = ir_llvm_value(g, instruction->target);
    assert(target_val);

    bool want_runtime_safety = ir_want_runtime_safety(g, &instruction->base);
    if (!want_runtime_safety) {
        return target_val;
    }

    ZigType *target_type = instruction->base.value->type;
    uint32_t align_bytes;
    LLVMValueRef ptr_val;

    if (target_type->id == ZigTypeIdPointer) {
        align_bytes = get_ptr_align(g, target_type);
        ptr_val = target_val;
    } else if (target_type->id == ZigTypeIdFn) {
        align_bytes = target_type->data.fn.fn_type_id.alignment;
        ptr_val = target_val;
    } else if (target_type->id == ZigTypeIdOptional &&
            target_type->data.maybe.child_type->id == ZigTypeIdPointer)
    {
        align_bytes = get_ptr_align(g, target_type->data.maybe.child_type);
        ptr_val = target_val;
    } else if (target_type->id == ZigTypeIdOptional &&
            target_type->data.maybe.child_type->id == ZigTypeIdFn)
    {
        align_bytes = target_type->data.maybe.child_type->data.fn.fn_type_id.alignment;
        ptr_val = target_val;
    } else if (target_type->id == ZigTypeIdStruct &&
            target_type->data.structure.special == StructSpecialSlice)
    {
        ZigType *slice_ptr_type = target_type->data.structure.fields[slice_ptr_index]->type_entry;
        align_bytes = get_ptr_align(g, slice_ptr_type);

        size_t ptr_index = target_type->data.structure.fields[slice_ptr_index]->gen_index;
        LLVMValueRef ptr_val_ptr = LLVMBuildStructGEP(g->builder, target_val, (unsigned)ptr_index, "");
        ptr_val = gen_load_untyped(g, ptr_val_ptr, 0, false, "");
    } else {
        zig_unreachable();
    }

    assert(align_bytes != 1);

    ZigType *usize = g->builtin_types.entry_usize;
    LLVMValueRef ptr_as_int_val = LLVMBuildPtrToInt(g->builder, ptr_val, usize->llvm_type, "");
    LLVMValueRef alignment_minus_1 = LLVMConstInt(usize->llvm_type, align_bytes - 1, false);
    LLVMValueRef anded_val = LLVMBuildAnd(g->builder, ptr_as_int_val, alignment_minus_1, "");
    LLVMValueRef ok_bit = LLVMBuildICmp(g->builder, LLVMIntEQ, anded_val, LLVMConstNull(usize->llvm_type), "");

    LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "AlignCastOk");
    LLVMBasicBlockRef fail_block = LLVMAppendBasicBlock(g->cur_fn_val, "AlignCastFail");

    LLVMBuildCondBr(g->builder, ok_bit, ok_block, fail_block);

    LLVMPositionBuilderAtEnd(g->builder, fail_block);
    gen_safety_crash(g, PanicMsgIdIncorrectAlignment);

    LLVMPositionBuilderAtEnd(g->builder, ok_block);

    return target_val;
}

static LLVMValueRef ir_render_error_return_trace(CodeGen *g, IrExecutable *executable,
        IrInstructionErrorReturnTrace *instruction)
{
    bool is_llvm_alloca;
    LLVMValueRef cur_err_ret_trace_val = get_cur_err_ret_trace_val(g, instruction->base.scope, &is_llvm_alloca);
    if (cur_err_ret_trace_val == nullptr) {
        return LLVMConstNull(get_llvm_type(g, ptr_to_stack_trace_type(g)));
    }
    return cur_err_ret_trace_val;
}

static LLVMAtomicOrdering to_LLVMAtomicOrdering(AtomicOrder atomic_order) {
    switch (atomic_order) {
        case AtomicOrderUnordered: return LLVMAtomicOrderingUnordered;
        case AtomicOrderMonotonic: return LLVMAtomicOrderingMonotonic;
        case AtomicOrderAcquire: return LLVMAtomicOrderingAcquire;
        case AtomicOrderRelease: return LLVMAtomicOrderingRelease;
        case AtomicOrderAcqRel: return LLVMAtomicOrderingAcquireRelease;
        case AtomicOrderSeqCst: return LLVMAtomicOrderingSequentiallyConsistent;
    }
    zig_unreachable();
}

static enum ZigLLVM_AtomicRMWBinOp to_ZigLLVMAtomicRMWBinOp(AtomicRmwOp op, bool is_signed, bool is_float) {
    switch (op) {
        case AtomicRmwOp_xchg: return ZigLLVMAtomicRMWBinOpXchg;
        case AtomicRmwOp_add:
            return is_float ? ZigLLVMAtomicRMWBinOpFAdd : ZigLLVMAtomicRMWBinOpAdd;
        case AtomicRmwOp_sub:
            return is_float ? ZigLLVMAtomicRMWBinOpFSub : ZigLLVMAtomicRMWBinOpSub;
        case AtomicRmwOp_and: return ZigLLVMAtomicRMWBinOpAnd;
        case AtomicRmwOp_nand: return ZigLLVMAtomicRMWBinOpNand;
        case AtomicRmwOp_or: return ZigLLVMAtomicRMWBinOpOr;
        case AtomicRmwOp_xor: return ZigLLVMAtomicRMWBinOpXor;
        case AtomicRmwOp_max:
            return is_signed ? ZigLLVMAtomicRMWBinOpMax : ZigLLVMAtomicRMWBinOpUMax;
        case AtomicRmwOp_min:
            return is_signed ? ZigLLVMAtomicRMWBinOpMin : ZigLLVMAtomicRMWBinOpUMin;
    }
    zig_unreachable();
}

static LLVMValueRef ir_render_cmpxchg(CodeGen *g, IrExecutable *executable, IrInstructionCmpxchgGen *instruction) {
    LLVMValueRef ptr_val = ir_llvm_value(g, instruction->ptr);
    LLVMValueRef cmp_val = ir_llvm_value(g, instruction->cmp_value);
    LLVMValueRef new_val = ir_llvm_value(g, instruction->new_value);

    LLVMAtomicOrdering success_order = to_LLVMAtomicOrdering(instruction->success_order);
    LLVMAtomicOrdering failure_order = to_LLVMAtomicOrdering(instruction->failure_order);

    LLVMValueRef result_val = ZigLLVMBuildCmpXchg(g->builder, ptr_val, cmp_val, new_val,
            success_order, failure_order, instruction->is_weak);

    ZigType *optional_type = instruction->base.value->type;
    assert(optional_type->id == ZigTypeIdOptional);
    ZigType *child_type = optional_type->data.maybe.child_type;

    if (!handle_is_ptr(optional_type)) {
        LLVMValueRef payload_val = LLVMBuildExtractValue(g->builder, result_val, 0, "");
        LLVMValueRef success_bit = LLVMBuildExtractValue(g->builder, result_val, 1, "");
        return LLVMBuildSelect(g->builder, success_bit, LLVMConstNull(get_llvm_type(g, child_type)), payload_val, "");
    }

    // When the cmpxchg is discarded, the result location will have no bits.
    if (!type_has_bits(instruction->result_loc->value->type)) {
        return nullptr;
    }

    LLVMValueRef result_loc = ir_llvm_value(g, instruction->result_loc);
    ir_assert(result_loc != nullptr, &instruction->base);
    ir_assert(type_has_bits(child_type), &instruction->base);

    LLVMValueRef payload_val = LLVMBuildExtractValue(g->builder, result_val, 0, "");
    LLVMValueRef val_ptr = LLVMBuildStructGEP(g->builder, result_loc, maybe_child_index, "");
    gen_assign_raw(g, val_ptr, get_pointer_to_type(g, child_type, false), payload_val);

    LLVMValueRef success_bit = LLVMBuildExtractValue(g->builder, result_val, 1, "");
    LLVMValueRef nonnull_bit = LLVMBuildNot(g->builder, success_bit, "");
    LLVMValueRef maybe_ptr = LLVMBuildStructGEP(g->builder, result_loc, maybe_null_index, "");
    gen_store_untyped(g, nonnull_bit, maybe_ptr, 0, false);
    return result_loc;
}

static LLVMValueRef ir_render_fence(CodeGen *g, IrExecutable *executable, IrInstructionFence *instruction) {
    LLVMAtomicOrdering atomic_order = to_LLVMAtomicOrdering(instruction->order);
    LLVMBuildFence(g->builder, atomic_order, false, "");
    return nullptr;
}

static LLVMValueRef ir_render_truncate(CodeGen *g, IrExecutable *executable, IrInstructionTruncate *instruction) {
    LLVMValueRef target_val = ir_llvm_value(g, instruction->target);
    ZigType *dest_type = instruction->base.value->type;
    ZigType *src_type = instruction->target->value->type;
    if (dest_type == src_type) {
        // no-op
        return target_val;
    } if (src_type->data.integral.bit_count == dest_type->data.integral.bit_count) {
        return LLVMBuildBitCast(g->builder, target_val, get_llvm_type(g, dest_type), "");
    } else {
        LLVMValueRef target_val = ir_llvm_value(g, instruction->target);
        return LLVMBuildTrunc(g->builder, target_val, get_llvm_type(g, dest_type), "");
    }
}

static LLVMValueRef ir_render_memset(CodeGen *g, IrExecutable *executable, IrInstructionMemset *instruction) {
    LLVMValueRef dest_ptr = ir_llvm_value(g, instruction->dest_ptr);
    LLVMValueRef len_val = ir_llvm_value(g, instruction->count);

    LLVMTypeRef ptr_u8 = LLVMPointerType(LLVMInt8Type(), 0);
    LLVMValueRef dest_ptr_casted = LLVMBuildBitCast(g->builder, dest_ptr, ptr_u8, "");

    ZigType *ptr_type = instruction->dest_ptr->value->type;
    assert(ptr_type->id == ZigTypeIdPointer);

    bool val_is_undef = value_is_all_undef(g, instruction->byte->value);
    LLVMValueRef fill_char;
    if (val_is_undef && ir_want_runtime_safety_scope(g, instruction->base.scope)) {
        fill_char = LLVMConstInt(LLVMInt8Type(), 0xaa, false);
    } else {
        fill_char = ir_llvm_value(g, instruction->byte);
    }
    ZigLLVMBuildMemSet(g->builder, dest_ptr_casted, fill_char, len_val, get_ptr_align(g, ptr_type),
            ptr_type->data.pointer.is_volatile);

    if (val_is_undef && want_valgrind_support(g)) {
        gen_valgrind_undef(g, dest_ptr_casted, len_val);
    }
    return nullptr;
}

static LLVMValueRef ir_render_memcpy(CodeGen *g, IrExecutable *executable, IrInstructionMemcpy *instruction) {
    LLVMValueRef dest_ptr = ir_llvm_value(g, instruction->dest_ptr);
    LLVMValueRef src_ptr = ir_llvm_value(g, instruction->src_ptr);
    LLVMValueRef len_val = ir_llvm_value(g, instruction->count);

    LLVMTypeRef ptr_u8 = LLVMPointerType(LLVMInt8Type(), 0);

    LLVMValueRef dest_ptr_casted = LLVMBuildBitCast(g->builder, dest_ptr, ptr_u8, "");
    LLVMValueRef src_ptr_casted = LLVMBuildBitCast(g->builder, src_ptr, ptr_u8, "");

    ZigType *dest_ptr_type = instruction->dest_ptr->value->type;
    ZigType *src_ptr_type = instruction->src_ptr->value->type;

    assert(dest_ptr_type->id == ZigTypeIdPointer);
    assert(src_ptr_type->id == ZigTypeIdPointer);

    bool is_volatile = (dest_ptr_type->data.pointer.is_volatile || src_ptr_type->data.pointer.is_volatile);
    ZigLLVMBuildMemCpy(g->builder, dest_ptr_casted, get_ptr_align(g, dest_ptr_type),
            src_ptr_casted, get_ptr_align(g, src_ptr_type), len_val, is_volatile);
    return nullptr;
}

static LLVMValueRef ir_render_slice(CodeGen *g, IrExecutable *executable, IrInstructionSliceGen *instruction) {
    LLVMValueRef array_ptr_ptr = ir_llvm_value(g, instruction->ptr);
    ZigType *array_ptr_type = instruction->ptr->value->type;
    assert(array_ptr_type->id == ZigTypeIdPointer);
    ZigType *array_type = array_ptr_type->data.pointer.child_type;
    LLVMValueRef array_ptr = get_handle_value(g, array_ptr_ptr, array_type, array_ptr_type);

    LLVMValueRef tmp_struct_ptr = ir_llvm_value(g, instruction->result_loc);

    bool want_runtime_safety = instruction->safety_check_on && ir_want_runtime_safety(g, &instruction->base);

    ZigType *res_slice_ptr_type = instruction->base.value->type->data.structure.fields[slice_ptr_index]->type_entry;
    ZigValue *sentinel = res_slice_ptr_type->data.pointer.sentinel;

    if (array_type->id == ZigTypeIdArray ||
        (array_type->id == ZigTypeIdPointer && array_type->data.pointer.ptr_len == PtrLenSingle))
    {
        if (array_type->id == ZigTypeIdPointer) {
            array_type = array_type->data.pointer.child_type;
        }
        LLVMValueRef start_val = ir_llvm_value(g, instruction->start);
        LLVMValueRef end_val;
        if (instruction->end) {
            end_val = ir_llvm_value(g, instruction->end);
        } else {
            end_val = LLVMConstInt(g->builtin_types.entry_usize->llvm_type, array_type->data.array.len, false);
        }
        if (want_runtime_safety) {
            if (instruction->start->value->special == ConstValSpecialRuntime || instruction->end) {
                add_bounds_check(g, start_val, LLVMIntEQ, nullptr, LLVMIntULE, end_val);
            }
            if (instruction->end) {
                LLVMValueRef array_end = LLVMConstInt(g->builtin_types.entry_usize->llvm_type,
                        array_type->data.array.len, false);
                add_bounds_check(g, end_val, LLVMIntEQ, nullptr, LLVMIntULE, array_end);

                if (sentinel != nullptr) {
                    LLVMValueRef indices[] = {
                        LLVMConstNull(g->builtin_types.entry_usize->llvm_type),
                        end_val,
                    };
                    LLVMValueRef sentinel_elem_ptr = LLVMBuildInBoundsGEP(g->builder, array_ptr, indices, 2, "");
                    add_sentinel_check(g, sentinel_elem_ptr, sentinel);
                }
            }
        }
        if (!type_has_bits(array_type)) {
            LLVMValueRef len_field_ptr = LLVMBuildStructGEP(g->builder, tmp_struct_ptr, slice_len_index, "");

            // TODO if runtime safety is on, store 0xaaaaaaa in ptr field
            LLVMValueRef len_value = LLVMBuildNSWSub(g->builder, end_val, start_val, "");
            gen_store_untyped(g, len_value, len_field_ptr, 0, false);
            return tmp_struct_ptr;
        }


        LLVMValueRef ptr_field_ptr = LLVMBuildStructGEP(g->builder, tmp_struct_ptr, slice_ptr_index, "");
        LLVMValueRef indices[] = {
            LLVMConstNull(g->builtin_types.entry_usize->llvm_type),
            start_val,
        };
        LLVMValueRef slice_start_ptr = LLVMBuildInBoundsGEP(g->builder, array_ptr, indices, 2, "");
        gen_store_untyped(g, slice_start_ptr, ptr_field_ptr, 0, false);

        LLVMValueRef len_field_ptr = LLVMBuildStructGEP(g->builder, tmp_struct_ptr, slice_len_index, "");
        LLVMValueRef len_value = LLVMBuildNSWSub(g->builder, end_val, start_val, "");
        gen_store_untyped(g, len_value, len_field_ptr, 0, false);

        return tmp_struct_ptr;
    } else if (array_type->id == ZigTypeIdPointer) {
        assert(array_type->data.pointer.ptr_len != PtrLenSingle);
        LLVMValueRef start_val = ir_llvm_value(g, instruction->start);
        LLVMValueRef end_val = ir_llvm_value(g, instruction->end);

        if (want_runtime_safety) {
            add_bounds_check(g, start_val, LLVMIntEQ, nullptr, LLVMIntULE, end_val);
            if (sentinel != nullptr) {
                LLVMValueRef sentinel_elem_ptr = LLVMBuildInBoundsGEP(g->builder, array_ptr, &end_val, 1, "");
                add_sentinel_check(g, sentinel_elem_ptr, sentinel);
            }
        }

        if (type_has_bits(array_type)) {
            size_t gen_ptr_index = instruction->base.value->type->data.structure.fields[slice_ptr_index]->gen_index;
            LLVMValueRef ptr_field_ptr = LLVMBuildStructGEP(g->builder, tmp_struct_ptr, gen_ptr_index, "");
            LLVMValueRef slice_start_ptr = LLVMBuildInBoundsGEP(g->builder, array_ptr, &start_val, 1, "");
            gen_store_untyped(g, slice_start_ptr, ptr_field_ptr, 0, false);
        }

        size_t gen_len_index = instruction->base.value->type->data.structure.fields[slice_len_index]->gen_index;
        LLVMValueRef len_field_ptr = LLVMBuildStructGEP(g->builder, tmp_struct_ptr, gen_len_index, "");
        LLVMValueRef len_value = LLVMBuildNSWSub(g->builder, end_val, start_val, "");
        gen_store_untyped(g, len_value, len_field_ptr, 0, false);

        return tmp_struct_ptr;
    } else if (array_type->id == ZigTypeIdStruct) {
        assert(array_type->data.structure.special == StructSpecialSlice);
        assert(LLVMGetTypeKind(LLVMTypeOf(array_ptr)) == LLVMPointerTypeKind);
        assert(LLVMGetTypeKind(LLVMGetElementType(LLVMTypeOf(array_ptr))) == LLVMStructTypeKind);
        assert(LLVMGetTypeKind(LLVMGetElementType(LLVMTypeOf(tmp_struct_ptr))) == LLVMStructTypeKind);

        size_t ptr_index = array_type->data.structure.fields[slice_ptr_index]->gen_index;
        assert(ptr_index != SIZE_MAX);
        size_t len_index = array_type->data.structure.fields[slice_len_index]->gen_index;
        assert(len_index != SIZE_MAX);

        LLVMValueRef prev_end = nullptr;
        if (!instruction->end || want_runtime_safety) {
            LLVMValueRef src_len_ptr = LLVMBuildStructGEP(g->builder, array_ptr, (unsigned)len_index, "");
            prev_end = gen_load_untyped(g, src_len_ptr, 0, false, "");
        }

        LLVMValueRef start_val = ir_llvm_value(g, instruction->start);
        LLVMValueRef end_val;
        if (instruction->end) {
            end_val = ir_llvm_value(g, instruction->end);
        } else {
            end_val = prev_end;
        }

        LLVMValueRef src_ptr_ptr = LLVMBuildStructGEP(g->builder, array_ptr, (unsigned)ptr_index, "");
        LLVMValueRef src_ptr = gen_load_untyped(g, src_ptr_ptr, 0, false, "");

        if (want_runtime_safety) {
            assert(prev_end);
            add_bounds_check(g, start_val, LLVMIntEQ, nullptr, LLVMIntULE, end_val);
            if (instruction->end) {
                add_bounds_check(g, end_val, LLVMIntEQ, nullptr, LLVMIntULE, prev_end);

                if (sentinel != nullptr) {
                    LLVMValueRef sentinel_elem_ptr = LLVMBuildInBoundsGEP(g->builder, src_ptr, &end_val, 1, "");
                    add_sentinel_check(g, sentinel_elem_ptr, sentinel);
                }
            }
        }

        LLVMValueRef ptr_field_ptr = LLVMBuildStructGEP(g->builder, tmp_struct_ptr, (unsigned)ptr_index, "");
        LLVMValueRef slice_start_ptr = LLVMBuildInBoundsGEP(g->builder, src_ptr, &start_val, 1, "");
        gen_store_untyped(g, slice_start_ptr, ptr_field_ptr, 0, false);

        LLVMValueRef len_field_ptr = LLVMBuildStructGEP(g->builder, tmp_struct_ptr, (unsigned)len_index, "");
        LLVMValueRef len_value = LLVMBuildNSWSub(g->builder, end_val, start_val, "");
        gen_store_untyped(g, len_value, len_field_ptr, 0, false);

        return tmp_struct_ptr;
    } else {
        zig_unreachable();
    }
}

static LLVMValueRef get_trap_fn_val(CodeGen *g) {
    if (g->trap_fn_val)
        return g->trap_fn_val;

    LLVMTypeRef fn_type = LLVMFunctionType(LLVMVoidType(), nullptr, 0, false);
    g->trap_fn_val = LLVMAddFunction(g->module, "llvm.debugtrap", fn_type);
    assert(LLVMGetIntrinsicID(g->trap_fn_val));

    return g->trap_fn_val;
}


static LLVMValueRef ir_render_breakpoint(CodeGen *g, IrExecutable *executable, IrInstructionBreakpoint *instruction) {
    LLVMBuildCall(g->builder, get_trap_fn_val(g), nullptr, 0, "");
    return nullptr;
}

static LLVMValueRef ir_render_return_address(CodeGen *g, IrExecutable *executable,
        IrInstructionReturnAddress *instruction)
{
    LLVMValueRef zero = LLVMConstNull(g->builtin_types.entry_i32->llvm_type);
    LLVMValueRef ptr_val = LLVMBuildCall(g->builder, get_return_address_fn_val(g), &zero, 1, "");
    return LLVMBuildPtrToInt(g->builder, ptr_val, g->builtin_types.entry_usize->llvm_type, "");
}

static LLVMValueRef get_frame_address_fn_val(CodeGen *g) {
    if (g->frame_address_fn_val)
        return g->frame_address_fn_val;

    ZigType *return_type = get_pointer_to_type(g, g->builtin_types.entry_u8, true);

    LLVMTypeRef fn_type = LLVMFunctionType(get_llvm_type(g, return_type),
            &g->builtin_types.entry_i32->llvm_type, 1, false);
    g->frame_address_fn_val = LLVMAddFunction(g->module, "llvm.frameaddress", fn_type);
    assert(LLVMGetIntrinsicID(g->frame_address_fn_val));

    return g->frame_address_fn_val;
}

static LLVMValueRef ir_render_frame_address(CodeGen *g, IrExecutable *executable,
        IrInstructionFrameAddress *instruction)
{
    LLVMValueRef zero = LLVMConstNull(g->builtin_types.entry_i32->llvm_type);
    LLVMValueRef ptr_val = LLVMBuildCall(g->builder, get_frame_address_fn_val(g), &zero, 1, "");
    return LLVMBuildPtrToInt(g->builder, ptr_val, g->builtin_types.entry_usize->llvm_type, "");
}

static LLVMValueRef ir_render_handle(CodeGen *g, IrExecutable *executable, IrInstructionFrameHandle *instruction) {
    return g->cur_frame_ptr;
}

static LLVMValueRef render_shl_with_overflow(CodeGen *g, IrInstructionOverflowOp *instruction) {
    ZigType *int_type = instruction->result_ptr_type;
    assert(int_type->id == ZigTypeIdInt);

    LLVMValueRef op1 = ir_llvm_value(g, instruction->op1);
    LLVMValueRef op2 = ir_llvm_value(g, instruction->op2);
    LLVMValueRef ptr_result = ir_llvm_value(g, instruction->result_ptr);

    LLVMValueRef op2_casted = gen_widen_or_shorten(g, false, instruction->op2->value->type,
            instruction->op1->value->type, op2);

    LLVMValueRef result = LLVMBuildShl(g->builder, op1, op2_casted, "");
    LLVMValueRef orig_val;
    if (int_type->data.integral.is_signed) {
        orig_val = LLVMBuildAShr(g->builder, result, op2_casted, "");
    } else {
        orig_val = LLVMBuildLShr(g->builder, result, op2_casted, "");
    }
    LLVMValueRef overflow_bit = LLVMBuildICmp(g->builder, LLVMIntNE, op1, orig_val, "");

    gen_store(g, result, ptr_result, instruction->result_ptr->value->type);

    return overflow_bit;
}

static LLVMValueRef ir_render_overflow_op(CodeGen *g, IrExecutable *executable, IrInstructionOverflowOp *instruction) {
    AddSubMul add_sub_mul;
    switch (instruction->op) {
        case IrOverflowOpAdd:
            add_sub_mul = AddSubMulAdd;
            break;
        case IrOverflowOpSub:
            add_sub_mul = AddSubMulSub;
            break;
        case IrOverflowOpMul:
            add_sub_mul = AddSubMulMul;
            break;
        case IrOverflowOpShl:
            return render_shl_with_overflow(g, instruction);
    }

    ZigType *int_type = instruction->result_ptr_type;
    assert(int_type->id == ZigTypeIdInt);

    LLVMValueRef fn_val = get_int_overflow_fn(g, int_type, add_sub_mul);

    LLVMValueRef op1 = ir_llvm_value(g, instruction->op1);
    LLVMValueRef op2 = ir_llvm_value(g, instruction->op2);
    LLVMValueRef ptr_result = ir_llvm_value(g, instruction->result_ptr);

    LLVMValueRef params[] = {
        op1,
        op2,
    };

    LLVMValueRef result_struct = LLVMBuildCall(g->builder, fn_val, params, 2, "");
    LLVMValueRef result = LLVMBuildExtractValue(g->builder, result_struct, 0, "");
    LLVMValueRef overflow_bit = LLVMBuildExtractValue(g->builder, result_struct, 1, "");
    gen_store(g, result, ptr_result, instruction->result_ptr->value->type);

    return overflow_bit;
}

static LLVMValueRef ir_render_test_err(CodeGen *g, IrExecutable *executable, IrInstructionTestErrGen *instruction) {
    ZigType *err_union_type = instruction->err_union->value->type;
    ZigType *payload_type = err_union_type->data.error_union.payload_type;
    LLVMValueRef err_union_handle = ir_llvm_value(g, instruction->err_union);

    LLVMValueRef err_val;
    if (type_has_bits(payload_type)) {
        LLVMValueRef err_val_ptr = LLVMBuildStructGEP(g->builder, err_union_handle, err_union_err_index, "");
        err_val = gen_load_untyped(g, err_val_ptr, 0, false, "");
    } else {
        err_val = err_union_handle;
    }

    LLVMValueRef zero = LLVMConstNull(get_llvm_type(g, g->err_tag_type));
    return LLVMBuildICmp(g->builder, LLVMIntNE, err_val, zero, "");
}

static LLVMValueRef ir_render_unwrap_err_code(CodeGen *g, IrExecutable *executable,
        IrInstructionUnwrapErrCode *instruction)
{
    if (instruction->base.value->special != ConstValSpecialRuntime)
        return nullptr;

    ZigType *ptr_type = instruction->err_union_ptr->value->type;
    assert(ptr_type->id == ZigTypeIdPointer);
    ZigType *err_union_type = ptr_type->data.pointer.child_type;
    ZigType *payload_type = err_union_type->data.error_union.payload_type;
    LLVMValueRef err_union_ptr = ir_llvm_value(g, instruction->err_union_ptr);
    if (!type_has_bits(payload_type)) {
        return err_union_ptr;
    } else {
        // TODO assign undef to the payload
        LLVMValueRef err_union_handle = get_handle_value(g, err_union_ptr, err_union_type, ptr_type);
        return LLVMBuildStructGEP(g->builder, err_union_handle, err_union_err_index, "");
    }
}

static LLVMValueRef ir_render_unwrap_err_payload(CodeGen *g, IrExecutable *executable,
        IrInstructionUnwrapErrPayload *instruction)
{
    Error err;

    if (instruction->base.value->special != ConstValSpecialRuntime)
        return nullptr;

    bool want_safety = instruction->safety_check_on && ir_want_runtime_safety(g, &instruction->base) &&
        g->errors_by_index.length > 1;

    bool value_has_bits;
    if ((err = type_has_bits2(g, instruction->base.value->type, &value_has_bits)))
        codegen_report_errors_and_exit(g);

    if (!want_safety && !value_has_bits)
        return nullptr;

    ZigType *ptr_type = instruction->value->value->type;
    assert(ptr_type->id == ZigTypeIdPointer);
    ZigType *err_union_type = ptr_type->data.pointer.child_type;
    ZigType *payload_type = err_union_type->data.error_union.payload_type;
    LLVMValueRef err_union_ptr = ir_llvm_value(g, instruction->value);
    LLVMValueRef err_union_handle = get_handle_value(g, err_union_ptr, err_union_type, ptr_type);

    if (!type_has_bits(err_union_type->data.error_union.err_set_type)) {
        return err_union_handle;
    }

    if (want_safety) {
        LLVMValueRef err_val;
        if (type_has_bits(payload_type)) {
            LLVMValueRef err_val_ptr = LLVMBuildStructGEP(g->builder, err_union_handle, err_union_err_index, "");
            err_val = gen_load_untyped(g, err_val_ptr, 0, false, "");
        } else {
            err_val = err_union_handle;
        }
        LLVMValueRef zero = LLVMConstNull(get_llvm_type(g, g->err_tag_type));
        LLVMValueRef cond_val = LLVMBuildICmp(g->builder, LLVMIntEQ, err_val, zero, "");
        LLVMBasicBlockRef err_block = LLVMAppendBasicBlock(g->cur_fn_val, "UnwrapErrError");
        LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "UnwrapErrOk");
        LLVMBuildCondBr(g->builder, cond_val, ok_block, err_block);

        LLVMPositionBuilderAtEnd(g->builder, err_block);
        gen_safety_crash_for_err(g, err_val, instruction->base.scope);

        LLVMPositionBuilderAtEnd(g->builder, ok_block);
    }

    if (type_has_bits(payload_type)) {
        if (instruction->initializing) {
            LLVMValueRef err_tag_ptr = LLVMBuildStructGEP(g->builder, err_union_handle, err_union_err_index, "");
            LLVMValueRef ok_err_val = LLVMConstNull(get_llvm_type(g, g->err_tag_type));
            gen_store_untyped(g, ok_err_val, err_tag_ptr, 0, false);
        }
        return LLVMBuildStructGEP(g->builder, err_union_handle, err_union_payload_index, "");
    } else {
        return nullptr;
    }
}

static LLVMValueRef ir_render_optional_wrap(CodeGen *g, IrExecutable *executable, IrInstructionOptionalWrap *instruction) {
    ZigType *wanted_type = instruction->base.value->type;

    assert(wanted_type->id == ZigTypeIdOptional);

    ZigType *child_type = wanted_type->data.maybe.child_type;

    if (!type_has_bits(child_type)) {
        LLVMValueRef result = LLVMConstAllOnes(LLVMInt1Type());
        if (instruction->result_loc != nullptr) {
            LLVMValueRef result_loc = ir_llvm_value(g, instruction->result_loc);
            gen_store_untyped(g, result, result_loc, 0, false);
        }
        return result;
    }

    LLVMValueRef payload_val = ir_llvm_value(g, instruction->operand);
    if (!handle_is_ptr(wanted_type)) {
        if (instruction->result_loc != nullptr) {
            LLVMValueRef result_loc = ir_llvm_value(g, instruction->result_loc);
            gen_store_untyped(g, payload_val, result_loc, 0, false);
        }
        return payload_val;
    }

    LLVMValueRef result_loc = ir_llvm_value(g, instruction->result_loc);

    LLVMValueRef val_ptr = LLVMBuildStructGEP(g->builder, result_loc, maybe_child_index, "");
    // child_type and instruction->value->value->type may differ by constness
    gen_assign_raw(g, val_ptr, get_pointer_to_type(g, child_type, false), payload_val);
    LLVMValueRef maybe_ptr = LLVMBuildStructGEP(g->builder, result_loc, maybe_null_index, "");
    gen_store_untyped(g, LLVMConstAllOnes(LLVMInt1Type()), maybe_ptr, 0, false);

    return result_loc;
}

static LLVMValueRef ir_render_err_wrap_code(CodeGen *g, IrExecutable *executable, IrInstructionErrWrapCode *instruction) {
    ZigType *wanted_type = instruction->base.value->type;

    assert(wanted_type->id == ZigTypeIdErrorUnion);

    LLVMValueRef err_val = ir_llvm_value(g, instruction->operand);

    if (!handle_is_ptr(wanted_type))
        return err_val;

    LLVMValueRef result_loc = ir_llvm_value(g, instruction->result_loc);

    LLVMValueRef err_tag_ptr = LLVMBuildStructGEP(g->builder, result_loc, err_union_err_index, "");
    gen_store_untyped(g, err_val, err_tag_ptr, 0, false);

    // TODO store undef to the payload

    return result_loc;
}

static LLVMValueRef ir_render_err_wrap_payload(CodeGen *g, IrExecutable *executable, IrInstructionErrWrapPayload *instruction) {
    ZigType *wanted_type = instruction->base.value->type;

    assert(wanted_type->id == ZigTypeIdErrorUnion);

    ZigType *payload_type = wanted_type->data.error_union.payload_type;
    ZigType *err_set_type = wanted_type->data.error_union.err_set_type;

    if (!type_has_bits(err_set_type)) {
        return ir_llvm_value(g, instruction->operand);
    }

    LLVMValueRef ok_err_val = LLVMConstNull(get_llvm_type(g, g->err_tag_type));

    if (!type_has_bits(payload_type))
        return ok_err_val;


    LLVMValueRef result_loc = ir_llvm_value(g, instruction->result_loc);

    LLVMValueRef payload_val = ir_llvm_value(g, instruction->operand);

    LLVMValueRef err_tag_ptr = LLVMBuildStructGEP(g->builder, result_loc, err_union_err_index, "");
    gen_store_untyped(g, ok_err_val, err_tag_ptr, 0, false);

    LLVMValueRef payload_ptr = LLVMBuildStructGEP(g->builder, result_loc, err_union_payload_index, "");
    gen_assign_raw(g, payload_ptr, get_pointer_to_type(g, payload_type, false), payload_val);

    return result_loc;
}

static LLVMValueRef ir_render_union_tag(CodeGen *g, IrExecutable *executable, IrInstructionUnionTag *instruction) {
    ZigType *union_type = instruction->value->value->type;

    ZigType *tag_type = union_type->data.unionation.tag_type;
    if (!type_has_bits(tag_type))
        return nullptr;

    LLVMValueRef union_val = ir_llvm_value(g, instruction->value);
    if (union_type->data.unionation.gen_field_count == 0)
        return union_val;

    assert(union_type->data.unionation.gen_tag_index != SIZE_MAX);
    LLVMValueRef tag_field_ptr = LLVMBuildStructGEP(g->builder, union_val,
            union_type->data.unionation.gen_tag_index, "");
    ZigType *ptr_type = get_pointer_to_type(g, tag_type, false);
    return get_handle_value(g, tag_field_ptr, tag_type, ptr_type);
}

static LLVMValueRef ir_render_panic(CodeGen *g, IrExecutable *executable, IrInstructionPanic *instruction) {
    bool is_llvm_alloca;
    LLVMValueRef err_ret_trace_val = get_cur_err_ret_trace_val(g, instruction->base.scope, &is_llvm_alloca);
    gen_panic(g, ir_llvm_value(g, instruction->msg), err_ret_trace_val, is_llvm_alloca);
    return nullptr;
}

static LLVMValueRef ir_render_atomic_rmw(CodeGen *g, IrExecutable *executable,
        IrInstructionAtomicRmw *instruction)
{
    bool is_signed;
    ZigType *operand_type = instruction->operand->value->type;
    bool is_float = operand_type->id == ZigTypeIdFloat;
    if (operand_type->id == ZigTypeIdInt) {
        is_signed = operand_type->data.integral.is_signed;
    } else {
        is_signed = false;
    }
    enum ZigLLVM_AtomicRMWBinOp op = to_ZigLLVMAtomicRMWBinOp(instruction->resolved_op, is_signed, is_float);
    LLVMAtomicOrdering ordering = to_LLVMAtomicOrdering(instruction->resolved_ordering);
    LLVMValueRef ptr = ir_llvm_value(g, instruction->ptr);
    LLVMValueRef operand = ir_llvm_value(g, instruction->operand);

    if (get_codegen_ptr_type(operand_type) == nullptr) {
        return ZigLLVMBuildAtomicRMW(g->builder, op, ptr, operand, ordering, g->is_single_threaded);
    }

    // it's a pointer but we need to treat it as an int
    LLVMValueRef casted_ptr = LLVMBuildBitCast(g->builder, ptr,
        LLVMPointerType(g->builtin_types.entry_usize->llvm_type, 0), "");
    LLVMValueRef casted_operand = LLVMBuildPtrToInt(g->builder, operand, g->builtin_types.entry_usize->llvm_type, "");
    LLVMValueRef uncasted_result = ZigLLVMBuildAtomicRMW(g->builder, op, casted_ptr, casted_operand, ordering,
            g->is_single_threaded);
    return LLVMBuildIntToPtr(g->builder, uncasted_result, get_llvm_type(g, operand_type), "");
}

static LLVMValueRef ir_render_atomic_load(CodeGen *g, IrExecutable *executable,
        IrInstructionAtomicLoad *instruction)
{
    LLVMAtomicOrdering ordering = to_LLVMAtomicOrdering(instruction->resolved_ordering);
    LLVMValueRef ptr = ir_llvm_value(g, instruction->ptr);
    LLVMValueRef load_inst = gen_load(g, ptr, instruction->ptr->value->type, "");
    LLVMSetOrdering(load_inst, ordering);
    return load_inst;
}

static LLVMValueRef ir_render_atomic_store(CodeGen *g, IrExecutable *executable,
        IrInstructionAtomicStore *instruction)
{
    LLVMAtomicOrdering ordering = to_LLVMAtomicOrdering(instruction->resolved_ordering);
    LLVMValueRef ptr = ir_llvm_value(g, instruction->ptr);
    LLVMValueRef value = ir_llvm_value(g, instruction->value);
    LLVMValueRef store_inst = gen_store(g, value, ptr, instruction->ptr->value->type);
    LLVMSetOrdering(store_inst, ordering);
    return nullptr;
}

static LLVMValueRef ir_render_float_op(CodeGen *g, IrExecutable *executable, IrInstructionFloatOp *instruction) {
    LLVMValueRef operand = ir_llvm_value(g, instruction->operand);
    LLVMValueRef fn_val = get_float_fn(g, instruction->base.value->type, ZigLLVMFnIdFloatOp, instruction->fn_id);
    return LLVMBuildCall(g->builder, fn_val, &operand, 1, "");
}

static LLVMValueRef ir_render_mul_add(CodeGen *g, IrExecutable *executable, IrInstructionMulAdd *instruction) {
    LLVMValueRef op1 = ir_llvm_value(g, instruction->op1);
    LLVMValueRef op2 = ir_llvm_value(g, instruction->op2);
    LLVMValueRef op3 = ir_llvm_value(g, instruction->op3);
    assert(instruction->base.value->type->id == ZigTypeIdFloat ||
           instruction->base.value->type->id == ZigTypeIdVector);
    LLVMValueRef fn_val = get_float_fn(g, instruction->base.value->type, ZigLLVMFnIdFMA, BuiltinFnIdMulAdd);
    LLVMValueRef args[3] = {
        op1,
        op2,
        op3,
    };
    return LLVMBuildCall(g->builder, fn_val, args, 3, "");
}

static LLVMValueRef ir_render_bswap(CodeGen *g, IrExecutable *executable, IrInstructionBswap *instruction) {
    LLVMValueRef op = ir_llvm_value(g, instruction->op);
    ZigType *expr_type = instruction->base.value->type;
    bool is_vector = expr_type->id == ZigTypeIdVector;
    ZigType *int_type = is_vector ? expr_type->data.vector.elem_type : expr_type;
    assert(int_type->id == ZigTypeIdInt);
    if (int_type->data.integral.bit_count % 16 == 0) {
        LLVMValueRef fn_val = get_int_builtin_fn(g, expr_type, BuiltinFnIdBswap);
        return LLVMBuildCall(g->builder, fn_val, &op, 1, "");
    }
    // Not an even number of bytes, so we zext 1 byte, then bswap, shift right 1 byte, truncate
    ZigType *extended_type = get_int_type(g, int_type->data.integral.is_signed,
            int_type->data.integral.bit_count + 8);
    LLVMValueRef shift_amt = LLVMConstInt(get_llvm_type(g, extended_type), 8, false);
    if (is_vector) {
        extended_type = get_vector_type(g, expr_type->data.vector.len, extended_type);
        LLVMValueRef *values = allocate_nonzero<LLVMValueRef>(expr_type->data.vector.len);
        for (uint32_t i = 0; i < expr_type->data.vector.len; i += 1) {
            values[i] = shift_amt;
        }
        shift_amt = LLVMConstVector(values, expr_type->data.vector.len);
        free(values);
    }
    // aabbcc
    LLVMValueRef extended = LLVMBuildZExt(g->builder, op, get_llvm_type(g, extended_type), "");
    // 00aabbcc
    LLVMValueRef fn_val = get_int_builtin_fn(g, extended_type, BuiltinFnIdBswap);
    LLVMValueRef swapped = LLVMBuildCall(g->builder, fn_val, &extended, 1, "");
    // ccbbaa00
    LLVMValueRef shifted = ZigLLVMBuildLShrExact(g->builder, swapped, shift_amt, "");
    // 00ccbbaa
    return LLVMBuildTrunc(g->builder, shifted, get_llvm_type(g, expr_type), "");
}

static LLVMValueRef ir_render_bit_reverse(CodeGen *g, IrExecutable *executable, IrInstructionBitReverse *instruction) {
    LLVMValueRef op = ir_llvm_value(g, instruction->op);
    ZigType *int_type = instruction->base.value->type;
    assert(int_type->id == ZigTypeIdInt);
    LLVMValueRef fn_val = get_int_builtin_fn(g, instruction->base.value->type, BuiltinFnIdBitReverse);
    return LLVMBuildCall(g->builder, fn_val, &op, 1, "");
}

static LLVMValueRef ir_render_vector_to_array(CodeGen *g, IrExecutable *executable,
        IrInstructionVectorToArray *instruction)
{
    ZigType *array_type = instruction->base.value->type;
    assert(array_type->id == ZigTypeIdArray);
    assert(handle_is_ptr(array_type));
    LLVMValueRef result_loc = ir_llvm_value(g, instruction->result_loc);
    LLVMValueRef vector = ir_llvm_value(g, instruction->vector);

    ZigType *elem_type = array_type->data.array.child_type;
    bool bitcast_ok = elem_type->size_in_bits == elem_type->abi_size * 8;
    if (bitcast_ok) {
        LLVMValueRef casted_ptr = LLVMBuildBitCast(g->builder, result_loc,
                LLVMPointerType(get_llvm_type(g, instruction->vector->value->type), 0), "");
        uint32_t alignment = get_ptr_align(g, instruction->result_loc->value->type);
        gen_store_untyped(g, vector, casted_ptr, alignment, false);
    } else {
        // If the ABI size of the element type is not evenly divisible by size_in_bits, a simple bitcast
        // will not work, and we fall back to extractelement.
        LLVMTypeRef usize_type_ref = g->builtin_types.entry_usize->llvm_type;
        LLVMTypeRef u32_type_ref = LLVMInt32Type();
        LLVMValueRef zero = LLVMConstInt(usize_type_ref, 0, false);
        for (uintptr_t i = 0; i < instruction->vector->value->type->data.vector.len; i++) {
            LLVMValueRef index_usize = LLVMConstInt(usize_type_ref, i, false);
            LLVMValueRef index_u32 = LLVMConstInt(u32_type_ref, i, false);
            LLVMValueRef indexes[] = { zero, index_usize };
            LLVMValueRef elem_ptr = LLVMBuildInBoundsGEP(g->builder, result_loc, indexes, 2, "");
            LLVMValueRef elem = LLVMBuildExtractElement(g->builder, vector, index_u32, "");
            LLVMBuildStore(g->builder, elem, elem_ptr);
        }
    }
    return result_loc;
}

static LLVMValueRef ir_render_array_to_vector(CodeGen *g, IrExecutable *executable,
        IrInstructionArrayToVector *instruction)
{
    ZigType *vector_type = instruction->base.value->type;
    assert(vector_type->id == ZigTypeIdVector);
    assert(!handle_is_ptr(vector_type));
    LLVMValueRef array_ptr = ir_llvm_value(g, instruction->array);
    LLVMTypeRef vector_type_ref = get_llvm_type(g, vector_type);

    ZigType *elem_type = vector_type->data.vector.elem_type;
    bool bitcast_ok = elem_type->size_in_bits == elem_type->abi_size * 8;
    if (bitcast_ok) {
        LLVMValueRef casted_ptr = LLVMBuildBitCast(g->builder, array_ptr,
                LLVMPointerType(vector_type_ref, 0), "");
        ZigType *array_type = instruction->array->value->type;
        assert(array_type->id == ZigTypeIdArray);
        uint32_t alignment = get_abi_alignment(g, array_type->data.array.child_type);
        return gen_load_untyped(g, casted_ptr, alignment, false, "");
    } else {
        // If the ABI size of the element type is not evenly divisible by size_in_bits, a simple bitcast
        // will not work, and we fall back to insertelement.
        LLVMTypeRef usize_type_ref = g->builtin_types.entry_usize->llvm_type;
        LLVMTypeRef u32_type_ref = LLVMInt32Type();
        LLVMValueRef zero = LLVMConstInt(usize_type_ref, 0, false);
        LLVMValueRef vector = LLVMGetUndef(vector_type_ref);
        for (uintptr_t i = 0; i < instruction->base.value->type->data.vector.len; i++) {
            LLVMValueRef index_usize = LLVMConstInt(usize_type_ref, i, false);
            LLVMValueRef index_u32 = LLVMConstInt(u32_type_ref, i, false);
            LLVMValueRef indexes[] = { zero, index_usize };
            LLVMValueRef elem_ptr = LLVMBuildInBoundsGEP(g->builder, array_ptr, indexes, 2, "");
            LLVMValueRef elem = LLVMBuildLoad(g->builder, elem_ptr, "");
            vector = LLVMBuildInsertElement(g->builder, vector, elem, index_u32, "");
        }
        return vector;
    }
}

static LLVMValueRef ir_render_assert_zero(CodeGen *g, IrExecutable *executable,
        IrInstructionAssertZero *instruction)
{
    LLVMValueRef target = ir_llvm_value(g, instruction->target);
    ZigType *int_type = instruction->target->value->type;
    if (ir_want_runtime_safety(g, &instruction->base)) {
        return gen_assert_zero(g, target, int_type);
    }
    return nullptr;
}

static LLVMValueRef ir_render_assert_non_null(CodeGen *g, IrExecutable *executable,
        IrInstructionAssertNonNull *instruction)
{
    LLVMValueRef target = ir_llvm_value(g, instruction->target);
    ZigType *target_type = instruction->target->value->type;

    if (target_type->id == ZigTypeIdPointer) {
        assert(target_type->data.pointer.ptr_len == PtrLenC);
        LLVMValueRef non_null_bit = LLVMBuildICmp(g->builder, LLVMIntNE, target,
                LLVMConstNull(get_llvm_type(g, target_type)), "");

        LLVMBasicBlockRef fail_block = LLVMAppendBasicBlock(g->cur_fn_val, "AssertNonNullFail");
        LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "AssertNonNullOk");
        LLVMBuildCondBr(g->builder, non_null_bit, ok_block, fail_block);

        LLVMPositionBuilderAtEnd(g->builder, fail_block);
        gen_assertion(g, PanicMsgIdUnwrapOptionalFail, &instruction->base);

        LLVMPositionBuilderAtEnd(g->builder, ok_block);
    } else {
        zig_unreachable();
    }
    return nullptr;
}

static LLVMValueRef ir_render_suspend_begin(CodeGen *g, IrExecutable *executable,
        IrInstructionSuspendBegin *instruction)
{
    if (fn_is_async(g->cur_fn)) {
        instruction->resume_bb = gen_suspend_begin(g, "SuspendResume");
    }
    return nullptr;
}

static LLVMValueRef ir_render_suspend_finish(CodeGen *g, IrExecutable *executable,
        IrInstructionSuspendFinish *instruction)
{
    LLVMBuildRetVoid(g->builder);

    LLVMPositionBuilderAtEnd(g->builder, instruction->begin->resume_bb);
    if (ir_want_runtime_safety(g, &instruction->base)) {
        LLVMBuildStore(g->builder, g->cur_bad_not_suspended_index, g->cur_async_resume_index_ptr);
    }
    render_async_var_decls(g, instruction->base.scope);
    return nullptr;
}

static LLVMValueRef gen_await_early_return(CodeGen *g, IrInstruction *source_instr,
        LLVMValueRef target_frame_ptr, ZigType *result_type, ZigType *ptr_result_type,
        LLVMValueRef result_loc, bool non_async)
{
    LLVMTypeRef usize_type_ref = g->builtin_types.entry_usize->llvm_type;
    LLVMValueRef their_result_ptr = nullptr;
    if (type_has_bits(result_type) && (non_async || result_loc != nullptr)) {
        LLVMValueRef their_result_ptr_ptr = LLVMBuildStructGEP(g->builder, target_frame_ptr, frame_ret_start, "");
        their_result_ptr = LLVMBuildLoad(g->builder, their_result_ptr_ptr, "");
        if (result_loc != nullptr) {
            LLVMTypeRef ptr_u8 = LLVMPointerType(LLVMInt8Type(), 0);
            LLVMValueRef dest_ptr_casted = LLVMBuildBitCast(g->builder, result_loc, ptr_u8, "");
            LLVMValueRef src_ptr_casted = LLVMBuildBitCast(g->builder, their_result_ptr, ptr_u8, "");
            bool is_volatile = false;
            uint32_t abi_align = get_abi_alignment(g, result_type);
            LLVMValueRef byte_count_val = LLVMConstInt(usize_type_ref, type_size(g, result_type), false);
            ZigLLVMBuildMemCpy(g->builder,
                    dest_ptr_casted, abi_align,
                    src_ptr_casted, abi_align, byte_count_val, is_volatile);
        }
    }
    if (codegen_fn_has_err_ret_tracing_arg(g, result_type)) {
        LLVMValueRef their_trace_ptr_ptr = LLVMBuildStructGEP(g->builder, target_frame_ptr,
                frame_index_trace_arg(g, result_type), "");
        LLVMValueRef src_trace_ptr = LLVMBuildLoad(g->builder, their_trace_ptr_ptr, "");
        bool is_llvm_alloca;
        LLVMValueRef dest_trace_ptr = get_cur_err_ret_trace_val(g, source_instr->scope, &is_llvm_alloca);
        LLVMValueRef args[] = { dest_trace_ptr, src_trace_ptr };
        ZigLLVMBuildCall(g->builder, get_merge_err_ret_traces_fn_val(g), args, 2,
                get_llvm_cc(g, CallingConventionUnspecified), ZigLLVM_CallAttrAuto, "");
    }
    if (non_async && type_has_bits(result_type)) {
        LLVMValueRef result_ptr = (result_loc == nullptr) ? their_result_ptr : result_loc;
        return get_handle_value(g, result_ptr, result_type, ptr_result_type);
    } else {
        return nullptr;
    }
}

static LLVMValueRef ir_render_await(CodeGen *g, IrExecutable *executable, IrInstructionAwaitGen *instruction) {
    LLVMTypeRef usize_type_ref = g->builtin_types.entry_usize->llvm_type;
    LLVMValueRef zero = LLVMConstNull(usize_type_ref);
    LLVMValueRef target_frame_ptr = ir_llvm_value(g, instruction->frame);
    ZigType *result_type = instruction->base.value->type;
    ZigType *ptr_result_type = get_pointer_to_type(g, result_type, true);

    LLVMValueRef result_loc = (instruction->result_loc == nullptr) ?
        nullptr : ir_llvm_value(g, instruction->result_loc);

    if (instruction->target_fn != nullptr && !fn_is_async(instruction->target_fn)) {
        return gen_await_early_return(g, &instruction->base, target_frame_ptr, result_type,
                ptr_result_type, result_loc, true);
    }

    // Prepare to be suspended
    LLVMBasicBlockRef resume_bb = gen_suspend_begin(g, "AwaitResume");
    LLVMBasicBlockRef end_bb = LLVMAppendBasicBlock(g->cur_fn_val, "AwaitEnd");

    // At this point resuming the function will continue from resume_bb.
    // This code is as if it is running inside the suspend block.

    // supply the awaiter return pointer
    if (type_has_bits(result_type)) {
        LLVMValueRef awaiter_ret_ptr_ptr = LLVMBuildStructGEP(g->builder, target_frame_ptr, frame_ret_start + 1, "");
        if (result_loc == nullptr) {
            // no copy needed
            LLVMBuildStore(g->builder, LLVMConstNull(LLVMGetElementType(LLVMTypeOf(awaiter_ret_ptr_ptr))),
                    awaiter_ret_ptr_ptr);
        } else {
            LLVMBuildStore(g->builder, result_loc, awaiter_ret_ptr_ptr);
        }
    }

    // supply the error return trace pointer
    if (codegen_fn_has_err_ret_tracing_arg(g, result_type)) {
        bool is_llvm_alloca;
        LLVMValueRef my_err_ret_trace_val = get_cur_err_ret_trace_val(g, instruction->base.scope, &is_llvm_alloca);
        assert(my_err_ret_trace_val != nullptr);
        LLVMValueRef err_ret_trace_ptr_ptr = LLVMBuildStructGEP(g->builder, target_frame_ptr,
                frame_index_trace_arg(g, result_type) + 1, "");
        LLVMBuildStore(g->builder, my_err_ret_trace_val, err_ret_trace_ptr_ptr);
    }

    // caller's own frame pointer
    LLVMValueRef awaiter_init_val = LLVMBuildPtrToInt(g->builder, g->cur_frame_ptr, usize_type_ref, "");
    LLVMValueRef awaiter_ptr = LLVMBuildStructGEP(g->builder, target_frame_ptr, frame_awaiter_index, "");
    LLVMValueRef prev_val = gen_maybe_atomic_op(g, LLVMAtomicRMWBinOpXchg, awaiter_ptr, awaiter_init_val,
            LLVMAtomicOrderingRelease);

    LLVMBasicBlockRef bad_await_block = LLVMAppendBasicBlock(g->cur_fn_val, "BadAwait");
    LLVMBasicBlockRef complete_suspend_block = LLVMAppendBasicBlock(g->cur_fn_val, "CompleteSuspend");
    LLVMBasicBlockRef early_return_block = LLVMAppendBasicBlock(g->cur_fn_val, "EarlyReturn");

    LLVMValueRef all_ones = LLVMConstAllOnes(usize_type_ref);
    LLVMValueRef switch_instr = LLVMBuildSwitch(g->builder, prev_val, bad_await_block, 2);

    LLVMAddCase(switch_instr, zero, complete_suspend_block);
    LLVMAddCase(switch_instr, all_ones, early_return_block);

    // We discovered that another awaiter was already here.
    LLVMPositionBuilderAtEnd(g->builder, bad_await_block);
    gen_assertion(g, PanicMsgIdBadAwait, &instruction->base);

    // Rely on the target to resume us from suspension.
    LLVMPositionBuilderAtEnd(g->builder, complete_suspend_block);
    LLVMBuildRetVoid(g->builder);

    // Early return: The async function has already completed. We must copy the result and
    // the error return trace if applicable.
    LLVMPositionBuilderAtEnd(g->builder, early_return_block);
    gen_await_early_return(g, &instruction->base, target_frame_ptr, result_type, ptr_result_type,
            result_loc, false);
    LLVMBuildBr(g->builder, end_bb);

    LLVMPositionBuilderAtEnd(g->builder, resume_bb);
    gen_assert_resume_id(g, &instruction->base, ResumeIdReturn, PanicMsgIdResumedAnAwaitingFn, nullptr);
    LLVMBuildBr(g->builder, end_bb);

    LLVMPositionBuilderAtEnd(g->builder, end_bb);
    // Rely on the spill for the llvm_value to be populated.
    // See the implementation of ir_llvm_value.
    return nullptr;
}

static LLVMValueRef ir_render_resume(CodeGen *g, IrExecutable *executable, IrInstructionResume *instruction) {
    LLVMValueRef frame = ir_llvm_value(g, instruction->frame);
    ZigType *frame_type = instruction->frame->value->type;
    assert(frame_type->id == ZigTypeIdAnyFrame);

    gen_resume(g, nullptr, frame, ResumeIdManual);
    return nullptr;
}

static LLVMValueRef ir_render_frame_size(CodeGen *g, IrExecutable *executable,
        IrInstructionFrameSizeGen *instruction)
{
    LLVMValueRef fn_val = ir_llvm_value(g, instruction->fn);
    return gen_frame_size(g, fn_val);
}

static LLVMValueRef ir_render_spill_begin(CodeGen *g, IrExecutable *executable,
        IrInstructionSpillBegin *instruction)
{
    if (!fn_is_async(g->cur_fn))
        return nullptr;

    switch (instruction->spill_id) {
        case SpillIdInvalid:
            zig_unreachable();
        case SpillIdRetErrCode: {
            LLVMValueRef operand = ir_llvm_value(g, instruction->operand);
            LLVMValueRef ptr = ir_llvm_value(g, g->cur_fn->err_code_spill);
            LLVMBuildStore(g->builder, operand, ptr);
            return nullptr;
        }

    }
    zig_unreachable();
}

static LLVMValueRef ir_render_spill_end(CodeGen *g, IrExecutable *executable, IrInstructionSpillEnd *instruction) {
    if (!fn_is_async(g->cur_fn))
        return ir_llvm_value(g, instruction->begin->operand);

    switch (instruction->begin->spill_id) {
        case SpillIdInvalid:
            zig_unreachable();
        case SpillIdRetErrCode: {
            LLVMValueRef ptr = ir_llvm_value(g, g->cur_fn->err_code_spill);
            return LLVMBuildLoad(g->builder, ptr, "");
        }

    }
    zig_unreachable();
}

static LLVMValueRef ir_render_vector_extract_elem(CodeGen *g, IrExecutable *executable,
        IrInstructionVectorExtractElem *instruction)
{
    LLVMValueRef vector = ir_llvm_value(g, instruction->vector);
    LLVMValueRef index = ir_llvm_value(g, instruction->index);
    return LLVMBuildExtractElement(g->builder, vector, index, "");
}

static void set_debug_location(CodeGen *g, IrInstruction *instruction) {
    AstNode *source_node = instruction->source_node;
    Scope *scope = instruction->scope;

    assert(source_node);
    assert(scope);

    ZigLLVMSetCurrentDebugLocation(g->builder, (int)source_node->line + 1,
            (int)source_node->column + 1, get_di_scope(g, scope));
}

static LLVMValueRef ir_render_instruction(CodeGen *g, IrExecutable *executable, IrInstruction *instruction) {
    switch (instruction->id) {
        case IrInstructionIdInvalid:
        case IrInstructionIdConst:
        case IrInstructionIdTypeOf:
        case IrInstructionIdFieldPtr:
        case IrInstructionIdSetCold:
        case IrInstructionIdSetRuntimeSafety:
        case IrInstructionIdSetFloatMode:
        case IrInstructionIdArrayType:
        case IrInstructionIdAnyFrameType:
        case IrInstructionIdSliceType:
        case IrInstructionIdSizeOf:
        case IrInstructionIdSwitchTarget:
        case IrInstructionIdContainerInitFields:
        case IrInstructionIdCompileErr:
        case IrInstructionIdCompileLog:
        case IrInstructionIdImport:
        case IrInstructionIdCImport:
        case IrInstructionIdCInclude:
        case IrInstructionIdCDefine:
        case IrInstructionIdCUndef:
        case IrInstructionIdEmbedFile:
        case IrInstructionIdIntType:
        case IrInstructionIdVectorType:
        case IrInstructionIdMemberCount:
        case IrInstructionIdMemberType:
        case IrInstructionIdMemberName:
        case IrInstructionIdAlignOf:
        case IrInstructionIdFnProto:
        case IrInstructionIdTestComptime:
        case IrInstructionIdCheckSwitchProngs:
        case IrInstructionIdCheckStatementIsVoid:
        case IrInstructionIdTypeName:
        case IrInstructionIdDeclRef:
        case IrInstructionIdSwitchVar:
        case IrInstructionIdSwitchElseVar:
        case IrInstructionIdByteOffsetOf:
        case IrInstructionIdBitOffsetOf:
        case IrInstructionIdTypeInfo:
        case IrInstructionIdType:
        case IrInstructionIdHasField:
        case IrInstructionIdTypeId:
        case IrInstructionIdSetEvalBranchQuota:
        case IrInstructionIdPtrType:
        case IrInstructionIdOpaqueType:
        case IrInstructionIdSetAlignStack:
        case IrInstructionIdArgType:
        case IrInstructionIdTagType:
        case IrInstructionIdExport:
        case IrInstructionIdErrorUnion:
        case IrInstructionIdAddImplicitReturnType:
        case IrInstructionIdIntCast:
        case IrInstructionIdFloatCast:
        case IrInstructionIdIntToFloat:
        case IrInstructionIdFloatToInt:
        case IrInstructionIdBoolToInt:
        case IrInstructionIdErrSetCast:
        case IrInstructionIdFromBytes:
        case IrInstructionIdToBytes:
        case IrInstructionIdEnumToInt:
        case IrInstructionIdCheckRuntimeScope:
        case IrInstructionIdDeclVarSrc:
        case IrInstructionIdPtrCastSrc:
        case IrInstructionIdCmpxchgSrc:
        case IrInstructionIdLoadPtr:
        case IrInstructionIdHasDecl:
        case IrInstructionIdUndeclaredIdent:
        case IrInstructionIdCallExtra:
        case IrInstructionIdCallSrc:
        case IrInstructionIdCallSrcArgs:
        case IrInstructionIdAllocaSrc:
        case IrInstructionIdEndExpr:
        case IrInstructionIdImplicitCast:
        case IrInstructionIdResolveResult:
        case IrInstructionIdResetResult:
        case IrInstructionIdContainerInitList:
        case IrInstructionIdSliceSrc:
        case IrInstructionIdRef:
        case IrInstructionIdBitCastSrc:
        case IrInstructionIdTestErrSrc:
        case IrInstructionIdUnionInitNamedField:
        case IrInstructionIdFrameType:
        case IrInstructionIdFrameSizeSrc:
        case IrInstructionIdAllocaGen:
        case IrInstructionIdAwaitSrc:
        case IrInstructionIdSplatSrc:
        case IrInstructionIdMergeErrSets:
        case IrInstructionIdAsmSrc:
            zig_unreachable();

        case IrInstructionIdDeclVarGen:
            return ir_render_decl_var(g, executable, (IrInstructionDeclVarGen *)instruction);
        case IrInstructionIdReturn:
            return ir_render_return(g, executable, (IrInstructionReturn *)instruction);
        case IrInstructionIdBinOp:
            return ir_render_bin_op(g, executable, (IrInstructionBinOp *)instruction);
        case IrInstructionIdCast:
            return ir_render_cast(g, executable, (IrInstructionCast *)instruction);
        case IrInstructionIdUnreachable:
            return ir_render_unreachable(g, executable, (IrInstructionUnreachable *)instruction);
        case IrInstructionIdCondBr:
            return ir_render_cond_br(g, executable, (IrInstructionCondBr *)instruction);
        case IrInstructionIdBr:
            return ir_render_br(g, executable, (IrInstructionBr *)instruction);
        case IrInstructionIdUnOp:
            return ir_render_un_op(g, executable, (IrInstructionUnOp *)instruction);
        case IrInstructionIdLoadPtrGen:
            return ir_render_load_ptr(g, executable, (IrInstructionLoadPtrGen *)instruction);
        case IrInstructionIdStorePtr:
            return ir_render_store_ptr(g, executable, (IrInstructionStorePtr *)instruction);
        case IrInstructionIdVectorStoreElem:
            return ir_render_vector_store_elem(g, executable, (IrInstructionVectorStoreElem *)instruction);
        case IrInstructionIdVarPtr:
            return ir_render_var_ptr(g, executable, (IrInstructionVarPtr *)instruction);
        case IrInstructionIdReturnPtr:
            return ir_render_return_ptr(g, executable, (IrInstructionReturnPtr *)instruction);
        case IrInstructionIdElemPtr:
            return ir_render_elem_ptr(g, executable, (IrInstructionElemPtr *)instruction);
        case IrInstructionIdCallGen:
            return ir_render_call(g, executable, (IrInstructionCallGen *)instruction);
        case IrInstructionIdStructFieldPtr:
            return ir_render_struct_field_ptr(g, executable, (IrInstructionStructFieldPtr *)instruction);
        case IrInstructionIdUnionFieldPtr:
            return ir_render_union_field_ptr(g, executable, (IrInstructionUnionFieldPtr *)instruction);
        case IrInstructionIdAsmGen:
            return ir_render_asm_gen(g, executable, (IrInstructionAsmGen *)instruction);
        case IrInstructionIdTestNonNull:
            return ir_render_test_non_null(g, executable, (IrInstructionTestNonNull *)instruction);
        case IrInstructionIdOptionalUnwrapPtr:
            return ir_render_optional_unwrap_ptr(g, executable, (IrInstructionOptionalUnwrapPtr *)instruction);
        case IrInstructionIdClz:
            return ir_render_clz(g, executable, (IrInstructionClz *)instruction);
        case IrInstructionIdCtz:
            return ir_render_ctz(g, executable, (IrInstructionCtz *)instruction);
        case IrInstructionIdPopCount:
            return ir_render_pop_count(g, executable, (IrInstructionPopCount *)instruction);
        case IrInstructionIdSwitchBr:
            return ir_render_switch_br(g, executable, (IrInstructionSwitchBr *)instruction);
        case IrInstructionIdBswap:
            return ir_render_bswap(g, executable, (IrInstructionBswap *)instruction);
        case IrInstructionIdBitReverse:
            return ir_render_bit_reverse(g, executable, (IrInstructionBitReverse *)instruction);
        case IrInstructionIdPhi:
            return ir_render_phi(g, executable, (IrInstructionPhi *)instruction);
        case IrInstructionIdRefGen:
            return ir_render_ref(g, executable, (IrInstructionRefGen *)instruction);
        case IrInstructionIdErrName:
            return ir_render_err_name(g, executable, (IrInstructionErrName *)instruction);
        case IrInstructionIdCmpxchgGen:
            return ir_render_cmpxchg(g, executable, (IrInstructionCmpxchgGen *)instruction);
        case IrInstructionIdFence:
            return ir_render_fence(g, executable, (IrInstructionFence *)instruction);
        case IrInstructionIdTruncate:
            return ir_render_truncate(g, executable, (IrInstructionTruncate *)instruction);
        case IrInstructionIdBoolNot:
            return ir_render_bool_not(g, executable, (IrInstructionBoolNot *)instruction);
        case IrInstructionIdMemset:
            return ir_render_memset(g, executable, (IrInstructionMemset *)instruction);
        case IrInstructionIdMemcpy:
            return ir_render_memcpy(g, executable, (IrInstructionMemcpy *)instruction);
        case IrInstructionIdSliceGen:
            return ir_render_slice(g, executable, (IrInstructionSliceGen *)instruction);
        case IrInstructionIdBreakpoint:
            return ir_render_breakpoint(g, executable, (IrInstructionBreakpoint *)instruction);
        case IrInstructionIdReturnAddress:
            return ir_render_return_address(g, executable, (IrInstructionReturnAddress *)instruction);
        case IrInstructionIdFrameAddress:
            return ir_render_frame_address(g, executable, (IrInstructionFrameAddress *)instruction);
        case IrInstructionIdFrameHandle:
            return ir_render_handle(g, executable, (IrInstructionFrameHandle *)instruction);
        case IrInstructionIdOverflowOp:
            return ir_render_overflow_op(g, executable, (IrInstructionOverflowOp *)instruction);
        case IrInstructionIdTestErrGen:
            return ir_render_test_err(g, executable, (IrInstructionTestErrGen *)instruction);
        case IrInstructionIdUnwrapErrCode:
            return ir_render_unwrap_err_code(g, executable, (IrInstructionUnwrapErrCode *)instruction);
        case IrInstructionIdUnwrapErrPayload:
            return ir_render_unwrap_err_payload(g, executable, (IrInstructionUnwrapErrPayload *)instruction);
        case IrInstructionIdOptionalWrap:
            return ir_render_optional_wrap(g, executable, (IrInstructionOptionalWrap *)instruction);
        case IrInstructionIdErrWrapCode:
            return ir_render_err_wrap_code(g, executable, (IrInstructionErrWrapCode *)instruction);
        case IrInstructionIdErrWrapPayload:
            return ir_render_err_wrap_payload(g, executable, (IrInstructionErrWrapPayload *)instruction);
        case IrInstructionIdUnionTag:
            return ir_render_union_tag(g, executable, (IrInstructionUnionTag *)instruction);
        case IrInstructionIdPtrCastGen:
            return ir_render_ptr_cast(g, executable, (IrInstructionPtrCastGen *)instruction);
        case IrInstructionIdBitCastGen:
            return ir_render_bit_cast(g, executable, (IrInstructionBitCastGen *)instruction);
        case IrInstructionIdWidenOrShorten:
            return ir_render_widen_or_shorten(g, executable, (IrInstructionWidenOrShorten *)instruction);
        case IrInstructionIdPtrToInt:
            return ir_render_ptr_to_int(g, executable, (IrInstructionPtrToInt *)instruction);
        case IrInstructionIdIntToPtr:
            return ir_render_int_to_ptr(g, executable, (IrInstructionIntToPtr *)instruction);
        case IrInstructionIdIntToEnum:
            return ir_render_int_to_enum(g, executable, (IrInstructionIntToEnum *)instruction);
        case IrInstructionIdIntToErr:
            return ir_render_int_to_err(g, executable, (IrInstructionIntToErr *)instruction);
        case IrInstructionIdErrToInt:
            return ir_render_err_to_int(g, executable, (IrInstructionErrToInt *)instruction);
        case IrInstructionIdPanic:
            return ir_render_panic(g, executable, (IrInstructionPanic *)instruction);
        case IrInstructionIdTagName:
            return ir_render_enum_tag_name(g, executable, (IrInstructionTagName *)instruction);
        case IrInstructionIdFieldParentPtr:
            return ir_render_field_parent_ptr(g, executable, (IrInstructionFieldParentPtr *)instruction);
        case IrInstructionIdAlignCast:
            return ir_render_align_cast(g, executable, (IrInstructionAlignCast *)instruction);
        case IrInstructionIdErrorReturnTrace:
            return ir_render_error_return_trace(g, executable, (IrInstructionErrorReturnTrace *)instruction);
        case IrInstructionIdAtomicRmw:
            return ir_render_atomic_rmw(g, executable, (IrInstructionAtomicRmw *)instruction);
        case IrInstructionIdAtomicLoad:
            return ir_render_atomic_load(g, executable, (IrInstructionAtomicLoad *)instruction);
        case IrInstructionIdAtomicStore:
            return ir_render_atomic_store(g, executable, (IrInstructionAtomicStore *)instruction);
        case IrInstructionIdSaveErrRetAddr:
            return ir_render_save_err_ret_addr(g, executable, (IrInstructionSaveErrRetAddr *)instruction);
        case IrInstructionIdFloatOp:
            return ir_render_float_op(g, executable, (IrInstructionFloatOp *)instruction);
        case IrInstructionIdMulAdd:
            return ir_render_mul_add(g, executable, (IrInstructionMulAdd *)instruction);
        case IrInstructionIdArrayToVector:
            return ir_render_array_to_vector(g, executable, (IrInstructionArrayToVector *)instruction);
        case IrInstructionIdVectorToArray:
            return ir_render_vector_to_array(g, executable, (IrInstructionVectorToArray *)instruction);
        case IrInstructionIdAssertZero:
            return ir_render_assert_zero(g, executable, (IrInstructionAssertZero *)instruction);
        case IrInstructionIdAssertNonNull:
            return ir_render_assert_non_null(g, executable, (IrInstructionAssertNonNull *)instruction);
        case IrInstructionIdResizeSlice:
            return ir_render_resize_slice(g, executable, (IrInstructionResizeSlice *)instruction);
        case IrInstructionIdPtrOfArrayToSlice:
            return ir_render_ptr_of_array_to_slice(g, executable, (IrInstructionPtrOfArrayToSlice *)instruction);
        case IrInstructionIdSuspendBegin:
            return ir_render_suspend_begin(g, executable, (IrInstructionSuspendBegin *)instruction);
        case IrInstructionIdSuspendFinish:
            return ir_render_suspend_finish(g, executable, (IrInstructionSuspendFinish *)instruction);
        case IrInstructionIdResume:
            return ir_render_resume(g, executable, (IrInstructionResume *)instruction);
        case IrInstructionIdFrameSizeGen:
            return ir_render_frame_size(g, executable, (IrInstructionFrameSizeGen *)instruction);
        case IrInstructionIdAwaitGen:
            return ir_render_await(g, executable, (IrInstructionAwaitGen *)instruction);
        case IrInstructionIdSpillBegin:
            return ir_render_spill_begin(g, executable, (IrInstructionSpillBegin *)instruction);
        case IrInstructionIdSpillEnd:
            return ir_render_spill_end(g, executable, (IrInstructionSpillEnd *)instruction);
        case IrInstructionIdShuffleVector:
            return ir_render_shuffle_vector(g, executable, (IrInstructionShuffleVector *) instruction);
        case IrInstructionIdSplatGen:
            return ir_render_splat(g, executable, (IrInstructionSplatGen *) instruction);
        case IrInstructionIdVectorExtractElem:
            return ir_render_vector_extract_elem(g, executable, (IrInstructionVectorExtractElem *) instruction);
    }
    zig_unreachable();
}

static void ir_render(CodeGen *g, ZigFn *fn_entry) {
    assert(fn_entry);

    IrExecutable *executable = &fn_entry->analyzed_executable;
    assert(executable->basic_block_list.length > 0);

    for (size_t block_i = 0; block_i < executable->basic_block_list.length; block_i += 1) {
        IrBasicBlock *current_block = executable->basic_block_list.at(block_i);
        if (get_scope_typeof(current_block->scope) != nullptr) {
            LLVMBuildBr(g->builder, current_block->llvm_block);
        }
        assert(current_block->llvm_block);
        LLVMPositionBuilderAtEnd(g->builder, current_block->llvm_block);
        for (size_t instr_i = 0; instr_i < current_block->instruction_list.length; instr_i += 1) {
            IrInstruction *instruction = current_block->instruction_list.at(instr_i);
            if (instruction->ref_count == 0 && !ir_has_side_effects(instruction))
                continue;
            if (get_scope_typeof(instruction->scope) != nullptr)
                continue;

            if (!g->strip_debug_symbols) {
                set_debug_location(g, instruction);
            }
            instruction->llvm_value = ir_render_instruction(g, executable, instruction);
            if (instruction->spill != nullptr) {
                LLVMValueRef spill_ptr = ir_llvm_value(g, instruction->spill);
                gen_assign_raw(g, spill_ptr, instruction->spill->value->type, instruction->llvm_value);
                instruction->llvm_value = nullptr;
            }
        }
        current_block->llvm_exit_block = LLVMGetInsertBlock(g->builder);
    }
}

static LLVMValueRef gen_const_ptr_struct_recursive(CodeGen *g, ZigValue *struct_const_val, size_t field_index);
static LLVMValueRef gen_const_ptr_array_recursive(CodeGen *g, ZigValue *array_const_val, size_t index);
static LLVMValueRef gen_const_ptr_union_recursive(CodeGen *g, ZigValue *union_const_val);
static LLVMValueRef gen_const_ptr_err_union_code_recursive(CodeGen *g, ZigValue *err_union_const_val);
static LLVMValueRef gen_const_ptr_err_union_payload_recursive(CodeGen *g, ZigValue *err_union_const_val);
static LLVMValueRef gen_const_ptr_optional_payload_recursive(CodeGen *g, ZigValue *optional_const_val);

static LLVMValueRef gen_parent_ptr(CodeGen *g, ZigValue *val, ConstParent *parent) {
    switch (parent->id) {
        case ConstParentIdNone:
            render_const_val(g, val, "");
            render_const_val_global(g, val, "");
            return val->llvm_global;
        case ConstParentIdStruct:
            return gen_const_ptr_struct_recursive(g, parent->data.p_struct.struct_val,
                    parent->data.p_struct.field_index);
        case ConstParentIdErrUnionCode:
            return gen_const_ptr_err_union_code_recursive(g, parent->data.p_err_union_code.err_union_val);
        case ConstParentIdErrUnionPayload:
            return gen_const_ptr_err_union_payload_recursive(g, parent->data.p_err_union_payload.err_union_val);
        case ConstParentIdOptionalPayload:
            return gen_const_ptr_optional_payload_recursive(g, parent->data.p_optional_payload.optional_val);
        case ConstParentIdArray:
            return gen_const_ptr_array_recursive(g, parent->data.p_array.array_val,
                    parent->data.p_array.elem_index);
        case ConstParentIdUnion:
            return gen_const_ptr_union_recursive(g, parent->data.p_union.union_val);
        case ConstParentIdScalar:
            render_const_val(g, parent->data.p_scalar.scalar_val, "");
            render_const_val_global(g, parent->data.p_scalar.scalar_val, "");
            return parent->data.p_scalar.scalar_val->llvm_global;
    }
    zig_unreachable();
}

static LLVMValueRef gen_const_ptr_array_recursive(CodeGen *g, ZigValue *array_const_val, size_t index) {
    expand_undef_array(g, array_const_val);
    ConstParent *parent = &array_const_val->parent;
    LLVMValueRef base_ptr = gen_parent_ptr(g, array_const_val, parent);

    LLVMTypeKind el_type = LLVMGetTypeKind(LLVMGetElementType(LLVMTypeOf(base_ptr)));
    if (el_type == LLVMArrayTypeKind) {
        ZigType *usize = g->builtin_types.entry_usize;
        LLVMValueRef indices[] = {
            LLVMConstNull(usize->llvm_type),
            LLVMConstInt(usize->llvm_type, index, false),
        };
        return LLVMConstInBoundsGEP(base_ptr, indices, 2);
    } else if (el_type == LLVMStructTypeKind) {
        ZigType *u32 = g->builtin_types.entry_u32;
        LLVMValueRef indices[] = {
            LLVMConstNull(get_llvm_type(g, u32)),
            LLVMConstInt(get_llvm_type(g, u32), index, false),
        };
        return LLVMConstInBoundsGEP(base_ptr, indices, 2);
    } else {
        assert(parent->id == ConstParentIdScalar);
        return base_ptr;
    }
}

static LLVMValueRef gen_const_ptr_struct_recursive(CodeGen *g, ZigValue *struct_const_val, size_t field_index) {
    ConstParent *parent = &struct_const_val->parent;
    LLVMValueRef base_ptr = gen_parent_ptr(g, struct_const_val, parent);

    ZigType *u32 = g->builtin_types.entry_u32;
    LLVMValueRef indices[] = {
        LLVMConstNull(get_llvm_type(g, u32)),
        LLVMConstInt(get_llvm_type(g, u32), field_index, false),
    };
    return LLVMConstInBoundsGEP(base_ptr, indices, 2);
}

static LLVMValueRef gen_const_ptr_err_union_code_recursive(CodeGen *g, ZigValue *err_union_const_val) {
    ConstParent *parent = &err_union_const_val->parent;
    LLVMValueRef base_ptr = gen_parent_ptr(g, err_union_const_val, parent);

    ZigType *u32 = g->builtin_types.entry_u32;
    LLVMValueRef indices[] = {
        LLVMConstNull(get_llvm_type(g, u32)),
        LLVMConstInt(get_llvm_type(g, u32), err_union_err_index, false),
    };
    return LLVMConstInBoundsGEP(base_ptr, indices, 2);
}

static LLVMValueRef gen_const_ptr_err_union_payload_recursive(CodeGen *g, ZigValue *err_union_const_val) {
    ConstParent *parent = &err_union_const_val->parent;
    LLVMValueRef base_ptr = gen_parent_ptr(g, err_union_const_val, parent);

    ZigType *u32 = g->builtin_types.entry_u32;
    LLVMValueRef indices[] = {
        LLVMConstNull(get_llvm_type(g, u32)),
        LLVMConstInt(get_llvm_type(g, u32), err_union_payload_index, false),
    };
    return LLVMConstInBoundsGEP(base_ptr, indices, 2);
}

static LLVMValueRef gen_const_ptr_optional_payload_recursive(CodeGen *g, ZigValue *optional_const_val) {
    ConstParent *parent = &optional_const_val->parent;
    LLVMValueRef base_ptr = gen_parent_ptr(g, optional_const_val, parent);

    ZigType *u32 = g->builtin_types.entry_u32;
    LLVMValueRef indices[] = {
        LLVMConstNull(get_llvm_type(g, u32)),
        LLVMConstInt(get_llvm_type(g, u32), maybe_child_index, false),
    };
    return LLVMConstInBoundsGEP(base_ptr, indices, 2);
}

static LLVMValueRef gen_const_ptr_union_recursive(CodeGen *g, ZigValue *union_const_val) {
    ConstParent *parent = &union_const_val->parent;
    LLVMValueRef base_ptr = gen_parent_ptr(g, union_const_val, parent);

    // Slot in the structure where the payload is stored, if equal to SIZE_MAX
    // the union has no tag and a single field and is collapsed into the field
    // itself
    size_t union_payload_index = union_const_val->type->data.unionation.gen_union_index;

    ZigType *u32 = g->builtin_types.entry_u32;
    LLVMValueRef indices[] = {
        LLVMConstNull(get_llvm_type(g, u32)),
        LLVMConstInt(get_llvm_type(g, u32), union_payload_index, false),
    };
    return LLVMConstInBoundsGEP(base_ptr, indices, (union_payload_index != SIZE_MAX) ? 2 : 1);
}

static LLVMValueRef pack_const_int(CodeGen *g, LLVMTypeRef big_int_type_ref, ZigValue *const_val) {
    switch (const_val->special) {
        case ConstValSpecialLazy:
        case ConstValSpecialRuntime:
            zig_unreachable();
        case ConstValSpecialUndef:
            return LLVMConstInt(big_int_type_ref, 0, false);
        case ConstValSpecialStatic:
            break;
    }

    ZigType *type_entry = const_val->type;
    assert(type_has_bits(type_entry));
    switch (type_entry->id) {
        case ZigTypeIdInvalid:
        case ZigTypeIdMetaType:
        case ZigTypeIdUnreachable:
        case ZigTypeIdComptimeFloat:
        case ZigTypeIdComptimeInt:
        case ZigTypeIdEnumLiteral:
        case ZigTypeIdUndefined:
        case ZigTypeIdNull:
        case ZigTypeIdErrorUnion:
        case ZigTypeIdErrorSet:
        case ZigTypeIdBoundFn:
        case ZigTypeIdVoid:
        case ZigTypeIdOpaque:
            zig_unreachable();
        case ZigTypeIdBool:
            return LLVMConstInt(big_int_type_ref, const_val->data.x_bool ? 1 : 0, false);
        case ZigTypeIdEnum:
            {
                assert(type_entry->data.enumeration.decl_node->data.container_decl.init_arg_expr != nullptr);
                LLVMValueRef int_val = gen_const_val(g, const_val, "");
                return LLVMConstZExt(int_val, big_int_type_ref);
            }
        case ZigTypeIdInt:
            {
                LLVMValueRef int_val = gen_const_val(g, const_val, "");
                return LLVMConstZExt(int_val, big_int_type_ref);
            }
        case ZigTypeIdFloat:
            {
                LLVMValueRef float_val = gen_const_val(g, const_val, "");
                LLVMValueRef int_val = LLVMConstFPToUI(float_val,
                        LLVMIntType((unsigned)type_entry->data.floating.bit_count));
                return LLVMConstZExt(int_val, big_int_type_ref);
            }
        case ZigTypeIdPointer:
        case ZigTypeIdFn:
        case ZigTypeIdOptional:
            {
                LLVMValueRef ptr_val = gen_const_val(g, const_val, "");
                LLVMValueRef ptr_size_int_val = LLVMConstPtrToInt(ptr_val, g->builtin_types.entry_usize->llvm_type);
                return LLVMConstZExt(ptr_size_int_val, big_int_type_ref);
            }
        case ZigTypeIdArray: {
            LLVMValueRef val = LLVMConstInt(big_int_type_ref, 0, false);
            if (const_val->data.x_array.special == ConstArraySpecialUndef) {
                return val;
            }
            expand_undef_array(g, const_val);
            bool is_big_endian = g->is_big_endian; // TODO get endianness from struct type
            uint32_t packed_bits_size = type_size_bits(g, type_entry->data.array.child_type);
            size_t used_bits = 0;
            for (size_t i = 0; i < type_entry->data.array.len; i += 1) {
                ZigValue *elem_val = &const_val->data.x_array.data.s_none.elements[i];
                LLVMValueRef child_val = pack_const_int(g, big_int_type_ref, elem_val);

                if (is_big_endian) {
                    LLVMValueRef shift_amt = LLVMConstInt(big_int_type_ref, packed_bits_size, false);
                    val = LLVMConstShl(val, shift_amt);
                    val = LLVMConstOr(val, child_val);
                } else {
                    LLVMValueRef shift_amt = LLVMConstInt(big_int_type_ref, used_bits, false);
                    LLVMValueRef child_val_shifted = LLVMConstShl(child_val, shift_amt);
                    val = LLVMConstOr(val, child_val_shifted);
                    used_bits += packed_bits_size;
                }
            }
            return val;
        }
        case ZigTypeIdVector:
            zig_panic("TODO bit pack a vector");
        case ZigTypeIdUnion:
            zig_panic("TODO bit pack a union");
        case ZigTypeIdStruct:
            {
                assert(type_entry->data.structure.layout == ContainerLayoutPacked);
                bool is_big_endian = g->is_big_endian; // TODO get endianness from struct type

                LLVMValueRef val = LLVMConstInt(big_int_type_ref, 0, false);
                size_t used_bits = 0;
                for (size_t i = 0; i < type_entry->data.structure.src_field_count; i += 1) {
                    TypeStructField *field = type_entry->data.structure.fields[i];
                    if (field->gen_index == SIZE_MAX) {
                        continue;
                    }
                    LLVMValueRef child_val = pack_const_int(g, big_int_type_ref, const_val->data.x_struct.fields[i]);
                    uint32_t packed_bits_size = type_size_bits(g, field->type_entry);
                    if (is_big_endian) {
                        LLVMValueRef shift_amt = LLVMConstInt(big_int_type_ref, packed_bits_size, false);
                        val = LLVMConstShl(val, shift_amt);
                        val = LLVMConstOr(val, child_val);
                    } else {
                        LLVMValueRef shift_amt = LLVMConstInt(big_int_type_ref, used_bits, false);
                        LLVMValueRef child_val_shifted = LLVMConstShl(child_val, shift_amt);
                        val = LLVMConstOr(val, child_val_shifted);
                        used_bits += packed_bits_size;
                    }
                }
                return val;
            }
        case ZigTypeIdFnFrame:
            zig_panic("TODO bit pack an async function frame");
        case ZigTypeIdAnyFrame:
            zig_panic("TODO bit pack an anyframe");
    }
    zig_unreachable();
}

// We have this because union constants can't be represented by the official union type,
// and this property bubbles up in whatever aggregate type contains a union constant
static bool is_llvm_value_unnamed_type(CodeGen *g, ZigType *type_entry, LLVMValueRef val) {
    return LLVMTypeOf(val) != get_llvm_type(g, type_entry);
}

static LLVMValueRef gen_const_val_ptr(CodeGen *g, ZigValue *const_val, const char *name) {
    switch (const_val->data.x_ptr.special) {
        case ConstPtrSpecialInvalid:
        case ConstPtrSpecialDiscard:
            zig_unreachable();
        case ConstPtrSpecialRef:
            {
                ZigValue *pointee = const_val->data.x_ptr.data.ref.pointee;
                render_const_val(g, pointee, "");
                render_const_val_global(g, pointee, "");
                const_val->llvm_value = LLVMConstBitCast(pointee->llvm_global,
                        get_llvm_type(g, const_val->type));
                return const_val->llvm_value;
            }
        case ConstPtrSpecialBaseArray:
            {
                ZigValue *array_const_val = const_val->data.x_ptr.data.base_array.array_val;
                assert(array_const_val->type->id == ZigTypeIdArray);
                if (!type_has_bits(array_const_val->type)) {
                    if (array_const_val->type->data.array.sentinel != nullptr) {
                        ZigValue *pointee = array_const_val->type->data.array.sentinel;
                        render_const_val(g, pointee, "");
                        render_const_val_global(g, pointee, "");
                        const_val->llvm_value = LLVMConstBitCast(pointee->llvm_global,
                                get_llvm_type(g, const_val->type));
                        return const_val->llvm_value;
                    } else {
                        // make this a null pointer
                        ZigType *usize = g->builtin_types.entry_usize;
                        const_val->llvm_value = LLVMConstIntToPtr(LLVMConstNull(usize->llvm_type),
                                get_llvm_type(g, const_val->type));
                        return const_val->llvm_value;
                    }
                }
                size_t elem_index = const_val->data.x_ptr.data.base_array.elem_index;
                LLVMValueRef uncasted_ptr_val = gen_const_ptr_array_recursive(g, array_const_val, elem_index);
                LLVMValueRef ptr_val = LLVMConstBitCast(uncasted_ptr_val, get_llvm_type(g, const_val->type));
                const_val->llvm_value = ptr_val;
                return ptr_val;
            }
        case ConstPtrSpecialBaseStruct:
            {
                ZigValue *struct_const_val = const_val->data.x_ptr.data.base_struct.struct_val;
                assert(struct_const_val->type->id == ZigTypeIdStruct);
                if (!type_has_bits(struct_const_val->type)) {
                    // make this a null pointer
                    ZigType *usize = g->builtin_types.entry_usize;
                    const_val->llvm_value = LLVMConstIntToPtr(LLVMConstNull(usize->llvm_type),
                            get_llvm_type(g, const_val->type));
                    return const_val->llvm_value;
                }
                size_t src_field_index = const_val->data.x_ptr.data.base_struct.field_index;
                size_t gen_field_index = struct_const_val->type->data.structure.fields[src_field_index]->gen_index;
                LLVMValueRef uncasted_ptr_val = gen_const_ptr_struct_recursive(g, struct_const_val,
                        gen_field_index);
                LLVMValueRef ptr_val = LLVMConstBitCast(uncasted_ptr_val, get_llvm_type(g, const_val->type));
                const_val->llvm_value = ptr_val;
                return ptr_val;
            }
        case ConstPtrSpecialBaseErrorUnionCode:
            {
                ZigValue *err_union_const_val = const_val->data.x_ptr.data.base_err_union_code.err_union_val;
                assert(err_union_const_val->type->id == ZigTypeIdErrorUnion);
                if (!type_has_bits(err_union_const_val->type)) {
                    // make this a null pointer
                    ZigType *usize = g->builtin_types.entry_usize;
                    const_val->llvm_value = LLVMConstIntToPtr(LLVMConstNull(usize->llvm_type),
                            get_llvm_type(g, const_val->type));
                    return const_val->llvm_value;
                }
                LLVMValueRef uncasted_ptr_val = gen_const_ptr_err_union_code_recursive(g, err_union_const_val);
                LLVMValueRef ptr_val = LLVMConstBitCast(uncasted_ptr_val, get_llvm_type(g, const_val->type));
                const_val->llvm_value = ptr_val;
                return ptr_val;
            }
        case ConstPtrSpecialBaseErrorUnionPayload:
            {
                ZigValue *err_union_const_val = const_val->data.x_ptr.data.base_err_union_payload.err_union_val;
                assert(err_union_const_val->type->id == ZigTypeIdErrorUnion);
                if (!type_has_bits(err_union_const_val->type)) {
                    // make this a null pointer
                    ZigType *usize = g->builtin_types.entry_usize;
                    const_val->llvm_value = LLVMConstIntToPtr(LLVMConstNull(usize->llvm_type),
                            get_llvm_type(g, const_val->type));
                    return const_val->llvm_value;
                }
                LLVMValueRef uncasted_ptr_val = gen_const_ptr_err_union_payload_recursive(g, err_union_const_val);
                LLVMValueRef ptr_val = LLVMConstBitCast(uncasted_ptr_val, get_llvm_type(g, const_val->type));
                const_val->llvm_value = ptr_val;
                return ptr_val;
            }
        case ConstPtrSpecialBaseOptionalPayload:
            {
                ZigValue *optional_const_val = const_val->data.x_ptr.data.base_optional_payload.optional_val;
                assert(optional_const_val->type->id == ZigTypeIdOptional);
                if (!type_has_bits(optional_const_val->type)) {
                    // make this a null pointer
                    ZigType *usize = g->builtin_types.entry_usize;
                    const_val->llvm_value = LLVMConstIntToPtr(LLVMConstNull(usize->llvm_type),
                            get_llvm_type(g, const_val->type));
                    return const_val->llvm_value;
                }
                LLVMValueRef uncasted_ptr_val = gen_const_ptr_optional_payload_recursive(g, optional_const_val);
                LLVMValueRef ptr_val = LLVMConstBitCast(uncasted_ptr_val, get_llvm_type(g, const_val->type));
                const_val->llvm_value = ptr_val;
                return ptr_val;
            }
        case ConstPtrSpecialHardCodedAddr:
            {
                uint64_t addr_value = const_val->data.x_ptr.data.hard_coded_addr.addr;
                ZigType *usize = g->builtin_types.entry_usize;
                const_val->llvm_value = LLVMConstIntToPtr(
                        LLVMConstInt(usize->llvm_type, addr_value, false), get_llvm_type(g, const_val->type));
                return const_val->llvm_value;
            }
        case ConstPtrSpecialFunction:
            return LLVMConstBitCast(fn_llvm_value(g, const_val->data.x_ptr.data.fn.fn_entry),
                    get_llvm_type(g, const_val->type));
        case ConstPtrSpecialNull:
            return LLVMConstNull(get_llvm_type(g, const_val->type));
    }
    zig_unreachable();
}

static LLVMValueRef gen_const_val_err_set(CodeGen *g, ZigValue *const_val, const char *name) {
    uint64_t value = (const_val->data.x_err_set == nullptr) ? 0 : const_val->data.x_err_set->value;
    return LLVMConstInt(get_llvm_type(g, g->builtin_types.entry_global_error_set), value, false);
}

static LLVMValueRef gen_const_val(CodeGen *g, ZigValue *const_val, const char *name) {
    Error err;

    ZigType *type_entry = const_val->type;
    assert(type_has_bits(type_entry));

check: switch (const_val->special) {
        case ConstValSpecialLazy:
            if ((err = ir_resolve_lazy(g, nullptr, const_val))) {
                codegen_report_errors_and_exit(g);
            }
            goto check;
        case ConstValSpecialRuntime:
            zig_unreachable();
        case ConstValSpecialUndef:
            return LLVMGetUndef(get_llvm_type(g, type_entry));
        case ConstValSpecialStatic:
            break;
    }

    if ((err = type_resolve(g, type_entry, ResolveStatusLLVMFull)))
        zig_unreachable();

    switch (type_entry->id) {
        case ZigTypeIdInt:
            return bigint_to_llvm_const(get_llvm_type(g, type_entry), &const_val->data.x_bigint);
        case ZigTypeIdErrorSet:
            return gen_const_val_err_set(g, const_val, name);
        case ZigTypeIdFloat:
            switch (type_entry->data.floating.bit_count) {
                case 16:
                    return LLVMConstReal(get_llvm_type(g, type_entry), zig_f16_to_double(const_val->data.x_f16));
                case 32:
                    return LLVMConstReal(get_llvm_type(g, type_entry), const_val->data.x_f32);
                case 64:
                    return LLVMConstReal(get_llvm_type(g, type_entry), const_val->data.x_f64);
                case 128:
                    {
                        // TODO make sure this is correct on big endian targets too
                        uint8_t buf[16];
                        memcpy(buf, &const_val->data.x_f128, 16);
                        LLVMValueRef as_int = LLVMConstIntOfArbitraryPrecision(LLVMInt128Type(), 2,
                                (uint64_t*)buf);
                        return LLVMConstBitCast(as_int, get_llvm_type(g, type_entry));
                    }
                default:
                    zig_unreachable();
            }
        case ZigTypeIdBool:
            if (const_val->data.x_bool) {
                return LLVMConstAllOnes(LLVMInt1Type());
            } else {
                return LLVMConstNull(LLVMInt1Type());
            }
        case ZigTypeIdOptional:
            {
                ZigType *child_type = type_entry->data.maybe.child_type;
                if (!type_has_bits(child_type)) {
                    return LLVMConstInt(LLVMInt1Type(), const_val->data.x_optional ? 1 : 0, false);
                } else if (get_codegen_ptr_type(type_entry) != nullptr) {
                    return gen_const_val_ptr(g, const_val, name);
                } else if (child_type->id == ZigTypeIdErrorSet) {
                    return gen_const_val_err_set(g, const_val, name);
                } else {
                    LLVMValueRef child_val;
                    LLVMValueRef maybe_val;
                    bool make_unnamed_struct;
                    if (const_val->data.x_optional) {
                        child_val = gen_const_val(g, const_val->data.x_optional, "");
                        maybe_val = LLVMConstAllOnes(LLVMInt1Type());

                        make_unnamed_struct = is_llvm_value_unnamed_type(g, const_val->type, child_val);
                    } else {
                        child_val = LLVMGetUndef(get_llvm_type(g, child_type));
                        maybe_val = LLVMConstNull(LLVMInt1Type());

                        make_unnamed_struct = false;
                    }
                    LLVMValueRef fields[] = {
                        child_val,
                        maybe_val,
                    };
                    if (make_unnamed_struct) {
                        return LLVMConstStruct(fields, 2, false);
                    } else {
                        return LLVMConstNamedStruct(get_llvm_type(g, type_entry), fields, 2);
                    }
                }
            }
        case ZigTypeIdStruct:
            {
                LLVMValueRef *fields = allocate<LLVMValueRef>(type_entry->data.structure.gen_field_count);
                size_t src_field_count = type_entry->data.structure.src_field_count;
                bool make_unnamed_struct = false;
                assert(type_entry->data.structure.resolve_status == ResolveStatusLLVMFull);
                if (type_entry->data.structure.layout == ContainerLayoutPacked) {
                    size_t src_field_index = 0;
                    while (src_field_index < src_field_count) {
                        TypeStructField *type_struct_field = type_entry->data.structure.fields[src_field_index];
                        if (type_struct_field->gen_index == SIZE_MAX) {
                            src_field_index += 1;
                            continue;
                        }

                        size_t src_field_index_end = src_field_index + 1;
                        for (; src_field_index_end < src_field_count; src_field_index_end += 1) {
                            TypeStructField *it_field = type_entry->data.structure.fields[src_field_index_end];
                            if (it_field->gen_index != type_struct_field->gen_index)
                                break;
                        }

                        if (src_field_index + 1 == src_field_index_end) {
                            ZigValue *field_val = const_val->data.x_struct.fields[src_field_index];
                            LLVMValueRef val = gen_const_val(g, field_val, "");
                            fields[type_struct_field->gen_index] = val;
                            make_unnamed_struct = make_unnamed_struct || is_llvm_value_unnamed_type(g, field_val->type, val);
                        } else {
                            bool is_big_endian = g->is_big_endian; // TODO get endianness from struct type
                            LLVMTypeRef field_ty = LLVMStructGetTypeAtIndex(get_llvm_type(g, type_entry),
                                    (unsigned)type_struct_field->gen_index);
                            const size_t size_in_bytes = LLVMStoreSizeOfType(g->target_data_ref, field_ty);
                            LLVMTypeRef big_int_type_ref = LLVMIntType(size_in_bytes * 8);
                            LLVMValueRef val = LLVMConstInt(big_int_type_ref, 0, false);
                            size_t used_bits = 0;
                            for (size_t i = src_field_index; i < src_field_index_end; i += 1) {
                                TypeStructField *it_field = type_entry->data.structure.fields[i];
                                if (it_field->gen_index == SIZE_MAX) {
                                    continue;
                                }
                                LLVMValueRef child_val = pack_const_int(g, big_int_type_ref,
                                        const_val->data.x_struct.fields[i]);
                                uint32_t packed_bits_size = type_size_bits(g, it_field->type_entry);
                                if (is_big_endian) {
                                    LLVMValueRef shift_amt = LLVMConstInt(big_int_type_ref,
                                            packed_bits_size, false);
                                    val = LLVMConstShl(val, shift_amt);
                                    val = LLVMConstOr(val, child_val);
                                } else {
                                    LLVMValueRef shift_amt = LLVMConstInt(big_int_type_ref, used_bits, false);
                                    LLVMValueRef child_val_shifted = LLVMConstShl(child_val, shift_amt);
                                    val = LLVMConstOr(val, child_val_shifted);
                                    used_bits += packed_bits_size;
                                }
                            }
                            if (LLVMGetTypeKind(field_ty) != LLVMArrayTypeKind) {
                                assert(LLVMGetTypeKind(field_ty) == LLVMIntegerTypeKind);
                                fields[type_struct_field->gen_index] = val;
                            } else {
                                const LLVMValueRef AMT = LLVMConstInt(LLVMTypeOf(val), 8, false);

                                LLVMValueRef *values = allocate<LLVMValueRef>(size_in_bytes);
                                for (size_t i = 0; i < size_in_bytes; i++) {
                                    const size_t idx = is_big_endian ? size_in_bytes - 1 - i : i;
                                    values[idx] = LLVMConstTruncOrBitCast(val, LLVMInt8Type());
                                    val = LLVMConstLShr(val, AMT);
                                }

                                fields[type_struct_field->gen_index] = LLVMConstArray(LLVMInt8Type(), values, size_in_bytes);
                            }
                        }

                        src_field_index = src_field_index_end;
                    }
                } else {
                    for (uint32_t i = 0; i < src_field_count; i += 1) {
                        TypeStructField *type_struct_field = type_entry->data.structure.fields[i];
                        if (type_struct_field->gen_index == SIZE_MAX) {
                            continue;
                        }
                        ZigValue *field_val = const_val->data.x_struct.fields[i];
                        assert(field_val->type != nullptr);
                        if ((err = ensure_const_val_repr(nullptr, g, nullptr, field_val,
                                        type_struct_field->type_entry)))
                        {
                            zig_unreachable();
                        }

                        LLVMValueRef val = gen_const_val(g, field_val, "");
                        fields[type_struct_field->gen_index] = val;
                        make_unnamed_struct = make_unnamed_struct || is_llvm_value_unnamed_type(g, field_val->type, val);

                        size_t end_pad_gen_index = (i + 1 < src_field_count) ?
                            type_entry->data.structure.fields[i + 1]->gen_index :
                            type_entry->data.structure.gen_field_count;
                        size_t next_offset = (i + 1 < src_field_count) ?
                            type_entry->data.structure.fields[i + 1]->offset : type_entry->abi_size;
                        if (end_pad_gen_index != SIZE_MAX) {
                            for (size_t gen_i = type_struct_field->gen_index + 1; gen_i < end_pad_gen_index;
                                    gen_i += 1)
                            {
                                size_t pad_bytes = next_offset -
                                    (type_struct_field->offset + type_struct_field->type_entry->abi_size);
                                LLVMTypeRef llvm_array_type = LLVMArrayType(LLVMInt8Type(), pad_bytes);
                                fields[gen_i] = LLVMGetUndef(llvm_array_type);
                            }
                        }
                    }
                }
                if (make_unnamed_struct) {
                    return LLVMConstStruct(fields, type_entry->data.structure.gen_field_count,
                        type_entry->data.structure.layout == ContainerLayoutPacked);
                } else {
                    return LLVMConstNamedStruct(get_llvm_type(g, type_entry), fields, type_entry->data.structure.gen_field_count);
                }
            }
        case ZigTypeIdArray:
            {
                uint64_t len = type_entry->data.array.len;
                switch (const_val->data.x_array.special) {
                    case ConstArraySpecialUndef:
                        return LLVMGetUndef(get_llvm_type(g, type_entry));
                    case ConstArraySpecialNone: {
                        uint64_t extra_len_from_sentinel = (type_entry->data.array.sentinel != nullptr) ? 1 : 0;
                        uint64_t full_len = len + extra_len_from_sentinel;
                        LLVMValueRef *values = allocate<LLVMValueRef>(full_len);
                        LLVMTypeRef element_type_ref = get_llvm_type(g, type_entry->data.array.child_type);
                        bool make_unnamed_struct = false;
                        for (uint64_t i = 0; i < len; i += 1) {
                            ZigValue *elem_value = &const_val->data.x_array.data.s_none.elements[i];
                            LLVMValueRef val = gen_const_val(g, elem_value, "");
                            values[i] = val;
                            make_unnamed_struct = make_unnamed_struct || is_llvm_value_unnamed_type(g, elem_value->type, val);
                        }
                        if (type_entry->data.array.sentinel != nullptr) {
                            values[len] = gen_const_val(g, type_entry->data.array.sentinel, "");
                        }
                        if (make_unnamed_struct) {
                            return LLVMConstStruct(values, full_len, true);
                        } else {
                            return LLVMConstArray(element_type_ref, values, (unsigned)full_len);
                        }
                    }
                    case ConstArraySpecialBuf: {
                        Buf *buf = const_val->data.x_array.data.s_buf;
                        return LLVMConstString(buf_ptr(buf), (unsigned)buf_len(buf),
                                type_entry->data.array.sentinel == nullptr);
                    }
                }
                zig_unreachable();
            }
        case ZigTypeIdVector: {
            uint32_t len = type_entry->data.vector.len;
            switch (const_val->data.x_array.special) {
                case ConstArraySpecialUndef:
                    return LLVMGetUndef(get_llvm_type(g, type_entry));
                case ConstArraySpecialNone: {
                    LLVMValueRef *values = allocate<LLVMValueRef>(len);
                    for (uint64_t i = 0; i < len; i += 1) {
                        ZigValue *elem_value = &const_val->data.x_array.data.s_none.elements[i];
                        values[i] = gen_const_val(g, elem_value, "");
                    }
                    return LLVMConstVector(values, len);
                }
                case ConstArraySpecialBuf: {
                    Buf *buf = const_val->data.x_array.data.s_buf;
                    assert(buf_len(buf) == len);
                    LLVMValueRef *values = allocate<LLVMValueRef>(len);
                    for (uint64_t i = 0; i < len; i += 1) {
                        values[i] = LLVMConstInt(g->builtin_types.entry_u8->llvm_type, buf_ptr(buf)[i], false);
                    }
                    return LLVMConstVector(values, len);
                }
            }
            zig_unreachable();
        }
        case ZigTypeIdUnion:
            {
                // Force type_entry->data.unionation.union_llvm_type to get resolved
                (void)get_llvm_type(g, type_entry);

                if (type_entry->data.unionation.gen_field_count == 0) {
                    if (type_entry->data.unionation.tag_type == nullptr) {
                        return nullptr;
                    } else {
                        return bigint_to_llvm_const(get_llvm_type(g, type_entry->data.unionation.tag_type),
                            &const_val->data.x_union.tag);
                    }
                }

                LLVMTypeRef union_type_ref = type_entry->data.unionation.union_llvm_type;
                assert(union_type_ref != nullptr);

                LLVMValueRef union_value_ref;
                bool make_unnamed_struct;
                ZigValue *payload_value = const_val->data.x_union.payload;
                if (payload_value == nullptr || !type_has_bits(payload_value->type)) {
                    if (type_entry->data.unionation.gen_tag_index == SIZE_MAX)
                        return LLVMGetUndef(get_llvm_type(g, type_entry));

                    union_value_ref = LLVMGetUndef(union_type_ref);
                    make_unnamed_struct = false;
                } else {
                    uint64_t field_type_bytes = LLVMABISizeOfType(g->target_data_ref,
                            get_llvm_type(g, payload_value->type));
                    uint64_t pad_bytes = type_entry->data.unionation.union_abi_size - field_type_bytes;
                    LLVMValueRef correctly_typed_value = gen_const_val(g, payload_value, "");
                    make_unnamed_struct = is_llvm_value_unnamed_type(g, payload_value->type, correctly_typed_value) ||
                        payload_value->type != type_entry->data.unionation.most_aligned_union_member->type_entry;

                    {
                        if (pad_bytes == 0) {
                            union_value_ref = correctly_typed_value;
                        } else {
                            LLVMValueRef fields[2];
                            fields[0] = correctly_typed_value;
                            fields[1] = LLVMGetUndef(LLVMArrayType(LLVMInt8Type(), (unsigned)pad_bytes));
                            if (make_unnamed_struct || type_entry->data.unionation.gen_tag_index != SIZE_MAX) {
                                union_value_ref = LLVMConstStruct(fields, 2, false);
                            } else {
                                union_value_ref = LLVMConstNamedStruct(union_type_ref, fields, 2);
                            }
                        }
                    }

                    if (type_entry->data.unionation.gen_tag_index == SIZE_MAX) {
                        return union_value_ref;
                    }
                }

                LLVMValueRef tag_value = bigint_to_llvm_const(
                        get_llvm_type(g, type_entry->data.unionation.tag_type),
                        &const_val->data.x_union.tag);

                LLVMValueRef fields[3];
                fields[type_entry->data.unionation.gen_union_index] = union_value_ref;
                fields[type_entry->data.unionation.gen_tag_index] = tag_value;

                if (make_unnamed_struct) {
                    LLVMValueRef result = LLVMConstStruct(fields, 2, false);
                    uint64_t last_field_offset = LLVMOffsetOfElement(g->target_data_ref, LLVMTypeOf(result), 1);
                    uint64_t end_offset = last_field_offset +
                        LLVMStoreSizeOfType(g->target_data_ref, LLVMTypeOf(fields[1]));
                    uint64_t expected_sz = LLVMABISizeOfType(g->target_data_ref, get_llvm_type(g, type_entry));
                    unsigned pad_sz = expected_sz - end_offset;
                    if (pad_sz != 0) {
                        fields[2] = LLVMGetUndef(LLVMArrayType(LLVMInt8Type(), pad_sz));
                        result = LLVMConstStruct(fields, 3, false);
                    }
                    uint64_t actual_sz = LLVMStoreSizeOfType(g->target_data_ref, LLVMTypeOf(result));
                    assert(actual_sz == expected_sz);
                    return result;
                } else {
                    return LLVMConstNamedStruct(get_llvm_type(g, type_entry), fields, 2);
                }

            }

        case ZigTypeIdEnum:
            return bigint_to_llvm_const(get_llvm_type(g, type_entry), &const_val->data.x_enum_tag);
        case ZigTypeIdFn:
            if (const_val->data.x_ptr.special == ConstPtrSpecialFunction &&
                const_val->data.x_ptr.mut != ConstPtrMutComptimeConst) {
                zig_unreachable();
            }
            // Treat it the same as we do for pointers
            return gen_const_val_ptr(g, const_val, name);
        case ZigTypeIdPointer:
            return gen_const_val_ptr(g, const_val, name);
        case ZigTypeIdErrorUnion:
            {
                ZigType *payload_type = type_entry->data.error_union.payload_type;
                ZigType *err_set_type = type_entry->data.error_union.err_set_type;
                if (!type_has_bits(payload_type)) {
                    assert(type_has_bits(err_set_type));
                    ErrorTableEntry *err_set = const_val->data.x_err_union.error_set->data.x_err_set;
                    uint64_t value = (err_set == nullptr) ? 0 : err_set->value;
                    return LLVMConstInt(get_llvm_type(g, g->err_tag_type), value, false);
                } else if (!type_has_bits(err_set_type)) {
                    assert(type_has_bits(payload_type));
                    return gen_const_val(g, const_val->data.x_err_union.payload, "");
                } else {
                    LLVMValueRef err_tag_value;
                    LLVMValueRef err_payload_value;
                    bool make_unnamed_struct;
                    ErrorTableEntry *err_set = const_val->data.x_err_union.error_set->data.x_err_set;
                    if (err_set != nullptr) {
                        err_tag_value = LLVMConstInt(get_llvm_type(g, g->err_tag_type), err_set->value, false);
                        err_payload_value = LLVMConstNull(get_llvm_type(g, payload_type));
                        make_unnamed_struct = false;
                    } else {
                        err_tag_value = LLVMConstNull(get_llvm_type(g, g->err_tag_type));
                        ZigValue *payload_val = const_val->data.x_err_union.payload;
                        err_payload_value = gen_const_val(g, payload_val, "");
                        make_unnamed_struct = is_llvm_value_unnamed_type(g, payload_val->type, err_payload_value);
                    }
                    LLVMValueRef fields[3];
                    fields[err_union_err_index] = err_tag_value;
                    fields[err_union_payload_index] = err_payload_value;
                    size_t field_count = 2;
                    if (type_entry->data.error_union.pad_llvm_type != nullptr) {
                        fields[2] = LLVMGetUndef(type_entry->data.error_union.pad_llvm_type);
                        field_count = 3;
                    }
                    if (make_unnamed_struct) {
                        return LLVMConstStruct(fields, field_count, false);
                    } else {
                        return LLVMConstNamedStruct(get_llvm_type(g, type_entry), fields, field_count);
                    }
                }
            }
        case ZigTypeIdVoid:
            return nullptr;
        case ZigTypeIdInvalid:
        case ZigTypeIdMetaType:
        case ZigTypeIdUnreachable:
        case ZigTypeIdComptimeFloat:
        case ZigTypeIdComptimeInt:
        case ZigTypeIdEnumLiteral:
        case ZigTypeIdUndefined:
        case ZigTypeIdNull:
        case ZigTypeIdBoundFn:
        case ZigTypeIdOpaque:
            zig_unreachable();
        case ZigTypeIdFnFrame:
            zig_panic("TODO");
        case ZigTypeIdAnyFrame:
            zig_panic("TODO");
    }
    zig_unreachable();
}

static void render_const_val(CodeGen *g, ZigValue *const_val, const char *name) {
    if (!const_val->llvm_value)
        const_val->llvm_value = gen_const_val(g, const_val, name);

    if (const_val->llvm_global)
        LLVMSetInitializer(const_val->llvm_global, const_val->llvm_value);
}

static void render_const_val_global(CodeGen *g, ZigValue *const_val, const char *name) {
    if (!const_val->llvm_global) {
        LLVMTypeRef type_ref = const_val->llvm_value ?
            LLVMTypeOf(const_val->llvm_value) : get_llvm_type(g, const_val->type);
        LLVMValueRef global_value = LLVMAddGlobal(g->module, type_ref, name);
        LLVMSetLinkage(global_value, (name == nullptr) ? LLVMPrivateLinkage : LLVMInternalLinkage);
        LLVMSetGlobalConstant(global_value, true);
        LLVMSetUnnamedAddr(global_value, true);
        LLVMSetAlignment(global_value, (const_val->llvm_align == 0) ?
                get_abi_alignment(g, const_val->type) : const_val->llvm_align);

        const_val->llvm_global = global_value;
    }

    if (const_val->llvm_value)
        LLVMSetInitializer(const_val->llvm_global, const_val->llvm_value);
}

static void generate_error_name_table(CodeGen *g) {
    if (g->err_name_table != nullptr || !g->generate_error_name_table || g->errors_by_index.length == 1) {
        return;
    }

    assert(g->errors_by_index.length > 0);

    ZigType *u8_ptr_type = get_pointer_to_type_extra(g, g->builtin_types.entry_u8, true, false,
            PtrLenUnknown, get_abi_alignment(g, g->builtin_types.entry_u8), 0, 0, false);
    ZigType *str_type = get_slice_type(g, u8_ptr_type);

    LLVMValueRef *values = allocate<LLVMValueRef>(g->errors_by_index.length);
    values[0] = LLVMGetUndef(get_llvm_type(g, str_type));
    for (size_t i = 1; i < g->errors_by_index.length; i += 1) {
        ErrorTableEntry *err_entry = g->errors_by_index.at(i);
        Buf *name = &err_entry->name;

        g->largest_err_name_len = max(g->largest_err_name_len, buf_len(name));

        LLVMValueRef str_init = LLVMConstString(buf_ptr(name), (unsigned)buf_len(name), true);
        LLVMValueRef str_global = LLVMAddGlobal(g->module, LLVMTypeOf(str_init), "");
        LLVMSetInitializer(str_global, str_init);
        LLVMSetLinkage(str_global, LLVMPrivateLinkage);
        LLVMSetGlobalConstant(str_global, true);
        LLVMSetUnnamedAddr(str_global, true);
        LLVMSetAlignment(str_global, LLVMABIAlignmentOfType(g->target_data_ref, LLVMTypeOf(str_init)));

        LLVMValueRef fields[] = {
            LLVMConstBitCast(str_global, get_llvm_type(g, u8_ptr_type)),
            LLVMConstInt(g->builtin_types.entry_usize->llvm_type, buf_len(name), false),
        };
        values[i] = LLVMConstNamedStruct(get_llvm_type(g, str_type), fields, 2);
    }

    LLVMValueRef err_name_table_init = LLVMConstArray(get_llvm_type(g, str_type), values, (unsigned)g->errors_by_index.length);

    g->err_name_table = LLVMAddGlobal(g->module, LLVMTypeOf(err_name_table_init),
            get_mangled_name(g, buf_ptr(buf_create_from_str("__zig_err_name_table")), false));
    LLVMSetInitializer(g->err_name_table, err_name_table_init);
    LLVMSetLinkage(g->err_name_table, LLVMPrivateLinkage);
    LLVMSetGlobalConstant(g->err_name_table, true);
    LLVMSetUnnamedAddr(g->err_name_table, true);
    LLVMSetAlignment(g->err_name_table, LLVMABIAlignmentOfType(g->target_data_ref, LLVMTypeOf(err_name_table_init)));
}

static void build_all_basic_blocks(CodeGen *g, ZigFn *fn) {
    IrExecutable *executable = &fn->analyzed_executable;
    assert(executable->basic_block_list.length > 0);
    LLVMValueRef fn_val = fn_llvm_value(g, fn);
    LLVMBasicBlockRef first_bb = nullptr;
    if (fn_is_async(fn)) {
        first_bb = LLVMAppendBasicBlock(fn_val, "AsyncSwitch");
        g->cur_preamble_llvm_block = first_bb;
    }
    for (size_t block_i = 0; block_i < executable->basic_block_list.length; block_i += 1) {
        IrBasicBlock *bb = executable->basic_block_list.at(block_i);
        bb->llvm_block = LLVMAppendBasicBlock(fn_val, bb->name_hint);
    }
    if (first_bb == nullptr) {
        first_bb = executable->basic_block_list.at(0)->llvm_block;
    }
    LLVMPositionBuilderAtEnd(g->builder, first_bb);
}

static void gen_global_var(CodeGen *g, ZigVar *var, LLVMValueRef init_val,
    ZigType *type_entry)
{
    if (g->strip_debug_symbols) {
        return;
    }

    assert(var->gen_is_const);
    assert(type_entry);

    ZigType *import = get_scope_import(var->parent_scope);
    assert(import);

    bool is_local_to_unit = true;
    ZigLLVMCreateGlobalVariable(g->dbuilder, get_di_scope(g, var->parent_scope), var->name,
        var->name, import->data.structure.root_struct->di_file,
        (unsigned)(var->decl_node->line + 1),
        get_llvm_di_type(g, type_entry), is_local_to_unit);

    // TODO ^^ make an actual global variable
}

static void validate_inline_fns(CodeGen *g) {
    for (size_t i = 0; i < g->inline_fns.length; i += 1) {
        ZigFn *fn_entry = g->inline_fns.at(i);
        LLVMValueRef fn_val = LLVMGetNamedFunction(g->module, fn_entry->llvm_name);
        if (fn_val != nullptr) {
            add_node_error(g, fn_entry->proto_node, buf_sprintf("unable to inline function"));
        }
    }
    report_errors_and_maybe_exit(g);
}

static void set_global_tls(CodeGen *g, ZigVar *var, LLVMValueRef global_value) {
    bool is_extern = var->decl_node->data.variable_declaration.is_extern;
    bool is_export = var->decl_node->data.variable_declaration.is_export;
    bool is_internal_linkage = !is_extern && !is_export;
    if (var->is_thread_local && (!g->is_single_threaded || !is_internal_linkage)) {
        LLVMSetThreadLocalMode(global_value, LLVMGeneralDynamicTLSModel);
    }
}

static void do_code_gen(CodeGen *g) {
    Error err;
    assert(!g->errors.length);

    generate_error_name_table(g);

    // Generate module level variables
    for (size_t i = 0; i < g->global_vars.length; i += 1) {
        TldVar *tld_var = g->global_vars.at(i);
        ZigVar *var = tld_var->var;

        if (var->var_type->id == ZigTypeIdComptimeFloat) {
            // Generate debug info for it but that's it.
            ZigValue *const_val = var->const_value;
            assert(const_val->special != ConstValSpecialRuntime);
            if ((err = ir_resolve_lazy(g, var->decl_node, const_val)))
                zig_unreachable();
            if (const_val->type != var->var_type) {
                zig_panic("TODO debug info for var with ptr casted value");
            }
            ZigType *var_type = g->builtin_types.entry_f128;
            ZigValue coerced_value = {};
            coerced_value.special = ConstValSpecialStatic;
            coerced_value.type = var_type;
            coerced_value.data.x_f128 = bigfloat_to_f128(&const_val->data.x_bigfloat);
            LLVMValueRef init_val = gen_const_val(g, &coerced_value, "");
            gen_global_var(g, var, init_val, var_type);
            continue;
        }

        if (var->var_type->id == ZigTypeIdComptimeInt) {
            // Generate debug info for it but that's it.
            ZigValue *const_val = var->const_value;
            assert(const_val->special != ConstValSpecialRuntime);
            if ((err = ir_resolve_lazy(g, var->decl_node, const_val)))
                zig_unreachable();
            if (const_val->type != var->var_type) {
                zig_panic("TODO debug info for var with ptr casted value");
            }
            size_t bits_needed = bigint_bits_needed(&const_val->data.x_bigint);
            if (bits_needed < 8) {
                bits_needed = 8;
            }
            ZigType *var_type = get_int_type(g, const_val->data.x_bigint.is_negative, bits_needed);
            LLVMValueRef init_val = bigint_to_llvm_const(get_llvm_type(g, var_type), &const_val->data.x_bigint);
            gen_global_var(g, var, init_val, var_type);
            continue;
        }

        if (!type_has_bits(var->var_type))
            continue;

        assert(var->decl_node);

        GlobalLinkageId linkage;
        const char *unmangled_name = var->name;
        const char *symbol_name;
        if (var->export_list.length == 0) {
            if (var->decl_node->data.variable_declaration.is_extern) {
                symbol_name = unmangled_name;
                linkage = GlobalLinkageIdStrong;
            } else {
                symbol_name = get_mangled_name(g, unmangled_name, false);
                linkage = GlobalLinkageIdInternal;
            }
        } else {
            GlobalExport *global_export = &var->export_list.items[0];
            symbol_name = buf_ptr(&global_export->name);
            linkage = global_export->linkage;
        }

        LLVMValueRef global_value;
        bool externally_initialized = var->decl_node->data.variable_declaration.expr == nullptr;
        if (externally_initialized) {
            LLVMValueRef existing_llvm_var = LLVMGetNamedGlobal(g->module, symbol_name);
            if (existing_llvm_var) {
                global_value = LLVMConstBitCast(existing_llvm_var,
                        LLVMPointerType(get_llvm_type(g, var->var_type), 0));
            } else {
                global_value = LLVMAddGlobal(g->module, get_llvm_type(g, var->var_type), symbol_name);
                // TODO debug info for the extern variable

                LLVMSetLinkage(global_value, to_llvm_linkage(linkage));
                maybe_import_dll(g, global_value, GlobalLinkageIdStrong);
                LLVMSetAlignment(global_value, var->align_bytes);
                LLVMSetGlobalConstant(global_value, var->gen_is_const);
                set_global_tls(g, var, global_value);
            }
        } else {
            bool exported = (linkage != GlobalLinkageIdInternal);
            render_const_val(g, var->const_value, symbol_name);
            render_const_val_global(g, var->const_value, symbol_name);
            global_value = var->const_value->llvm_global;

            if (exported) {
                LLVMSetLinkage(global_value, to_llvm_linkage(linkage));
                maybe_export_dll(g, global_value, GlobalLinkageIdStrong);
            }
            if (var->section_name) {
                LLVMSetSection(global_value, buf_ptr(var->section_name));
            }
            LLVMSetAlignment(global_value, var->align_bytes);

            // TODO debug info for function pointers
            // Here we use const_value->type because that's the type of the llvm global,
            // which we const ptr cast upon use to whatever it needs to be.
            if (var->gen_is_const && var->const_value->type->id != ZigTypeIdFn) {
                gen_global_var(g, var, var->const_value->llvm_value, var->const_value->type);
            }

            LLVMSetGlobalConstant(global_value, var->gen_is_const);
            set_global_tls(g, var, global_value);
        }

        var->value_ref = global_value;

        for (size_t export_i = 1; export_i < var->export_list.length; export_i += 1) {
            GlobalExport *global_export = &var->export_list.items[export_i];
            LLVMAddAlias(g->module, LLVMTypeOf(var->value_ref), var->value_ref, buf_ptr(&global_export->name));
        }
    }

    // Generate function definitions.
    stage2_progress_update_node(g->sub_progress_node, 0, g->fn_defs.length);
    for (size_t fn_i = 0; fn_i < g->fn_defs.length; fn_i += 1) {
        ZigFn *fn_table_entry = g->fn_defs.at(fn_i);
        Stage2ProgressNode *fn_prog_node = stage2_progress_start(g->sub_progress_node,
                buf_ptr(&fn_table_entry->symbol_name), buf_len(&fn_table_entry->symbol_name), 0);

        FnTypeId *fn_type_id = &fn_table_entry->type_entry->data.fn.fn_type_id;
        CallingConvention cc = fn_type_id->cc;
        bool is_c_abi = cc == CallingConventionC;
        bool want_sret = want_first_arg_sret(g, fn_type_id);

        LLVMValueRef fn = fn_llvm_value(g, fn_table_entry);
        g->cur_fn = fn_table_entry;
        g->cur_fn_val = fn;

        build_all_basic_blocks(g, fn_table_entry);
        clear_debug_source_node(g);

        bool is_async = fn_is_async(fn_table_entry);

        if (is_async) {
            g->cur_frame_ptr = LLVMGetParam(fn, 0);
        } else {
            if (want_sret) {
                g->cur_ret_ptr = LLVMGetParam(fn, 0);
            } else if (handle_is_ptr(fn_type_id->return_type)) {
                g->cur_ret_ptr = build_alloca(g, fn_type_id->return_type, "result", 0);
                // TODO add debug info variable for this
            } else {
                g->cur_ret_ptr = nullptr;
            }
        }

        uint32_t err_ret_trace_arg_index = get_err_ret_trace_arg_index(g, fn_table_entry);
        bool have_err_ret_trace_arg = err_ret_trace_arg_index != UINT32_MAX;
        if (have_err_ret_trace_arg) {
            g->cur_err_ret_trace_val_arg = LLVMGetParam(fn, err_ret_trace_arg_index);
        } else {
            g->cur_err_ret_trace_val_arg = nullptr;
        }

        // error return tracing setup
        bool have_err_ret_trace_stack = g->have_err_ret_tracing && fn_table_entry->calls_or_awaits_errorable_fn &&
            !is_async && !have_err_ret_trace_arg;
        LLVMValueRef err_ret_array_val = nullptr;
        if (have_err_ret_trace_stack) {
            ZigType *array_type = get_array_type(g, g->builtin_types.entry_usize, stack_trace_ptr_count, nullptr);
            err_ret_array_val = build_alloca(g, array_type, "error_return_trace_addresses", get_abi_alignment(g, array_type));

            (void)get_llvm_type(g, get_stack_trace_type(g));
            g->cur_err_ret_trace_val_stack = build_alloca(g, get_stack_trace_type(g), "error_return_trace",
                    get_abi_alignment(g, g->stack_trace_type));
        } else {
            g->cur_err_ret_trace_val_stack = nullptr;
        }

        if (!is_async) {
            // allocate async frames for noasync calls & awaits to async functions
            ZigType *largest_call_frame_type = nullptr;
            IrInstruction *all_calls_alloca = ir_create_alloca(g, &fn_table_entry->fndef_scope->base,
                    fn_table_entry->body_node, fn_table_entry, g->builtin_types.entry_void, "@async_call_frame");
            for (size_t i = 0; i < fn_table_entry->call_list.length; i += 1) {
                IrInstructionCallGen *call = fn_table_entry->call_list.at(i);
                if (call->fn_entry == nullptr)
                    continue;
                if (!fn_is_async(call->fn_entry))
                    continue;
                if (call->modifier != CallModifierNoAsync)
                    continue;
                if (call->frame_result_loc != nullptr)
                    continue;
                ZigType *callee_frame_type = get_fn_frame_type(g, call->fn_entry);
                if (largest_call_frame_type == nullptr ||
                    callee_frame_type->abi_size > largest_call_frame_type->abi_size)
                {
                    largest_call_frame_type = callee_frame_type;
                }
                call->frame_result_loc = all_calls_alloca;
            }
            if (largest_call_frame_type != nullptr) {
                all_calls_alloca->value->type = get_pointer_to_type(g, largest_call_frame_type, false);
            }
            // allocate temporary stack data
            for (size_t alloca_i = 0; alloca_i < fn_table_entry->alloca_gen_list.length; alloca_i += 1) {
                IrInstructionAllocaGen *instruction = fn_table_entry->alloca_gen_list.at(alloca_i);
                ZigType *ptr_type = instruction->base.value->type;
                assert(ptr_type->id == ZigTypeIdPointer);
                ZigType *child_type = ptr_type->data.pointer.child_type;
                if (type_resolve(g, child_type, ResolveStatusSizeKnown))
                    zig_unreachable();
                if (!type_has_bits(child_type))
                    continue;
                if (instruction->base.ref_count == 0)
                    continue;
                if (instruction->base.value->special != ConstValSpecialRuntime) {
                    if (const_ptr_pointee(nullptr, g, instruction->base.value, nullptr)->special !=
                            ConstValSpecialRuntime)
                    {
                        continue;
                    }
                }
                if (type_resolve(g, child_type, ResolveStatusLLVMFull))
                    zig_unreachable();
                instruction->base.llvm_value = build_alloca(g, child_type, instruction->name_hint,
                        get_ptr_align(g, ptr_type));
            }
        }

        ZigType *import = get_scope_import(&fn_table_entry->fndef_scope->base);
        unsigned gen_i_init = want_sret ? 1 : 0;

        // create debug variable declarations for variables and allocate all local variables
        FnWalk fn_walk_var = {};
        fn_walk_var.id = FnWalkIdVars;
        fn_walk_var.data.vars.import = import;
        fn_walk_var.data.vars.fn = fn_table_entry;
        fn_walk_var.data.vars.llvm_fn = fn;
        fn_walk_var.data.vars.gen_i = gen_i_init;
        for (size_t var_i = 0; var_i < fn_table_entry->variable_list.length; var_i += 1) {
            ZigVar *var = fn_table_entry->variable_list.at(var_i);

            if (!type_has_bits(var->var_type)) {
                continue;
            }
            if (ir_get_var_is_comptime(var))
                continue;
            switch (type_requires_comptime(g, var->var_type)) {
                case ReqCompTimeInvalid:
                    zig_unreachable();
                case ReqCompTimeYes:
                    continue;
                case ReqCompTimeNo:
                    break;
            }

            if (var->src_arg_index == SIZE_MAX) {
                var->di_loc_var = ZigLLVMCreateAutoVariable(g->dbuilder, get_di_scope(g, var->parent_scope),
                        var->name, import->data.structure.root_struct->di_file, (unsigned)(var->decl_node->line + 1),
                        get_llvm_di_type(g, var->var_type), !g->strip_debug_symbols, 0);

            } else if (is_c_abi) {
                fn_walk_var.data.vars.var = var;
                iter_function_params_c_abi(g, fn_table_entry->type_entry, &fn_walk_var, var->src_arg_index);
            } else if (!is_async) {
                ZigType *gen_type;
                FnGenParamInfo *gen_info = &fn_table_entry->type_entry->data.fn.gen_param_info[var->src_arg_index];
                assert(gen_info->gen_index != SIZE_MAX);

                if (handle_is_ptr(var->var_type)) {
                    if (gen_info->is_byval) {
                        gen_type = var->var_type;
                    } else {
                        gen_type = gen_info->type;
                    }
                    var->value_ref = LLVMGetParam(fn, gen_info->gen_index);
                } else {
                    gen_type = var->var_type;
                    var->value_ref = build_alloca(g, var->var_type, var->name, var->align_bytes);
                }
                if (var->decl_node) {
                    var->di_loc_var = ZigLLVMCreateParameterVariable(g->dbuilder, get_di_scope(g, var->parent_scope),
                        var->name, import->data.structure.root_struct->di_file,
                        (unsigned)(var->decl_node->line + 1),
                        get_llvm_di_type(g, gen_type), !g->strip_debug_symbols, 0, (unsigned)(gen_info->gen_index+1));
                }

            }
        }

        // finishing error return trace setup. we have to do this after all the allocas.
        if (have_err_ret_trace_stack) {
            ZigType *usize = g->builtin_types.entry_usize;
            size_t index_field_index = g->stack_trace_type->data.structure.fields[0]->gen_index;
            LLVMValueRef index_field_ptr = LLVMBuildStructGEP(g->builder, g->cur_err_ret_trace_val_stack, (unsigned)index_field_index, "");
            gen_store_untyped(g, LLVMConstNull(usize->llvm_type), index_field_ptr, 0, false);

            size_t addresses_field_index = g->stack_trace_type->data.structure.fields[1]->gen_index;
            LLVMValueRef addresses_field_ptr = LLVMBuildStructGEP(g->builder, g->cur_err_ret_trace_val_stack, (unsigned)addresses_field_index, "");

            ZigType *slice_type = g->stack_trace_type->data.structure.fields[1]->type_entry;
            size_t ptr_field_index = slice_type->data.structure.fields[slice_ptr_index]->gen_index;
            LLVMValueRef ptr_field_ptr = LLVMBuildStructGEP(g->builder, addresses_field_ptr, (unsigned)ptr_field_index, "");
            LLVMValueRef zero = LLVMConstNull(usize->llvm_type);
            LLVMValueRef indices[] = {zero, zero};
            LLVMValueRef err_ret_array_val_elem0_ptr = LLVMBuildInBoundsGEP(g->builder, err_ret_array_val,
                    indices, 2, "");
            ZigType *ptr_ptr_usize_type = get_pointer_to_type(g, get_pointer_to_type(g, usize, false), false);
            gen_store(g, err_ret_array_val_elem0_ptr, ptr_field_ptr, ptr_ptr_usize_type);

            size_t len_field_index = slice_type->data.structure.fields[slice_len_index]->gen_index;
            LLVMValueRef len_field_ptr = LLVMBuildStructGEP(g->builder, addresses_field_ptr, (unsigned)len_field_index, "");
            gen_store(g, LLVMConstInt(usize->llvm_type, stack_trace_ptr_count, false), len_field_ptr, get_pointer_to_type(g, usize, false));
        }

        if (is_async) {
            (void)get_llvm_type(g, fn_table_entry->frame_type);
            g->cur_resume_block_count = 0;

            LLVMTypeRef usize_type_ref = g->builtin_types.entry_usize->llvm_type;
            LLVMValueRef size_val = LLVMConstInt(usize_type_ref, fn_table_entry->frame_type->abi_size, false);
            if (g->need_frame_size_prefix_data) {
                ZigLLVMFunctionSetPrefixData(fn_table_entry->llvm_value, size_val);
            }

            if (!g->strip_debug_symbols) {
                AstNode *source_node = fn_table_entry->proto_node;
                ZigLLVMSetCurrentDebugLocation(g->builder, (int)source_node->line + 1,
                        (int)source_node->column + 1, get_di_scope(g, fn_table_entry->child_scope));
            }
            IrExecutable *executable = &fn_table_entry->analyzed_executable;
            LLVMBasicBlockRef bad_resume_block = LLVMAppendBasicBlock(g->cur_fn_val, "BadResume");
            LLVMPositionBuilderAtEnd(g->builder, bad_resume_block);
            gen_assertion_scope(g, PanicMsgIdBadResume, fn_table_entry->child_scope);

            LLVMPositionBuilderAtEnd(g->builder, g->cur_preamble_llvm_block);
            render_async_spills(g);
            g->cur_async_awaiter_ptr = LLVMBuildStructGEP(g->builder, g->cur_frame_ptr, frame_awaiter_index, "");
            LLVMValueRef resume_index_ptr = LLVMBuildStructGEP(g->builder, g->cur_frame_ptr, frame_resume_index, "");
            g->cur_async_resume_index_ptr = resume_index_ptr;

            if (type_has_bits(fn_type_id->return_type)) {
                LLVMValueRef cur_ret_ptr_ptr = LLVMBuildStructGEP(g->builder, g->cur_frame_ptr, frame_ret_start, "");
                g->cur_ret_ptr = LLVMBuildLoad(g->builder, cur_ret_ptr_ptr, "");
            }
            uint32_t trace_field_index_stack = UINT32_MAX;
            if (codegen_fn_has_err_ret_tracing_stack(g, fn_table_entry, true)) {
                trace_field_index_stack = frame_index_trace_stack(g, fn_type_id);
                g->cur_err_ret_trace_val_stack = LLVMBuildStructGEP(g->builder, g->cur_frame_ptr,
                        trace_field_index_stack, "");
            }

            LLVMValueRef resume_index = LLVMBuildLoad(g->builder, resume_index_ptr, "");
            LLVMValueRef switch_instr = LLVMBuildSwitch(g->builder, resume_index, bad_resume_block, 4);
            g->cur_async_switch_instr = switch_instr;

            LLVMValueRef zero = LLVMConstNull(usize_type_ref);
            IrBasicBlock *entry_block = executable->basic_block_list.at(0);
            LLVMAddCase(switch_instr, zero, entry_block->llvm_block);
            g->cur_resume_block_count += 1;

            {
                LLVMBasicBlockRef bad_not_suspended_bb = LLVMAppendBasicBlock(g->cur_fn_val, "NotSuspended");
                size_t new_block_index = g->cur_resume_block_count;
                g->cur_resume_block_count += 1;
                g->cur_bad_not_suspended_index = LLVMConstInt(usize_type_ref, new_block_index, false);
                LLVMAddCase(g->cur_async_switch_instr, g->cur_bad_not_suspended_index, bad_not_suspended_bb);

                LLVMPositionBuilderAtEnd(g->builder, bad_not_suspended_bb);
                gen_assertion_scope(g, PanicMsgIdResumeNotSuspendedFn, fn_table_entry->child_scope);
            }

            LLVMPositionBuilderAtEnd(g->builder, entry_block->llvm_block);
            LLVMBuildStore(g->builder, g->cur_bad_not_suspended_index, g->cur_async_resume_index_ptr);
            if (trace_field_index_stack != UINT32_MAX) {
                if (codegen_fn_has_err_ret_tracing_arg(g, fn_type_id->return_type)) {
                    LLVMValueRef trace_ptr_ptr = LLVMBuildStructGEP(g->builder, g->cur_frame_ptr,
                            frame_index_trace_arg(g, fn_type_id->return_type), "");
                    LLVMValueRef zero_ptr = LLVMConstNull(LLVMGetElementType(LLVMTypeOf(trace_ptr_ptr)));
                    LLVMBuildStore(g->builder, zero_ptr, trace_ptr_ptr);
                }

                LLVMValueRef trace_field_ptr = LLVMBuildStructGEP(g->builder, g->cur_frame_ptr,
                        trace_field_index_stack, "");
                LLVMValueRef addrs_field_ptr = LLVMBuildStructGEP(g->builder, g->cur_frame_ptr,
                        trace_field_index_stack + 1, "");

                gen_init_stack_trace(g, trace_field_ptr, addrs_field_ptr);
            }
            render_async_var_decls(g, entry_block->instruction_list.at(0)->scope);
        } else {
            // create debug variable declarations for parameters
            // rely on the first variables in the variable_list being parameters.
            FnWalk fn_walk_init = {};
            fn_walk_init.id = FnWalkIdInits;
            fn_walk_init.data.inits.fn = fn_table_entry;
            fn_walk_init.data.inits.llvm_fn = fn;
            fn_walk_init.data.inits.gen_i = gen_i_init;
            walk_function_params(g, fn_table_entry->type_entry, &fn_walk_init);
        }

        ir_render(g, fn_table_entry);

        stage2_progress_end(fn_prog_node);
    }

    assert(!g->errors.length);

    if (buf_len(&g->global_asm) != 0) {
        LLVMSetModuleInlineAsm(g->module, buf_ptr(&g->global_asm));
    }

    while (g->type_resolve_stack.length != 0) {
        ZigType *ty = g->type_resolve_stack.last();
        if (type_resolve(g, ty, ResolveStatusLLVMFull))
            zig_unreachable();
    }

    ZigLLVMDIBuilderFinalize(g->dbuilder);

    if (g->verbose_llvm_ir) {
        fflush(stderr);
        LLVMDumpModule(g->module);
    }

    char *error = nullptr;
    if (LLVMVerifyModule(g->module, LLVMReturnStatusAction, &error)) {
        zig_panic("broken LLVM module found: %s\nThis is a bug in the Zig compiler.", error);
    }
}

static void zig_llvm_emit_output(CodeGen *g) {
    bool is_small = g->build_mode == BuildModeSmallRelease;

    Buf *output_path = &g->o_file_output_path;
    char *err_msg = nullptr;
    switch (g->emit_file_type) {
        case EmitFileTypeBinary:
            if (g->disable_bin_generation)
                return;
            if (ZigLLVMTargetMachineEmitToFile(g->target_machine, g->module, buf_ptr(output_path),
                        ZigLLVM_EmitBinary, &err_msg, g->build_mode == BuildModeDebug, is_small,
                        g->enable_time_report))
            {
                zig_panic("unable to write object file %s: %s", buf_ptr(output_path), err_msg);
            }
            validate_inline_fns(g);
            g->link_objects.append(output_path);
            if (g->bundle_compiler_rt && (g->out_type == OutTypeObj ||
                (g->out_type == OutTypeLib && !g->is_dynamic)))
            {
                zig_link_add_compiler_rt(g, g->sub_progress_node);
            }
            break;

        case EmitFileTypeAssembly:
            if (ZigLLVMTargetMachineEmitToFile(g->target_machine, g->module, buf_ptr(output_path),
                        ZigLLVM_EmitAssembly, &err_msg, g->build_mode == BuildModeDebug, is_small,
                        g->enable_time_report))
            {
                zig_panic("unable to write assembly file %s: %s", buf_ptr(output_path), err_msg);
            }
            validate_inline_fns(g);
            break;

        case EmitFileTypeLLVMIr:
            if (ZigLLVMTargetMachineEmitToFile(g->target_machine, g->module, buf_ptr(output_path),
                        ZigLLVM_EmitLLVMIr, &err_msg, g->build_mode == BuildModeDebug, is_small,
                        g->enable_time_report))
            {
                zig_panic("unable to write llvm-ir file %s: %s", buf_ptr(output_path), err_msg);
            }
            validate_inline_fns(g);
            break;

        default:
            zig_unreachable();
    }
}

struct CIntTypeInfo {
    CIntType id;
    const char *name;
    bool is_signed;
};

static const CIntTypeInfo c_int_type_infos[] = {
    {CIntTypeShort, "c_short", true},
    {CIntTypeUShort, "c_ushort", false},
    {CIntTypeInt, "c_int", true},
    {CIntTypeUInt, "c_uint", false},
    {CIntTypeLong, "c_long", true},
    {CIntTypeULong, "c_ulong", false},
    {CIntTypeLongLong, "c_longlong", true},
    {CIntTypeULongLong, "c_ulonglong", false},
};

static const bool is_signed_list[] = { false, true, };

struct GlobalLinkageValue {
    GlobalLinkageId id;
    const char *name;
};

static void add_fp_entry(CodeGen *g, const char *name, uint32_t bit_count, LLVMTypeRef type_ref,
        ZigType **field)
{
    ZigType *entry = new_type_table_entry(ZigTypeIdFloat);
    entry->llvm_type = type_ref;
    entry->size_in_bits = 8*LLVMStoreSizeOfType(g->target_data_ref, entry->llvm_type);
    entry->abi_size = LLVMABISizeOfType(g->target_data_ref, entry->llvm_type);
    entry->abi_align = LLVMABIAlignmentOfType(g->target_data_ref, entry->llvm_type);
    buf_init_from_str(&entry->name, name);
    entry->data.floating.bit_count = bit_count;

    entry->llvm_di_type = ZigLLVMCreateDebugBasicType(g->dbuilder, buf_ptr(&entry->name),
            entry->size_in_bits, ZigLLVMEncoding_DW_ATE_float());
    *field = entry;
    g->primitive_type_table.put(&entry->name, entry);
}

static void define_builtin_types(CodeGen *g) {
    {
        // if this type is anywhere in the AST, we should never hit codegen.
        ZigType *entry = new_type_table_entry(ZigTypeIdInvalid);
        buf_init_from_str(&entry->name, "(invalid)");
        g->builtin_types.entry_invalid = entry;
    }
    {
        ZigType *entry = new_type_table_entry(ZigTypeIdComptimeFloat);
        buf_init_from_str(&entry->name, "comptime_float");
        g->builtin_types.entry_num_lit_float = entry;
        g->primitive_type_table.put(&entry->name, entry);
    }
    {
        ZigType *entry = new_type_table_entry(ZigTypeIdComptimeInt);
        buf_init_from_str(&entry->name, "comptime_int");
        g->builtin_types.entry_num_lit_int = entry;
        g->primitive_type_table.put(&entry->name, entry);
    }
    {
        ZigType *entry = new_type_table_entry(ZigTypeIdEnumLiteral);
        buf_init_from_str(&entry->name, "(enum literal)");
        g->builtin_types.entry_enum_literal = entry;
    }
    {
        ZigType *entry = new_type_table_entry(ZigTypeIdUndefined);
        buf_init_from_str(&entry->name, "(undefined)");
        g->builtin_types.entry_undef = entry;
    }
    {
        ZigType *entry = new_type_table_entry(ZigTypeIdNull);
        buf_init_from_str(&entry->name, "(null)");
        g->builtin_types.entry_null = entry;
    }
    {
        ZigType *entry = new_type_table_entry(ZigTypeIdOpaque);
        buf_init_from_str(&entry->name, "(var)");
        g->builtin_types.entry_var = entry;
    }

    for (size_t i = 0; i < array_length(c_int_type_infos); i += 1) {
        const CIntTypeInfo *info = &c_int_type_infos[i];
        uint32_t size_in_bits = target_c_type_size_in_bits(g->zig_target, info->id);
        bool is_signed = info->is_signed;

        ZigType *entry = new_type_table_entry(ZigTypeIdInt);
        entry->llvm_type = LLVMIntType(size_in_bits);
        entry->size_in_bits = size_in_bits;
        entry->abi_size = LLVMABISizeOfType(g->target_data_ref, entry->llvm_type);
        entry->abi_align = LLVMABIAlignmentOfType(g->target_data_ref, entry->llvm_type);

        buf_init_from_str(&entry->name, info->name);

        entry->llvm_di_type = ZigLLVMCreateDebugBasicType(g->dbuilder, buf_ptr(&entry->name),
                8*LLVMStoreSizeOfType(g->target_data_ref, entry->llvm_type),
                is_signed ? ZigLLVMEncoding_DW_ATE_signed() : ZigLLVMEncoding_DW_ATE_unsigned());
        entry->data.integral.is_signed = is_signed;
        entry->data.integral.bit_count = size_in_bits;
        g->primitive_type_table.put(&entry->name, entry);

        get_c_int_type_ptr(g, info->id)[0] = entry;
    }

    {
        ZigType *entry = new_type_table_entry(ZigTypeIdBool);
        entry->llvm_type = LLVMInt1Type();
        entry->size_in_bits = 1;
        entry->abi_size = LLVMABISizeOfType(g->target_data_ref, entry->llvm_type);
        entry->abi_align = LLVMABIAlignmentOfType(g->target_data_ref, entry->llvm_type);
        buf_init_from_str(&entry->name, "bool");
        entry->llvm_di_type = ZigLLVMCreateDebugBasicType(g->dbuilder, buf_ptr(&entry->name),
                8*LLVMStoreSizeOfType(g->target_data_ref, entry->llvm_type),
                ZigLLVMEncoding_DW_ATE_boolean());
        g->builtin_types.entry_bool = entry;
        g->primitive_type_table.put(&entry->name, entry);
    }

    for (size_t sign_i = 0; sign_i < array_length(is_signed_list); sign_i += 1) {
        bool is_signed = is_signed_list[sign_i];

        ZigType *entry = new_type_table_entry(ZigTypeIdInt);
        entry->llvm_type = LLVMIntType(g->pointer_size_bytes * 8);
        entry->size_in_bits = g->pointer_size_bytes * 8;
        entry->abi_size = LLVMABISizeOfType(g->target_data_ref, entry->llvm_type);
        entry->abi_align = LLVMABIAlignmentOfType(g->target_data_ref, entry->llvm_type);

        const char u_or_i = is_signed ? 'i' : 'u';
        buf_resize(&entry->name, 0);
        buf_appendf(&entry->name, "%csize", u_or_i);

        entry->data.integral.is_signed = is_signed;
        entry->data.integral.bit_count = g->pointer_size_bytes * 8;

        entry->llvm_di_type = ZigLLVMCreateDebugBasicType(g->dbuilder, buf_ptr(&entry->name),
                8*LLVMStoreSizeOfType(g->target_data_ref, entry->llvm_type),
                is_signed ? ZigLLVMEncoding_DW_ATE_signed() : ZigLLVMEncoding_DW_ATE_unsigned());
        g->primitive_type_table.put(&entry->name, entry);

        if (is_signed) {
            g->builtin_types.entry_isize = entry;
        } else {
            g->builtin_types.entry_usize = entry;
        }
    }

    add_fp_entry(g, "f16", 16, LLVMHalfType(), &g->builtin_types.entry_f16);
    add_fp_entry(g, "f32", 32, LLVMFloatType(), &g->builtin_types.entry_f32);
    add_fp_entry(g, "f64", 64, LLVMDoubleType(), &g->builtin_types.entry_f64);
    add_fp_entry(g, "f128", 128, LLVMFP128Type(), &g->builtin_types.entry_f128);
    add_fp_entry(g, "c_longdouble", 80, LLVMX86FP80Type(), &g->builtin_types.entry_c_longdouble);

    {
        ZigType *entry = new_type_table_entry(ZigTypeIdVoid);
        entry->llvm_type = LLVMVoidType();
        buf_init_from_str(&entry->name, "void");
        entry->llvm_di_type = ZigLLVMCreateDebugBasicType(g->dbuilder, buf_ptr(&entry->name),
                0,
                ZigLLVMEncoding_DW_ATE_signed());
        g->builtin_types.entry_void = entry;
        g->primitive_type_table.put(&entry->name, entry);
    }
    {
        ZigType *entry = new_type_table_entry(ZigTypeIdUnreachable);
        entry->llvm_type = LLVMVoidType();
        buf_init_from_str(&entry->name, "noreturn");
        entry->llvm_di_type = g->builtin_types.entry_void->llvm_di_type;
        g->builtin_types.entry_unreachable = entry;
        g->primitive_type_table.put(&entry->name, entry);
    }
    {
        ZigType *entry = new_type_table_entry(ZigTypeIdMetaType);
        buf_init_from_str(&entry->name, "type");
        g->builtin_types.entry_type = entry;
        g->primitive_type_table.put(&entry->name, entry);
    }

    g->builtin_types.entry_u8 = get_int_type(g, false, 8);
    g->builtin_types.entry_u16 = get_int_type(g, false, 16);
    g->builtin_types.entry_u29 = get_int_type(g, false, 29);
    g->builtin_types.entry_u32 = get_int_type(g, false, 32);
    g->builtin_types.entry_u64 = get_int_type(g, false, 64);
    g->builtin_types.entry_i8 = get_int_type(g, true, 8);
    g->builtin_types.entry_i32 = get_int_type(g, true, 32);
    g->builtin_types.entry_i64 = get_int_type(g, true, 64);

    {
        g->builtin_types.entry_c_void = get_opaque_type(g, nullptr, nullptr, "c_void",
                buf_create_from_str("c_void"));
        g->primitive_type_table.put(&g->builtin_types.entry_c_void->name, g->builtin_types.entry_c_void);
    }

    {
        ZigType *entry = new_type_table_entry(ZigTypeIdErrorSet);
        buf_init_from_str(&entry->name, "anyerror");
        entry->data.error_set.err_count = UINT32_MAX;

        // TODO https://github.com/ziglang/zig/issues/786
        g->err_tag_type = g->builtin_types.entry_u16;

        entry->size_in_bits = g->err_tag_type->size_in_bits;
        entry->abi_align = g->err_tag_type->abi_align;
        entry->abi_size = g->err_tag_type->abi_size;

        g->builtin_types.entry_global_error_set = entry;

        g->errors_by_index.append(nullptr);

        g->primitive_type_table.put(&entry->name, entry);
    }
}

static void define_intern_values(CodeGen *g) {
    {
        auto& value = g->intern.x_undefined;
        value.type = g->builtin_types.entry_undef;
        value.special = ConstValSpecialStatic;
    }
    {
        auto& value = g->intern.x_void;
        value.type = g->builtin_types.entry_void;
        value.special = ConstValSpecialStatic;
    }
    {
        auto& value = g->intern.x_null;
        value.type = g->builtin_types.entry_null;
        value.special = ConstValSpecialStatic;
    }
    {
        auto& value = g->intern.x_unreachable;
        value.type = g->builtin_types.entry_unreachable;
        value.special = ConstValSpecialStatic;
    }
    {
        auto& value = g->intern.zero_byte;
        value.type = g->builtin_types.entry_u8;
        value.special = ConstValSpecialStatic;
        bigint_init_unsigned(&value.data.x_bigint, 0);
    }
}

static BuiltinFnEntry *create_builtin_fn(CodeGen *g, BuiltinFnId id, const char *name, size_t count) {
    BuiltinFnEntry *builtin_fn = allocate<BuiltinFnEntry>(1);
    buf_init_from_str(&builtin_fn->name, name);
    builtin_fn->id = id;
    builtin_fn->param_count = count;
    g->builtin_fn_table.put(&builtin_fn->name, builtin_fn);
    return builtin_fn;
}

static void define_builtin_fns(CodeGen *g) {
    create_builtin_fn(g, BuiltinFnIdBreakpoint, "breakpoint", 0);
    create_builtin_fn(g, BuiltinFnIdReturnAddress, "returnAddress", 0);
    create_builtin_fn(g, BuiltinFnIdMemcpy, "memcpy", 3);
    create_builtin_fn(g, BuiltinFnIdMemset, "memset", 3);
    create_builtin_fn(g, BuiltinFnIdSizeof, "sizeOf", 1);
    create_builtin_fn(g, BuiltinFnIdAlignOf, "alignOf", 1);
    create_builtin_fn(g, BuiltinFnIdMemberCount, "memberCount", 1);
    create_builtin_fn(g, BuiltinFnIdMemberType, "memberType", 2);
    create_builtin_fn(g, BuiltinFnIdMemberName, "memberName", 2);
    create_builtin_fn(g, BuiltinFnIdField, "field", 2);
    create_builtin_fn(g, BuiltinFnIdTypeInfo, "typeInfo", 1);
    create_builtin_fn(g, BuiltinFnIdType, "Type", 1);
    create_builtin_fn(g, BuiltinFnIdHasField, "hasField", 2);
    create_builtin_fn(g, BuiltinFnIdTypeof, "TypeOf", 1);
    create_builtin_fn(g, BuiltinFnIdAddWithOverflow, "addWithOverflow", 4);
    create_builtin_fn(g, BuiltinFnIdSubWithOverflow, "subWithOverflow", 4);
    create_builtin_fn(g, BuiltinFnIdMulWithOverflow, "mulWithOverflow", 4);
    create_builtin_fn(g, BuiltinFnIdShlWithOverflow, "shlWithOverflow", 4);
    create_builtin_fn(g, BuiltinFnIdCInclude, "cInclude", 1);
    create_builtin_fn(g, BuiltinFnIdCDefine, "cDefine", 2);
    create_builtin_fn(g, BuiltinFnIdCUndef, "cUndef", 1);
    create_builtin_fn(g, BuiltinFnIdCtz, "ctz", 2);
    create_builtin_fn(g, BuiltinFnIdClz, "clz", 2);
    create_builtin_fn(g, BuiltinFnIdPopCount, "popCount", 2);
    create_builtin_fn(g, BuiltinFnIdBswap, "byteSwap", 2);
    create_builtin_fn(g, BuiltinFnIdBitReverse, "bitReverse", 2);
    create_builtin_fn(g, BuiltinFnIdImport, "import", 1);
    create_builtin_fn(g, BuiltinFnIdCImport, "cImport", 1);
    create_builtin_fn(g, BuiltinFnIdErrName, "errorName", 1);
    create_builtin_fn(g, BuiltinFnIdTypeName, "typeName", 1);
    create_builtin_fn(g, BuiltinFnIdEmbedFile, "embedFile", 1);
    create_builtin_fn(g, BuiltinFnIdCmpxchgWeak, "cmpxchgWeak", 6);
    create_builtin_fn(g, BuiltinFnIdCmpxchgStrong, "cmpxchgStrong", 6);
    create_builtin_fn(g, BuiltinFnIdFence, "fence", 1);
    create_builtin_fn(g, BuiltinFnIdTruncate, "truncate", 2);
    create_builtin_fn(g, BuiltinFnIdIntCast, "intCast", 2);
    create_builtin_fn(g, BuiltinFnIdFloatCast, "floatCast", 2);
    create_builtin_fn(g, BuiltinFnIdIntToFloat, "intToFloat", 2);
    create_builtin_fn(g, BuiltinFnIdFloatToInt, "floatToInt", 2);
    create_builtin_fn(g, BuiltinFnIdBoolToInt, "boolToInt", 1);
    create_builtin_fn(g, BuiltinFnIdErrToInt, "errorToInt", 1);
    create_builtin_fn(g, BuiltinFnIdIntToErr, "intToError", 1);
    create_builtin_fn(g, BuiltinFnIdEnumToInt, "enumToInt", 1);
    create_builtin_fn(g, BuiltinFnIdIntToEnum, "intToEnum", 2);
    create_builtin_fn(g, BuiltinFnIdCompileErr, "compileError", 1);
    create_builtin_fn(g, BuiltinFnIdCompileLog, "compileLog", SIZE_MAX);
    create_builtin_fn(g, BuiltinFnIdIntType, "IntType", 2); // TODO rename to Int
    create_builtin_fn(g, BuiltinFnIdVectorType, "Vector", 2);
    create_builtin_fn(g, BuiltinFnIdShuffle, "shuffle", 4);
    create_builtin_fn(g, BuiltinFnIdSplat, "splat", 2);
    create_builtin_fn(g, BuiltinFnIdSetCold, "setCold", 1);
    create_builtin_fn(g, BuiltinFnIdSetRuntimeSafety, "setRuntimeSafety", 1);
    create_builtin_fn(g, BuiltinFnIdSetFloatMode, "setFloatMode", 1);
    create_builtin_fn(g, BuiltinFnIdPanic, "panic", 1);
    create_builtin_fn(g, BuiltinFnIdPtrCast, "ptrCast", 2);
    create_builtin_fn(g, BuiltinFnIdBitCast, "bitCast", 2);
    create_builtin_fn(g, BuiltinFnIdIntToPtr, "intToPtr", 2);
    create_builtin_fn(g, BuiltinFnIdPtrToInt, "ptrToInt", 1);
    create_builtin_fn(g, BuiltinFnIdTagName, "tagName", 1);
    create_builtin_fn(g, BuiltinFnIdTagType, "TagType", 1);
    create_builtin_fn(g, BuiltinFnIdFieldParentPtr, "fieldParentPtr", 3);
    create_builtin_fn(g, BuiltinFnIdByteOffsetOf, "byteOffsetOf", 2);
    create_builtin_fn(g, BuiltinFnIdBitOffsetOf, "bitOffsetOf", 2);
    create_builtin_fn(g, BuiltinFnIdDivExact, "divExact", 2);
    create_builtin_fn(g, BuiltinFnIdDivTrunc, "divTrunc", 2);
    create_builtin_fn(g, BuiltinFnIdDivFloor, "divFloor", 2);
    create_builtin_fn(g, BuiltinFnIdRem, "rem", 2);
    create_builtin_fn(g, BuiltinFnIdMod, "mod", 2);
    create_builtin_fn(g, BuiltinFnIdSqrt, "sqrt", 1);
    create_builtin_fn(g, BuiltinFnIdSin, "sin", 1);
    create_builtin_fn(g, BuiltinFnIdCos, "cos", 1);
    create_builtin_fn(g, BuiltinFnIdExp, "exp", 1);
    create_builtin_fn(g, BuiltinFnIdExp2, "exp2", 1);
    create_builtin_fn(g, BuiltinFnIdLog, "log", 1);
    create_builtin_fn(g, BuiltinFnIdLog2, "log2", 1);
    create_builtin_fn(g, BuiltinFnIdLog10, "log10", 1);
    create_builtin_fn(g, BuiltinFnIdFabs, "fabs", 1);
    create_builtin_fn(g, BuiltinFnIdFloor, "floor", 1);
    create_builtin_fn(g, BuiltinFnIdCeil, "ceil", 1);
    create_builtin_fn(g, BuiltinFnIdTrunc, "trunc", 1);
    create_builtin_fn(g, BuiltinFnIdNearbyInt, "nearbyInt", 1);
    create_builtin_fn(g, BuiltinFnIdRound, "round", 1);
    create_builtin_fn(g, BuiltinFnIdMulAdd, "mulAdd", 4);
    create_builtin_fn(g, BuiltinFnIdNewStackCall, "newStackCall", SIZE_MAX);
    create_builtin_fn(g, BuiltinFnIdAsyncCall, "asyncCall", SIZE_MAX);
    create_builtin_fn(g, BuiltinFnIdTypeId, "typeId", 1);
    create_builtin_fn(g, BuiltinFnIdShlExact, "shlExact", 2);
    create_builtin_fn(g, BuiltinFnIdShrExact, "shrExact", 2);
    create_builtin_fn(g, BuiltinFnIdSetEvalBranchQuota, "setEvalBranchQuota", 1);
    create_builtin_fn(g, BuiltinFnIdAlignCast, "alignCast", 2);
    create_builtin_fn(g, BuiltinFnIdOpaqueType, "OpaqueType", 0);
    create_builtin_fn(g, BuiltinFnIdSetAlignStack, "setAlignStack", 1);
    create_builtin_fn(g, BuiltinFnIdArgType, "ArgType", 2);
    create_builtin_fn(g, BuiltinFnIdExport, "export", 2);
    create_builtin_fn(g, BuiltinFnIdErrorReturnTrace, "errorReturnTrace", 0);
    create_builtin_fn(g, BuiltinFnIdAtomicRmw, "atomicRmw", 5);
    create_builtin_fn(g, BuiltinFnIdAtomicLoad, "atomicLoad", 3);
    create_builtin_fn(g, BuiltinFnIdAtomicStore, "atomicStore", 4);
    create_builtin_fn(g, BuiltinFnIdErrSetCast, "errSetCast", 2);
    create_builtin_fn(g, BuiltinFnIdToBytes, "sliceToBytes", 1);
    create_builtin_fn(g, BuiltinFnIdFromBytes, "bytesToSlice", 2);
    create_builtin_fn(g, BuiltinFnIdThis, "This", 0);
    create_builtin_fn(g, BuiltinFnIdHasDecl, "hasDecl", 2);
    create_builtin_fn(g, BuiltinFnIdUnionInit, "unionInit", 3);
    create_builtin_fn(g, BuiltinFnIdFrameHandle, "frame", 0);
    create_builtin_fn(g, BuiltinFnIdFrameType, "Frame", 1);
    create_builtin_fn(g, BuiltinFnIdFrameAddress, "frameAddress", 0);
    create_builtin_fn(g, BuiltinFnIdFrameSize, "frameSize", 1);
    create_builtin_fn(g, BuiltinFnIdAs, "as", 2);
    create_builtin_fn(g, BuiltinFnIdCall, "call", 3);
    create_builtin_fn(g, BuiltinFnIdBitSizeof, "bitSizeOf", 1);
}

static const char *bool_to_str(bool b) {
    return b ? "true" : "false";
}

static const char *build_mode_to_str(BuildMode build_mode) {
    switch (build_mode) {
        case BuildModeDebug: return "Mode.Debug";
        case BuildModeSafeRelease: return "Mode.ReleaseSafe";
        case BuildModeFastRelease: return "Mode.ReleaseFast";
        case BuildModeSmallRelease: return "Mode.ReleaseSmall";
    }
    zig_unreachable();
}

static const char *subsystem_to_str(TargetSubsystem subsystem) {
    switch (subsystem) {
        case TargetSubsystemConsole: return "Console";
        case TargetSubsystemWindows: return "Windows";
        case TargetSubsystemPosix: return "Posix";
        case TargetSubsystemNative: return "Native";
        case TargetSubsystemEfiApplication: return "EfiApplication";
        case TargetSubsystemEfiBootServiceDriver: return "EfiBootServiceDriver";
        case TargetSubsystemEfiRom: return "EfiRom";
        case TargetSubsystemEfiRuntimeDriver: return "EfiRuntimeDriver";
        case TargetSubsystemAuto: zig_unreachable();
    }
    zig_unreachable();
}

static bool detect_dynamic_link(CodeGen *g) {
    if (g->is_dynamic)
        return true;
    if (g->zig_target->os == OsFreestanding)
        return false;
    if (target_requires_pic(g->zig_target, g->libc_link_lib != nullptr))
        return true;
    // If there are no dynamic libraries then we can disable PIC
    for (size_t i = 0; i < g->link_libs_list.length; i += 1) {
        LinkLib *link_lib = g->link_libs_list.at(i);
        if (target_is_libc_lib_name(g->zig_target, buf_ptr(link_lib->name)))
            continue;
        return true;
    }
    return false;
}

static bool detect_pic(CodeGen *g) {
    if (target_requires_pic(g->zig_target, g->libc_link_lib != nullptr))
        return true;
    switch (g->want_pic) {
        case WantPICDisabled:
            return false;
        case WantPICEnabled:
            return true;
        case WantPICAuto:
            return g->have_dynamic_link;
    }
    zig_unreachable();
}

static bool detect_stack_probing(CodeGen *g) {
    if (!target_supports_stack_probing(g->zig_target))
        return false;
    switch (g->want_stack_check) {
        case WantStackCheckDisabled:
            return false;
        case WantStackCheckEnabled:
            return true;
        case WantStackCheckAuto:
            return g->build_mode == BuildModeSafeRelease || g->build_mode == BuildModeDebug;
    }
    zig_unreachable();
}

static bool detect_sanitize_c(CodeGen *g) {
    if (!target_supports_sanitize_c(g->zig_target))
        return false;
    switch (g->want_sanitize_c) {
        case WantCSanitizeDisabled:
            return false;
        case WantCSanitizeEnabled:
            return true;
        case WantCSanitizeAuto:
            return g->build_mode == BuildModeSafeRelease || g->build_mode == BuildModeDebug;
    }
    zig_unreachable();
}

// Returns TargetSubsystemAuto to mean "no subsystem"
TargetSubsystem detect_subsystem(CodeGen *g) {
    if (g->subsystem != TargetSubsystemAuto)
        return g->subsystem;
    if (g->zig_target->os == OsWindows) {
        if (g->have_dllmain_crt_startup || (g->out_type == OutTypeLib && g->is_dynamic))
            return TargetSubsystemAuto;
        if (g->have_c_main || g->is_test_build || g->have_winmain_crt_startup)
            return TargetSubsystemConsole;
        if (g->have_winmain)
            return TargetSubsystemWindows;
    } else if (g->zig_target->os == OsUefi) {
        return TargetSubsystemEfiApplication;
    }
    return TargetSubsystemAuto;
}

static bool detect_single_threaded(CodeGen *g) {
    if (g->want_single_threaded)
        return true;
    if (target_is_single_threaded(g->zig_target)) {
        return true;
    }
    return false;
}

static bool detect_err_ret_tracing(CodeGen *g) {
    return !g->strip_debug_symbols &&
        g->build_mode != BuildModeFastRelease &&
        g->build_mode != BuildModeSmallRelease;
}

Buf *codegen_generate_builtin_source(CodeGen *g) {
    g->have_dynamic_link = detect_dynamic_link(g);
    g->have_pic = detect_pic(g);
    g->have_stack_probing = detect_stack_probing(g);
    g->have_sanitize_c = detect_sanitize_c(g);
    g->is_single_threaded = detect_single_threaded(g);
    g->have_err_ret_tracing = detect_err_ret_tracing(g);

    Buf *contents = buf_alloc();
    buf_appendf(contents, "usingnamespace @import(\"std\").builtin;\n\n");

    const char *cur_os = nullptr;
    {
        uint32_t field_count = (uint32_t)target_os_count();
        for (uint32_t i = 0; i < field_count; i += 1) {
            Os os_type = target_os_enum(i);
            const char *name = target_os_name(os_type);

            if (os_type == g->zig_target->os) {
                g->target_os_index = i;
                cur_os = name;
            }
        }
    }
    assert(cur_os != nullptr);

    const char *cur_arch = nullptr;
    {
        uint32_t field_count = (uint32_t)target_arch_count();
        for (uint32_t arch_i = 0; arch_i < field_count; arch_i += 1) {
            ZigLLVM_ArchType arch = target_arch_enum(arch_i);
            const char *arch_name = target_arch_name(arch);
            SubArchList sub_arch_list = target_subarch_list(arch);
            if (sub_arch_list == SubArchListNone) {
                if (arch == g->zig_target->arch) {
                    g->target_arch_index = arch_i;
                    cur_arch = buf_ptr(buf_sprintf("Arch.%s", arch_name));
                }
            } else {
                const char *sub_arch_list_name = target_subarch_list_name(sub_arch_list);
                if (arch == g->zig_target->arch) {
                    size_t sub_count = target_subarch_count(sub_arch_list);
                    for (size_t sub_i = 0; sub_i < sub_count; sub_i += 1) {
                        ZigLLVM_SubArchType sub = target_subarch_enum(sub_arch_list, sub_i);
                        if (sub == g->zig_target->sub_arch) {
                            g->target_sub_arch_index = sub_i;
                            cur_arch = buf_ptr(buf_sprintf("Arch{ .%s = Arch.%s.%s }",
                                        arch_name, sub_arch_list_name, target_subarch_name(sub)));
                        }
                    }
                }
            }
        }
    }
    assert(cur_arch != nullptr);

    const char *cur_abi = nullptr;
    {
        uint32_t field_count = (uint32_t)target_abi_count();
        for (uint32_t i = 0; i < field_count; i += 1) {
            ZigLLVM_EnvironmentType abi = target_abi_enum(i);
            const char *name = target_abi_name(abi);

            if (abi == g->zig_target->abi) {
                g->target_abi_index = i;
                cur_abi = name;
            }
        }
    }
    assert(cur_abi != nullptr);

    const char *cur_obj_fmt = nullptr;
    {
        uint32_t field_count = (uint32_t)target_oformat_count();
        for (uint32_t i = 0; i < field_count; i += 1) {
            ZigLLVM_ObjectFormatType oformat = target_oformat_enum(i);
            const char *name = target_oformat_name(oformat);

            ZigLLVM_ObjectFormatType target_oformat = target_object_format(g->zig_target);
            if (oformat == target_oformat) {
                g->target_oformat_index = i;
                cur_obj_fmt = name;
            }
        }

    }
    assert(cur_obj_fmt != nullptr);

    // If any of these asserts trip then you need to either fix the internal compiler enum
    // or the corresponding one in std.Target or std.builtin.
    static_assert(ContainerLayoutAuto == 0, "");
    static_assert(ContainerLayoutExtern == 1, "");
    static_assert(ContainerLayoutPacked == 2, "");

    static_assert(CallingConventionUnspecified == 0, "");
    static_assert(CallingConventionC == 1, "");
    static_assert(CallingConventionCold == 2, "");
    static_assert(CallingConventionNaked == 3, "");
    static_assert(CallingConventionAsync == 4, "");
    static_assert(CallingConventionInterrupt == 5, "");
    static_assert(CallingConventionSignal == 6, "");
    static_assert(CallingConventionStdcall == 7, "");
    static_assert(CallingConventionFastcall == 8, "");
    static_assert(CallingConventionVectorcall == 9, "");
    static_assert(CallingConventionThiscall == 10, "");
    static_assert(CallingConventionAPCS == 11, "");
    static_assert(CallingConventionAAPCS == 12, "");
    static_assert(CallingConventionAAPCSVFP == 13, "");

    static_assert(FnInlineAuto == 0, "");
    static_assert(FnInlineAlways == 1, "");
    static_assert(FnInlineNever == 2, "");

    static_assert(BuiltinPtrSizeOne == 0, "");
    static_assert(BuiltinPtrSizeMany == 1, "");
    static_assert(BuiltinPtrSizeSlice == 2, "");
    static_assert(BuiltinPtrSizeC == 3, "");

    static_assert(TargetSubsystemConsole == 0, "");
    static_assert(TargetSubsystemWindows == 1, "");
    static_assert(TargetSubsystemPosix == 2, "");
    static_assert(TargetSubsystemNative == 3, "");
    static_assert(TargetSubsystemEfiApplication == 4, "");
    static_assert(TargetSubsystemEfiBootServiceDriver == 5, "");
    static_assert(TargetSubsystemEfiRom == 6, "");
    static_assert(TargetSubsystemEfiRuntimeDriver == 7, "");
    {
        const char *endian_str = g->is_big_endian ? "Endian.Big" : "Endian.Little";
        buf_appendf(contents, "pub const endian = %s;\n", endian_str);
    }
    const char *out_type = nullptr;
    switch (g->out_type) {
        case OutTypeExe:
            out_type = "Exe";
            break;
        case OutTypeLib:
            out_type = "Lib";
            break;
        case OutTypeObj:
        case OutTypeUnknown: // This happens when running the `zig builtin` command.
            out_type = "Obj";
            break;
    }
    buf_appendf(contents, "pub const output_mode = OutputMode.%s;\n", out_type);
    const char *link_type = g->is_dynamic ? "Dynamic" : "Static";
    buf_appendf(contents, "pub const link_mode = LinkMode.%s;\n", link_type);
    buf_appendf(contents, "pub const is_test = %s;\n", bool_to_str(g->is_test_build));
    buf_appendf(contents, "pub const single_threaded = %s;\n", bool_to_str(g->is_single_threaded));
    buf_appendf(contents, "pub const os = Os.%s;\n", cur_os);
    buf_appendf(contents, "pub const arch = %s;\n", cur_arch);
    buf_appendf(contents, "pub const abi = Abi.%s;\n", cur_abi);
    if (g->libc_link_lib != nullptr && g->zig_target->glibc_version != nullptr) {
        buf_appendf(contents,
            "pub const glibc_version: ?Version = Version{.major = %d, .minor = %d, .patch = %d};\n",
                g->zig_target->glibc_version->major,
                g->zig_target->glibc_version->minor,
                g->zig_target->glibc_version->patch);
    } else {
        buf_appendf(contents, "pub const glibc_version: ?Version = null;\n");
    }
    buf_appendf(contents, "pub const object_format = ObjectFormat.%s;\n", cur_obj_fmt);
    buf_appendf(contents, "pub const mode = %s;\n", build_mode_to_str(g->build_mode));
    buf_appendf(contents, "pub const link_libc = %s;\n", bool_to_str(g->libc_link_lib != nullptr));
    buf_appendf(contents, "pub const have_error_return_tracing = %s;\n", bool_to_str(g->have_err_ret_tracing));
    buf_appendf(contents, "pub const valgrind_support = %s;\n", bool_to_str(want_valgrind_support(g)));
    buf_appendf(contents, "pub const position_independent_code = %s;\n", bool_to_str(g->have_pic));
    buf_appendf(contents, "pub const strip_debug_info = %s;\n", bool_to_str(g->strip_debug_symbols));

    {
        TargetSubsystem detected_subsystem = detect_subsystem(g);
        if (detected_subsystem != TargetSubsystemAuto) {
            buf_appendf(contents, "pub const explicit_subsystem = SubSystem.%s;\n", subsystem_to_str(detected_subsystem));
        }
    }

    if (g->is_test_build) {
        buf_appendf(contents,
            "pub var test_functions: []TestFn = undefined; // overwritten later\n"
        );
    }

    return contents;
}

static ZigPackage *create_test_runner_pkg(CodeGen *g) {
    return codegen_create_package(g, buf_ptr(g->zig_std_special_dir), "test_runner.zig", "std.special");
}

static Error define_builtin_compile_vars(CodeGen *g) {
    if (g->std_package == nullptr)
        return ErrorNone;

    Error err;

    Buf *manifest_dir = buf_alloc();
    os_path_join(get_global_cache_dir(), buf_create_from_str("builtin"), manifest_dir);

    CacheHash cache_hash;
    cache_init(&cache_hash, manifest_dir);

    Buf *compiler_id;
    if ((err = get_compiler_id(&compiler_id)))
        return err;

    // Only a few things affect builtin.zig
    cache_buf(&cache_hash, compiler_id);
    cache_int(&cache_hash, g->build_mode);
    cache_bool(&cache_hash, g->strip_debug_symbols);
    cache_int(&cache_hash, g->out_type);
    cache_bool(&cache_hash, g->is_dynamic);
    cache_bool(&cache_hash, g->is_test_build);
    cache_bool(&cache_hash, g->is_single_threaded);
    cache_int(&cache_hash, g->zig_target->is_native);
    cache_int(&cache_hash, g->zig_target->arch);
    cache_int(&cache_hash, g->zig_target->sub_arch);
    cache_int(&cache_hash, g->zig_target->vendor);
    cache_int(&cache_hash, g->zig_target->os);
    cache_int(&cache_hash, g->zig_target->abi);
    if (g->zig_target->glibc_version != nullptr) {
        cache_int(&cache_hash, g->zig_target->glibc_version->major);
        cache_int(&cache_hash, g->zig_target->glibc_version->minor);
        cache_int(&cache_hash, g->zig_target->glibc_version->patch);
    }
    cache_bool(&cache_hash, g->have_err_ret_tracing);
    cache_bool(&cache_hash, g->libc_link_lib != nullptr);
    cache_bool(&cache_hash, g->valgrind_support);
    cache_bool(&cache_hash, g->link_eh_frame_hdr);
    cache_int(&cache_hash, detect_subsystem(g));
    if (g->llvm_cpu) cache_str(&cache_hash, g->llvm_cpu);
    if (g->llvm_features) cache_str(&cache_hash, g->llvm_features);

    Buf digest = BUF_INIT;
    buf_resize(&digest, 0);
    if ((err = cache_hit(&cache_hash, &digest))) {
        // Treat an invalid format error as a cache miss.
        if (err != ErrorInvalidFormat)
            return err;
    }

    // We should always get a cache hit because there are no
    // files in the input hash.
    assert(buf_len(&digest) != 0);

    Buf *this_dir = buf_alloc();
    os_path_join(manifest_dir, &digest, this_dir);

    if ((err = os_make_path(this_dir)))
        return err;

    const char *builtin_zig_basename = "builtin.zig";
    Buf *builtin_zig_path = buf_alloc();
    os_path_join(this_dir, buf_create_from_str(builtin_zig_basename), builtin_zig_path);

    bool hit;
    if ((err = os_file_exists(builtin_zig_path, &hit)))
        return err;
    Buf *contents;
    if (hit) {
        contents = buf_alloc();
        if ((err = os_fetch_file_path(builtin_zig_path, contents))) {
            fprintf(stderr, "Unable to open '%s': %s\n", buf_ptr(builtin_zig_path), err_str(err));
            exit(1);
        }
    } else {
        contents = codegen_generate_builtin_source(g);
        if ((err = os_write_file(builtin_zig_path, contents))) {
            fprintf(stderr, "Unable to write file '%s': %s\n", buf_ptr(builtin_zig_path), err_str(err));
            exit(1);
        }
    }

    assert(g->main_pkg);
    assert(g->std_package);
    g->compile_var_package = new_package(buf_ptr(this_dir), builtin_zig_basename, "builtin");
    if (g->is_test_build) {
        if (g->test_runner_package == nullptr) {
            g->test_runner_package = create_test_runner_pkg(g);
        }
        g->root_pkg = g->test_runner_package;
    } else {
        g->root_pkg = g->main_pkg;
    }
    g->compile_var_package->package_table.put(buf_create_from_str("std"), g->std_package);
    g->main_pkg->package_table.put(buf_create_from_str("builtin"), g->compile_var_package);
    g->main_pkg->package_table.put(buf_create_from_str("root"), g->root_pkg);
    g->std_package->package_table.put(buf_create_from_str("builtin"), g->compile_var_package);
    g->std_package->package_table.put(buf_create_from_str("std"), g->std_package);
    g->std_package->package_table.put(buf_create_from_str("root"), g->root_pkg);
    g->compile_var_import = add_source_file(g, g->compile_var_package, builtin_zig_path, contents,
            SourceKindPkgMain);

    return ErrorNone;
}

static void init(CodeGen *g) {
    if (g->module)
        return;

    g->have_dynamic_link = detect_dynamic_link(g);
    g->have_pic = detect_pic(g);
    g->have_stack_probing = detect_stack_probing(g);
    g->have_sanitize_c = detect_sanitize_c(g);
    g->is_single_threaded = detect_single_threaded(g);
    g->have_err_ret_tracing = detect_err_ret_tracing(g);

    if (target_is_single_threaded(g->zig_target)) {
        g->is_single_threaded = true;
    }

    assert(g->root_out_name);
    g->module = LLVMModuleCreateWithName(buf_ptr(g->root_out_name));

    LLVMSetTarget(g->module, buf_ptr(&g->llvm_triple_str));

    if (target_object_format(g->zig_target) == ZigLLVM_COFF) {
        ZigLLVMAddModuleCodeViewFlag(g->module);
    } else {
        ZigLLVMAddModuleDebugInfoFlag(g->module);
    }

    LLVMTargetRef target_ref;
    char *err_msg = nullptr;
    if (LLVMGetTargetFromTriple(buf_ptr(&g->llvm_triple_str), &target_ref, &err_msg)) {
        fprintf(stderr,
            "Zig is expecting LLVM to understand this target: '%s'\n"
            "However LLVM responded with: \"%s\"\n"
            "Zig is unable to continue. This is a bug in Zig:\n"
            "https://github.com/ziglang/zig/issues/438\n"
        , buf_ptr(&g->llvm_triple_str), err_msg);
        exit(1);
    }

    bool is_optimized = g->build_mode != BuildModeDebug;
    LLVMCodeGenOptLevel opt_level = is_optimized ? LLVMCodeGenLevelAggressive : LLVMCodeGenLevelNone;

    LLVMRelocMode reloc_mode;
    if (g->have_pic) {
        reloc_mode = LLVMRelocPIC;
    } else if (g->have_dynamic_link) {
        reloc_mode = LLVMRelocDynamicNoPic;
    } else {
        reloc_mode = LLVMRelocStatic;
    }

    const char *target_specific_cpu_args;
    const char *target_specific_features;
    if (g->zig_target->is_native) {
        // LLVM creates invalid binaries on Windows sometimes.
        // See https://github.com/ziglang/zig/issues/508
        // As a workaround we do not use target native features on Windows.
        if (g->zig_target->os == OsWindows || g->zig_target->os == OsUefi) {
            target_specific_cpu_args = "";
            target_specific_features = "";
        } else {
            target_specific_cpu_args = ZigLLVMGetHostCPUName();
            target_specific_features = ZigLLVMGetNativeFeatures();
        }
    } else if (target_is_riscv(g->zig_target)) {
        // TODO https://github.com/ziglang/zig/issues/2883
        // Be aware of https://github.com/ziglang/zig/issues/3275
        target_specific_cpu_args = "";
        target_specific_features = riscv_default_features;
    } else if (g->zig_target->arch == ZigLLVM_x86) {
        // This is because we're really targeting i686 rather than i386.
        // It's pretty much impossible to use many of the language features
        // such as fp16 if you stick use the x87 only. This is also what clang
        // uses as base cpu.
        // TODO https://github.com/ziglang/zig/issues/2883
        target_specific_cpu_args = "pentium4";
        target_specific_features = (g->zig_target->os == OsFreestanding) ? "-sse": "";
    } else {
        target_specific_cpu_args = "";
        target_specific_features = "";
    }

    // Override CPU and features if non-null.
    if (g->llvm_cpu != nullptr) {
        target_specific_cpu_args = g->llvm_cpu;
    }
    
    if (g->llvm_features != nullptr) {
        target_specific_features = g->llvm_features;
    }

    g->target_machine = ZigLLVMCreateTargetMachine(target_ref, buf_ptr(&g->llvm_triple_str),
            target_specific_cpu_args, target_specific_features, opt_level, reloc_mode,
            LLVMCodeModelDefault, g->function_sections);

    g->target_data_ref = LLVMCreateTargetDataLayout(g->target_machine);

    char *layout_str = LLVMCopyStringRepOfTargetData(g->target_data_ref);
    LLVMSetDataLayout(g->module, layout_str);


    assert(g->pointer_size_bytes == LLVMPointerSize(g->target_data_ref));
    g->is_big_endian = (LLVMByteOrder(g->target_data_ref) == LLVMBigEndian);

    g->builder = LLVMCreateBuilder();
    g->dbuilder = ZigLLVMCreateDIBuilder(g->module, true);

    // Don't use ZIG_VERSION_STRING here, llvm misparses it when it includes
    // the git revision.
    Buf *producer = buf_sprintf("zig %d.%d.%d", ZIG_VERSION_MAJOR, ZIG_VERSION_MINOR, ZIG_VERSION_PATCH);
    const char *flags = "";
    unsigned runtime_version = 0;

    // For macOS stack traces, we want to avoid having to parse the compilation unit debug
    // info. As long as each debug info file has a path independent of the compilation unit
    // directory (DW_AT_comp_dir), then we never have to look at the compilation unit debug
    // info. If we provide an absolute path to LLVM here for the compilation unit debug info,
    // LLVM will emit DWARF info that depends on DW_AT_comp_dir. To avoid this, we pass "."
    // for the compilation unit directory. This forces each debug file to have a directory
    // rather than be relative to DW_AT_comp_dir. According to DWARF 5, debug files will
    // no longer reference DW_AT_comp_dir, for the purpose of being able to support the
    // common practice of stripping all but the line number sections from an executable.
    const char *compile_unit_dir = target_os_is_darwin(g->zig_target->os) ? "." :
        buf_ptr(&g->main_pkg->root_src_dir);

    ZigLLVMDIFile *compile_unit_file = ZigLLVMCreateFile(g->dbuilder, buf_ptr(g->root_out_name),
            compile_unit_dir);
    g->compile_unit = ZigLLVMCreateCompileUnit(g->dbuilder, ZigLLVMLang_DW_LANG_C99(),
            compile_unit_file, buf_ptr(producer), is_optimized, flags, runtime_version,
            "", 0, !g->strip_debug_symbols);

    // This is for debug stuff that doesn't have a real file.
    g->dummy_di_file = nullptr;

    define_builtin_types(g);
    define_intern_values(g);

    IrInstruction *sentinel_instructions = allocate<IrInstruction>(2);
    g->invalid_instruction = &sentinel_instructions[0];
    g->invalid_instruction->value = allocate<ZigValue>(1, "ZigValue");
    g->invalid_instruction->value->type = g->builtin_types.entry_invalid;

    g->unreach_instruction = &sentinel_instructions[1];
    g->unreach_instruction->value = allocate<ZigValue>(1, "ZigValue");
    g->unreach_instruction->value->type = g->builtin_types.entry_unreachable;

    define_builtin_fns(g);
    Error err;
    if ((err = define_builtin_compile_vars(g))) {
        fprintf(stderr, "Unable to create builtin.zig: %s\n", err_str(err));
        exit(1);
    }
}

static void detect_dynamic_linker(CodeGen *g) {
    if (g->dynamic_linker_path != nullptr)
        return;
    if (!g->have_dynamic_link)
        return;
    if (g->out_type == OutTypeObj || (g->out_type == OutTypeLib && !g->is_dynamic))
        return;

    const char *standard_ld_path = target_dynamic_linker(g->zig_target);
    if (standard_ld_path == nullptr)
        return;

    if (g->zig_target->is_native) {
        // target_dynamic_linker is usually correct. However on some systems, such as NixOS
        // it will be incorrect. See if we can do better by looking at what zig's own
        // dynamic linker path is.
        g->dynamic_linker_path = get_self_dynamic_linker_path();
        if (g->dynamic_linker_path != nullptr)
            return;

        // If Zig is statically linked, such as via distributed binary static builds, the above
        // trick won't work. What are we left with? Try to run the system C compiler and get
        // it to tell us the dynamic linker path
#if defined(ZIG_OS_LINUX)
        {
            Error err;
            Buf *result = buf_alloc();
            for (size_t i = 0; possible_ld_names[i] != NULL; i += 1) {
                const char *lib_name = possible_ld_names[i];
                if ((err = zig_libc_cc_print_file_name(lib_name, result, false, true))) {
                    if (err != ErrorCCompilerCannotFindFile && err != ErrorNoCCompilerInstalled) {
                        fprintf(stderr, "Unable to detect native dynamic linker: %s\n", err_str(err));
                        exit(1);
                    }
                    continue;
                }
                g->dynamic_linker_path = result;
                return;
            }
        }
#endif
    }

    g->dynamic_linker_path = buf_create_from_str(standard_ld_path);
}

static void detect_libc(CodeGen *g) {
    Error err;

    if (g->libc != nullptr || g->libc_link_lib == nullptr)
        return;

    if (target_can_build_libc(g->zig_target)) {
        const char *generic_name = target_libc_generic_name(g->zig_target);
        const char *arch_name = target_arch_name(g->zig_target->arch);
        const char *abi_name = target_abi_name(g->zig_target->abi);
        if (target_is_musl(g->zig_target)) {
            // musl has some overrides. its headers are ABI-agnostic and so they all have the "musl" ABI name.
            abi_name = "musl";
            // some architectures are handled by the same set of headers
            arch_name = target_arch_musl_name(g->zig_target->arch);
        }
        Buf *arch_include_dir = buf_sprintf("%s" OS_SEP "libc" OS_SEP "include" OS_SEP "%s-%s-%s",
                buf_ptr(g->zig_lib_dir), arch_name, target_os_name(g->zig_target->os), abi_name);
        Buf *generic_include_dir = buf_sprintf("%s" OS_SEP "libc" OS_SEP "include" OS_SEP "generic-%s",
                buf_ptr(g->zig_lib_dir), generic_name);
        Buf *arch_os_include_dir = buf_sprintf("%s" OS_SEP "libc" OS_SEP "include" OS_SEP "%s-%s-any",
                buf_ptr(g->zig_lib_dir), target_arch_name(g->zig_target->arch), target_os_name(g->zig_target->os));
        Buf *generic_os_include_dir = buf_sprintf("%s" OS_SEP "libc" OS_SEP "include" OS_SEP "any-%s-any",
                buf_ptr(g->zig_lib_dir), target_os_name(g->zig_target->os));

        g->libc_include_dir_len = 4;
        g->libc_include_dir_list = allocate<Buf*>(g->libc_include_dir_len);
        g->libc_include_dir_list[0] = arch_include_dir;
        g->libc_include_dir_list[1] = generic_include_dir;
        g->libc_include_dir_list[2] = arch_os_include_dir;
        g->libc_include_dir_list[3] = generic_os_include_dir;
        return;
    }

    if (g->zig_target->is_native) {
        g->libc = allocate<ZigLibCInstallation>(1);

        // search for native_libc.txt in following dirs:
        //   - LOCAL_CACHE_DIR
        //   - GLOBAL_CACHE_DIR
        // if not found create at:
        //   - GLOBAL_CACHE_DIR
        // be mindful local/global caches may be the same dir

        Buf basename = BUF_INIT;
        buf_init_from_str(&basename, "native_libc.txt");

        Buf local_libc_txt = BUF_INIT;
        os_path_join(g->cache_dir, &basename, &local_libc_txt);

        Buf global_libc_txt = BUF_INIT;
        os_path_join(get_global_cache_dir(), &basename, &global_libc_txt);

        Buf *pathnames[3] = { nullptr };
        size_t pathnames_idx = 0;

        pathnames[pathnames_idx] = &local_libc_txt;
        pathnames_idx += 1;

        if (!buf_eql_buf(pathnames[0], &global_libc_txt)) {
            pathnames[pathnames_idx] = &global_libc_txt;
            pathnames_idx += 1;
        }

        Buf* libc_txt = nullptr;
        for (auto name : pathnames) {
            if (name == nullptr)
                break;

            bool result;
            if (os_file_exists(name, &result) != ErrorNone || !result)
                continue;

            libc_txt = name;
            break;
        }

        if (libc_txt == nullptr)
            libc_txt = &global_libc_txt;

        if ((err = zig_libc_parse(g->libc, libc_txt, g->zig_target, false))) {
            if ((err = zig_libc_find_native(g->libc, true))) {
                fprintf(stderr,
                    "Unable to link against libc: Unable to find libc installation: %s\n"
                    "See `zig libc --help` for more details.\n", err_str(err));
                exit(1);
            }
            if ((err = os_make_path(g->cache_dir))) {
                fprintf(stderr, "Unable to create %s directory: %s\n",
                    buf_ptr(g->cache_dir), err_str(err));
                exit(1);
            }
            Buf *native_libc_tmp = buf_sprintf("%s.tmp", buf_ptr(libc_txt));
            FILE *file = fopen(buf_ptr(native_libc_tmp), "wb");
            if (file == nullptr) {
                fprintf(stderr, "Unable to open %s: %s\n", buf_ptr(native_libc_tmp), strerror(errno));
                exit(1);
            }
            zig_libc_render(g->libc, file);
            if (fclose(file) != 0) {
                fprintf(stderr, "Unable to save %s: %s\n", buf_ptr(native_libc_tmp), strerror(errno));
                exit(1);
            }
            if ((err = os_rename(native_libc_tmp, libc_txt))) {
                fprintf(stderr, "Unable to create %s: %s\n", buf_ptr(libc_txt), err_str(err));
                exit(1);
            }
        }
        bool want_sys_dir = !buf_eql_buf(&g->libc->include_dir, &g->libc->sys_include_dir);
        size_t want_um_and_shared_dirs = (g->zig_target->os == OsWindows) ? 2 : 0;
        size_t dir_count = 1 + want_sys_dir + want_um_and_shared_dirs;
        g->libc_include_dir_len = 0;
        g->libc_include_dir_list = allocate<Buf*>(dir_count);

        g->libc_include_dir_list[g->libc_include_dir_len] = &g->libc->include_dir;
        g->libc_include_dir_len += 1;

        if (want_sys_dir) {
            g->libc_include_dir_list[g->libc_include_dir_len] = &g->libc->sys_include_dir;
            g->libc_include_dir_len += 1;
        }

        if (want_um_and_shared_dirs != 0) {
            g->libc_include_dir_list[g->libc_include_dir_len] = buf_sprintf("%s" OS_SEP ".." OS_SEP "um",
                    buf_ptr(&g->libc->include_dir));
            g->libc_include_dir_len += 1;

            g->libc_include_dir_list[g->libc_include_dir_len] = buf_sprintf("%s" OS_SEP ".." OS_SEP "shared",
                    buf_ptr(&g->libc->include_dir));
            g->libc_include_dir_len += 1;
        }
        assert(g->libc_include_dir_len == dir_count);

        buf_deinit(&global_libc_txt);
        buf_deinit(&local_libc_txt);
        buf_deinit(&basename);
    } else if ((g->out_type == OutTypeExe || (g->out_type == OutTypeLib && g->is_dynamic)) &&
        !target_os_is_darwin(g->zig_target->os))
    {
        Buf triple_buf = BUF_INIT;
        target_triple_zig(&triple_buf, g->zig_target);
        fprintf(stderr,
            "Zig is unable to provide a libc for the chosen target '%s'.\n"
            "The target is non-native, so Zig also cannot use the native libc installation.\n"
            "Choose a target which has a libc available (see `zig targets`), or\n"
            "provide a libc installation text file (see `zig libc --help`).\n", buf_ptr(&triple_buf));
        exit(1);
    }
}

// does not add the "cc" arg
void add_cc_args(CodeGen *g, ZigList<const char *> &args, const char *out_dep_path, bool translate_c) {
    if (translate_c) {
        args.append("-x");
        args.append("c");
    }

    if (out_dep_path != nullptr) {
        args.append("-MD");
        args.append("-MV");
        args.append("-MF");
        args.append(out_dep_path);
    }

    args.append("-nostdinc");
    args.append("-fno-spell-checking");

    if (g->function_sections) {
        args.append("-ffunction-sections");
    }

    if (translate_c) {
        // this gives us access to preprocessing entities, presumably at
        // the cost of performance
        args.append("-Xclang");
        args.append("-detailed-preprocessing-record");
    } else {
        switch (g->err_color) {
            case ErrColorAuto:
                break;
            case ErrColorOff:
                args.append("-fno-color-diagnostics");
                args.append("-fno-caret-diagnostics");
                break;
            case ErrColorOn:
                args.append("-fcolor-diagnostics");
                args.append("-fcaret-diagnostics");
                break;
        }
    }

    for (size_t i = 0; i < g->framework_dirs.length; i += 1) {
        args.append("-iframework");
        args.append(g->framework_dirs.at(i));
    }

    // According to Rich Felker libc headers are supposed to go before C language headers.
    // However as noted by @dimenus, appending libc headers before c_headers breaks intrinsics
    // and other compiler specific items.
    args.append("-isystem");
    args.append(buf_ptr(g->zig_c_headers_dir));

    for (size_t i = 0; i < g->libc_include_dir_len; i += 1) {
        Buf *include_dir = g->libc_include_dir_list[i];
        args.append("-isystem");
        args.append(buf_ptr(include_dir));
    }

    if (g->zig_target->is_native) {
        if (target_supports_clang_march_native(g->zig_target)) {
            args.append("-march=native");
        }
    } else {
        args.append("-target");
        args.append(buf_ptr(&g->llvm_triple_str));

        if (target_is_musl(g->zig_target) && target_is_riscv(g->zig_target)) {
            // Musl depends on atomic instructions, which are disabled by default in Clang/LLVM's
            // cross compilation CPU info for RISCV.
            // TODO: https://github.com/ziglang/zig/issues/2883
            args.append("-Xclang");
            args.append("-target-feature");
            args.append("-Xclang");
            args.append(riscv_default_features);
        } else if (g->zig_target->os == OsFreestanding && g->zig_target->arch == ZigLLVM_x86) {
            args.append("-Xclang");
            args.append("-target-feature");
            args.append("-Xclang");
            args.append("-sse");
        }
    }
    if (g->zig_target->os == OsFreestanding) {
        args.append("-ffreestanding");
    }

    // windows.h has files such as pshpack1.h which do #pragma packing, triggering a clang warning.
    // So for this target, we disable this warning.
    if (g->zig_target->os == OsWindows && target_abi_is_gnu(g->zig_target->abi)) {
        args.append("-Wno-pragma-pack");
    }

    if (!g->strip_debug_symbols) {
        args.append("-g");
    }

    if (codegen_have_frame_pointer(g)) {
        args.append("-fno-omit-frame-pointer");
    } else {
        args.append("-fomit-frame-pointer");
    }

    if (g->have_sanitize_c) {
        args.append("-fsanitize=undefined");
        args.append("-fsanitize-trap=undefined");
    }

    switch (g->build_mode) {
        case BuildModeDebug:
            // windows c runtime requires -D_DEBUG if using debug libraries
            args.append("-D_DEBUG");

            if (g->libc_link_lib != nullptr) {
                args.append("-fstack-protector-strong");
                args.append("--param");
                args.append("ssp-buffer-size=4");
            } else {
                args.append("-fno-stack-protector");
            }
            break;
        case BuildModeSafeRelease:
            // See the comment in the BuildModeFastRelease case for why we pass -O2 rather
            // than -O3 here.
            args.append("-O2");
            if (g->libc_link_lib != nullptr) {
                args.append("-D_FORTIFY_SOURCE=2");
                args.append("-fstack-protector-strong");
                args.append("--param");
                args.append("ssp-buffer-size=4");
            } else {
                args.append("-fno-stack-protector");
            }
            break;
        case BuildModeFastRelease:
            args.append("-DNDEBUG");
            // Here we pass -O2 rather than -O3 because, although we do the equivalent of
            // -O3 in Zig code, the justification for the difference here is that Zig
            // has better detection and prevention of undefined behavior, so -O3 is safer for
            // Zig code than it is for C code. Also, C programmers are used to their code
            // running in -O2 and thus the -O3 path has been tested less.
            args.append("-O2");
            args.append("-fno-stack-protector");
            break;
        case BuildModeSmallRelease:
            args.append("-DNDEBUG");
            args.append("-Os");
            args.append("-fno-stack-protector");
            break;
    }

    if (target_supports_fpic(g->zig_target) && g->have_pic) {
        args.append("-fPIC");
    }

    for (size_t arg_i = 0; arg_i < g->clang_argv_len; arg_i += 1) {
        args.append(g->clang_argv[arg_i]);
    }

}

void codegen_translate_c(CodeGen *g, Buf *full_path) {
    Error err;

    Buf *src_basename = buf_alloc();
    Buf *src_dirname = buf_alloc();
    os_path_split(full_path, src_dirname, src_basename);

    Buf noextname = BUF_INIT;
    os_path_extname(src_basename, &noextname, nullptr);

    Buf *zig_basename = buf_sprintf("%s.zig", buf_ptr(&noextname));

    detect_libc(g);

    Buf cache_digest = BUF_INIT;
    buf_resize(&cache_digest, 0);

    CacheHash *cache_hash = nullptr;
    if (g->enable_cache) {
        if ((err = create_c_object_cache(g, &cache_hash, true))) {
            // Already printed error; verbose = true
            exit(1);
        }
        cache_file(cache_hash, full_path);
        // to distinguish from generating a C object
        cache_buf(cache_hash, buf_create_from_str("translate-c"));

        if ((err = cache_hit(cache_hash, &cache_digest))) {
            if (err != ErrorInvalidFormat) {
                fprintf(stderr, "unable to check cache: %s\n", err_str(err));
                exit(1);
            }
        }
        if (cache_hash->manifest_file_path != nullptr) {
            g->caches_to_release.append(cache_hash);
        }
    }

    if (g->enable_cache && buf_len(&cache_digest) != 0) {
        // cache hit
        Buf *cached_path = buf_sprintf("%s" OS_SEP CACHE_OUT_SUBDIR OS_SEP "%s" OS_SEP "%s",
                buf_ptr(g->cache_dir), buf_ptr(&cache_digest), buf_ptr(zig_basename));
        fprintf(stdout, "%s\n", buf_ptr(cached_path));
        return;
    }

    // cache miss or cache disabled
    init(g);

    Buf *out_dep_path = nullptr;
    const char *out_dep_path_cstr = nullptr;

    if (g->enable_cache) {
        buf_alloc();// we can't know the digest until we do the C compiler invocation, so we
        // need a tmp filename.
        out_dep_path = buf_alloc();
        if ((err = get_tmp_filename(g, out_dep_path, buf_sprintf("%s.d", buf_ptr(zig_basename))))) {
            fprintf(stderr, "unable to create tmp dir: %s\n", err_str(err));
            exit(1);
        }
        out_dep_path_cstr = buf_ptr(out_dep_path);
    }

    ZigList<const char *> clang_argv = {0};
    add_cc_args(g, clang_argv, out_dep_path_cstr, true);

    clang_argv.append(buf_ptr(full_path));

    if (g->verbose_cc) {
        fprintf(stderr, "clang");
        for (size_t i = 0; i < clang_argv.length; i += 1) {
            fprintf(stderr, " %s", clang_argv.at(i));
        }
        fprintf(stderr, "\n");
    }

    clang_argv.append(nullptr); // to make the [start...end] argument work

    const char *resources_path = buf_ptr(g->zig_c_headers_dir);
    Stage2ErrorMsg *errors_ptr;
    size_t errors_len;
    Stage2Ast *ast;

    err = stage2_translate_c(&ast, &errors_ptr, &errors_len,
                    &clang_argv.at(0), &clang_argv.last(), resources_path);

    if (err == ErrorCCompileErrors && errors_len > 0) {
        for (size_t i = 0; i < errors_len; i += 1) {
            Stage2ErrorMsg *clang_err = &errors_ptr[i];

            ErrorMsg *err_msg = err_msg_create_with_offset(
                clang_err->filename_ptr ?
                buf_create_from_mem(clang_err->filename_ptr, clang_err->filename_len) : nullptr,
                clang_err->line, clang_err->column, clang_err->offset, clang_err->source,
                buf_create_from_mem(clang_err->msg_ptr, clang_err->msg_len));
            print_err_msg(err_msg, g->err_color);
        }
        exit(1);
    }

    if (err) {
        fprintf(stderr, "unable to parse C file: %s\n", err_str(err));
        exit(1);
    }

    if (!g->enable_cache) {
        stage2_render_ast(ast, stdout);
        return;
    }

    // add the files depended on to the cache system
    if ((err = cache_add_dep_file(cache_hash, out_dep_path, true))) {
        // Don't treat the absence of the .d file as a fatal error, the
        // compiler may not produce one eg. when compiling .s files
        if (err != ErrorFileNotFound) {
            fprintf(stderr, "Failed to add C source dependencies to cache: %s\n", err_str(err));
            exit(1);
        }
    }
    if (err != ErrorFileNotFound) {
        os_delete_file(out_dep_path);
    }

    if ((err = cache_final(cache_hash, &cache_digest))) {
        fprintf(stderr, "Unable to finalize cache hash: %s\n", err_str(err));
        exit(1);
    }

    Buf *artifact_dir = buf_sprintf("%s" OS_SEP CACHE_OUT_SUBDIR OS_SEP "%s",
            buf_ptr(g->cache_dir), buf_ptr(&cache_digest));

    if ((err = os_make_path(artifact_dir))) {
        fprintf(stderr, "Unable to make dir: %s\n", err_str(err));
        exit(1);
    }

    Buf *cached_path = buf_sprintf("%s" OS_SEP "%s", buf_ptr(artifact_dir), buf_ptr(zig_basename));

    FILE *out_file = fopen(buf_ptr(cached_path), "wb");
    if (out_file == nullptr) {
        fprintf(stderr, "Unable to open output file: %s\n", strerror(errno));
        exit(1);
    }
    stage2_render_ast(ast, out_file);
    if (fclose(out_file) != 0) {
        fprintf(stderr, "Unable to write to output file: %s\n", strerror(errno));
        exit(1);
    }
    fprintf(stdout, "%s\n", buf_ptr(cached_path));
}

static void update_test_functions_builtin_decl(CodeGen *g) {
    Error err;

    assert(g->is_test_build);

    if (g->test_fns.length == 0) {
        fprintf(stderr, "No tests to run.\n");
        exit(0);
    }

    ZigType *fn_type = get_test_fn_type(g);

    ZigValue *test_fn_type_val = get_builtin_value(g, "TestFn");
    assert(test_fn_type_val->type->id == ZigTypeIdMetaType);
    ZigType *struct_type = test_fn_type_val->data.x_type;
    if ((err = type_resolve(g, struct_type, ResolveStatusSizeKnown)))
        zig_unreachable();

    ZigValue *test_fn_array = create_const_vals(1);
    test_fn_array->type = get_array_type(g, struct_type, g->test_fns.length, nullptr);
    test_fn_array->special = ConstValSpecialStatic;
    test_fn_array->data.x_array.data.s_none.elements = create_const_vals(g->test_fns.length);

    for (size_t i = 0; i < g->test_fns.length; i += 1) {
        ZigFn *test_fn_entry = g->test_fns.at(i);

        if (fn_is_async(test_fn_entry)) {
            ErrorMsg *msg = add_node_error(g, test_fn_entry->proto_node,
                buf_create_from_str("test functions cannot be async"));
            add_error_note(g, msg, test_fn_entry->proto_node,
                buf_sprintf("this restriction may be lifted in the future. See https://github.com/ziglang/zig/issues/3117 for more details"));
            add_async_error_notes(g, msg, test_fn_entry);
            continue;
        }

        ZigValue *this_val = &test_fn_array->data.x_array.data.s_none.elements[i];
        this_val->special = ConstValSpecialStatic;
        this_val->type = struct_type;
        this_val->parent.id = ConstParentIdArray;
        this_val->parent.data.p_array.array_val = test_fn_array;
        this_val->parent.data.p_array.elem_index = i;
        this_val->data.x_struct.fields = alloc_const_vals_ptrs(2);

        ZigValue *name_field = this_val->data.x_struct.fields[0];
        ZigValue *name_array_val = create_const_str_lit(g, &test_fn_entry->symbol_name)->data.x_ptr.data.ref.pointee;
        init_const_slice(g, name_field, name_array_val, 0, buf_len(&test_fn_entry->symbol_name), true);

        ZigValue *fn_field = this_val->data.x_struct.fields[1];
        fn_field->type = fn_type;
        fn_field->special = ConstValSpecialStatic;
        fn_field->data.x_ptr.special = ConstPtrSpecialFunction;
        fn_field->data.x_ptr.mut = ConstPtrMutComptimeConst;
        fn_field->data.x_ptr.data.fn.fn_entry = test_fn_entry;
    }
    report_errors_and_maybe_exit(g);

    ZigValue *test_fn_slice = create_const_slice(g, test_fn_array, 0, g->test_fns.length, true);

    update_compile_var(g, buf_create_from_str("test_functions"), test_fn_slice);
    assert(g->test_runner_package != nullptr);
}

static Buf *get_resolved_root_src_path(CodeGen *g) {
    // TODO memoize
    if (buf_len(&g->main_pkg->root_src_path) == 0)
        return nullptr;

    Buf rel_full_path = BUF_INIT;
    os_path_join(&g->main_pkg->root_src_dir, &g->main_pkg->root_src_path, &rel_full_path);

    Buf *resolved_path = buf_alloc();
    Buf *resolve_paths[] = {&rel_full_path};
    *resolved_path = os_path_resolve(resolve_paths, 1);

    return resolved_path;
}

static void gen_root_source(CodeGen *g) {
    Buf *resolved_path = get_resolved_root_src_path(g);
    if (resolved_path == nullptr)
        return;

    Buf *source_code = buf_alloc();
    Error err;
    // No need for using the caching system for this file fetch because it is handled
    // separately.
    if ((err = os_fetch_file_path(resolved_path, source_code))) {
        fprintf(stderr, "unable to open '%s': %s\n", buf_ptr(resolved_path), err_str(err));
        exit(1);
    }

    ZigType *root_import_alias = add_source_file(g, g->main_pkg, resolved_path, source_code, SourceKindRoot);
    assert(root_import_alias == g->root_import);

    assert(g->root_out_name);
    assert(g->out_type != OutTypeUnknown);

    if (!g->is_dummy_so) {
        // Zig has lazy top level definitions. Here we semantically analyze the panic function.
        Buf *import_target_path;
        Buf full_path = BUF_INIT;
        ZigType *std_import;
        if ((err = analyze_import(g, g->root_import, buf_create_from_str("std"), &std_import,
            &import_target_path, &full_path)))
        {
            if (err == ErrorFileNotFound) {
                fprintf(stderr, "unable to find '%s'", buf_ptr(import_target_path));
            } else {
                fprintf(stderr, "unable to open '%s': %s\n", buf_ptr(&full_path), err_str(err));
            }
            exit(1);
        }

        Tld *builtin_tld = find_decl(g, &get_container_scope(std_import)->base,
                buf_create_from_str("builtin"));
        assert(builtin_tld != nullptr);
        resolve_top_level_decl(g, builtin_tld, nullptr, false);
        report_errors_and_maybe_exit(g);
        assert(builtin_tld->id == TldIdVar);
        TldVar *builtin_tld_var = (TldVar*)builtin_tld;
        ZigValue *builtin_val = builtin_tld_var->var->const_value;
        assert(builtin_val->type->id == ZigTypeIdMetaType);
        ZigType *builtin_type = builtin_val->data.x_type;

        Tld *panic_tld = find_decl(g, &get_container_scope(builtin_type)->base,
                buf_create_from_str("panic"));
        assert(panic_tld != nullptr);
        resolve_top_level_decl(g, panic_tld, nullptr, false);
        report_errors_and_maybe_exit(g);
        assert(panic_tld->id == TldIdVar);
        TldVar *panic_tld_var = (TldVar*)panic_tld;
        ZigValue *panic_fn_val = panic_tld_var->var->const_value;
        assert(panic_fn_val->type->id == ZigTypeIdFn);
        assert(panic_fn_val->data.x_ptr.special == ConstPtrSpecialFunction);
        g->panic_fn = panic_fn_val->data.x_ptr.data.fn.fn_entry;
        assert(g->panic_fn != nullptr);
    }

    if (!g->error_during_imports) {
        semantic_analyze(g);
    }
    report_errors_and_maybe_exit(g);

    if (g->is_test_build) {
        update_test_functions_builtin_decl(g);
        if (!g->error_during_imports) {
            semantic_analyze(g);
        }
    }

    report_errors_and_maybe_exit(g);

}

static void print_zig_cc_cmd(ZigList<const char *> *args) {
    for (size_t arg_i = 0; arg_i < args->length; arg_i += 1) {
        const char *space_str = (arg_i == 0) ? "" : " ";
        fprintf(stderr, "%s%s", space_str, args->at(arg_i));
    }
    fprintf(stderr, "\n");
}

// Caller should delete the file when done or rename it into a better location.
static Error get_tmp_filename(CodeGen *g, Buf *out, Buf *suffix) {
    Error err;
    buf_resize(out, 0);
    os_path_join(g->cache_dir, buf_create_from_str("tmp" OS_SEP), out);
    if ((err = os_make_path(out))) {
        return err;
    }
    const char base64[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
    assert(array_length(base64) == 64 + 1);
    for (size_t i = 0; i < 12; i += 1) {
        buf_append_char(out, base64[rand() % 64]);
    }
    buf_append_char(out, '-');
    buf_append_buf(out, suffix);
    return ErrorNone;
}

Error create_c_object_cache(CodeGen *g, CacheHash **out_cache_hash, bool verbose) {
    Error err;
    CacheHash *cache_hash = allocate<CacheHash>(1);
    Buf *manifest_dir = buf_sprintf("%s" OS_SEP CACHE_HASH_SUBDIR, buf_ptr(g->cache_dir));
    cache_init(cache_hash, manifest_dir);

    Buf *compiler_id;
    if ((err = get_compiler_id(&compiler_id))) {
        if (verbose) {
            fprintf(stderr, "unable to get compiler id: %s\n", err_str(err));
        }
        return err;
    }
    cache_buf(cache_hash, compiler_id);
    cache_int(cache_hash, g->err_color);
    cache_buf(cache_hash, g->zig_c_headers_dir);
    cache_list_of_buf(cache_hash, g->libc_include_dir_list, g->libc_include_dir_len);
    cache_int(cache_hash, g->zig_target->is_native);
    cache_int(cache_hash, g->zig_target->arch);
    cache_int(cache_hash, g->zig_target->sub_arch);
    cache_int(cache_hash, g->zig_target->vendor);
    cache_int(cache_hash, g->zig_target->os);
    cache_int(cache_hash, g->zig_target->abi);
    cache_bool(cache_hash, g->strip_debug_symbols);
    cache_int(cache_hash, g->build_mode);
    cache_bool(cache_hash, g->have_pic);
    cache_bool(cache_hash, g->have_sanitize_c);
    cache_bool(cache_hash, want_valgrind_support(g));
    cache_bool(cache_hash, g->function_sections);
    for (size_t arg_i = 0; arg_i < g->clang_argv_len; arg_i += 1) {
        cache_str(cache_hash, g->clang_argv[arg_i]);
    }

    *out_cache_hash = cache_hash;
    return ErrorNone;
}

// returns true if it was a cache miss
static void gen_c_object(CodeGen *g, Buf *self_exe_path, CFile *c_file) {
    Error err;

    Buf *artifact_dir;
    Buf *o_final_path;

    Buf *o_dir = buf_sprintf("%s" OS_SEP CACHE_OUT_SUBDIR, buf_ptr(g->cache_dir));

    Buf *c_source_file = buf_create_from_str(c_file->source_path);
    Buf *c_source_basename = buf_alloc();
    os_path_split(c_source_file, nullptr, c_source_basename);

    Stage2ProgressNode *child_prog_node = stage2_progress_start(g->sub_progress_node, buf_ptr(c_source_basename),
            buf_len(c_source_basename), 0);

    Buf *final_o_basename = buf_alloc();
    os_path_extname(c_source_basename, final_o_basename, nullptr);
    buf_append_str(final_o_basename, target_o_file_ext(g->zig_target));

    CacheHash *cache_hash;
    if ((err = create_c_object_cache(g, &cache_hash, true))) {
        // Already printed error; verbose = true
        exit(1);
    }
    cache_file(cache_hash, c_source_file);

    // Note: not directory args, just args that always have a file next
    static const char *file_args[] = {
        "-include",
    };
    for (size_t arg_i = 0; arg_i < c_file->args.length; arg_i += 1) {
        const char *arg = c_file->args.at(arg_i);
        cache_str(cache_hash, arg);
        for (size_t file_arg_i = 0; file_arg_i < array_length(file_args); file_arg_i += 1) {
            if (strcmp(arg, file_args[file_arg_i]) == 0 && arg_i + 1 < c_file->args.length) {
                arg_i += 1;
                cache_file(cache_hash, buf_create_from_str(c_file->args.at(arg_i)));
            }
        }
    }

    Buf digest = BUF_INIT;
    buf_resize(&digest, 0);
    if ((err = cache_hit(cache_hash, &digest))) {
        if (err != ErrorInvalidFormat) {
            if (err == ErrorCacheUnavailable) {
                // already printed error
            } else {
                fprintf(stderr, "unable to check cache when compiling C object: %s\n", err_str(err));
            }
            exit(1);
        }
    }
    bool is_cache_miss = (buf_len(&digest) == 0);
    if (is_cache_miss) {
        // we can't know the digest until we do the C compiler invocation, so we
        // need a tmp filename.
        Buf *out_obj_path = buf_alloc();
        if ((err = get_tmp_filename(g, out_obj_path, final_o_basename))) {
            fprintf(stderr, "unable to create tmp dir: %s\n", err_str(err));
            exit(1);
        }

        Termination term;
        ZigList<const char *> args = {};
        args.append(buf_ptr(self_exe_path));
        args.append("cc");

        Buf *out_dep_path = buf_sprintf("%s.d", buf_ptr(out_obj_path));
        add_cc_args(g, args, buf_ptr(out_dep_path), false);

        args.append("-o");
        args.append(buf_ptr(out_obj_path));

        args.append("-c");
        args.append(buf_ptr(c_source_file));

        for (size_t arg_i = 0; arg_i < c_file->args.length; arg_i += 1) {
            args.append(c_file->args.at(arg_i));
        }

        if (g->verbose_cc) {
            print_zig_cc_cmd(&args);
        }
        os_spawn_process(args, &term);
        if (term.how != TerminationIdClean || term.code != 0) {
            fprintf(stderr, "\nThe following command failed:\n");
            print_zig_cc_cmd(&args);
            exit(1);
        }

        // add the files depended on to the cache system
        if ((err = cache_add_dep_file(cache_hash, out_dep_path, true))) {
            // Don't treat the absence of the .d file as a fatal error, the
            // compiler may not produce one eg. when compiling .s files
            if (err != ErrorFileNotFound) {
                fprintf(stderr, "Failed to add C source dependencies to cache: %s\n", err_str(err));
                exit(1);
            }
        }
        if (err != ErrorFileNotFound) {
            os_delete_file(out_dep_path);
        }

        if ((err = cache_final(cache_hash, &digest))) {
            fprintf(stderr, "Unable to finalize cache hash: %s\n", err_str(err));
            exit(1);
        }
        artifact_dir = buf_alloc();
        os_path_join(o_dir, &digest, artifact_dir);
        if ((err = os_make_path(artifact_dir))) {
            fprintf(stderr, "Unable to create output directory '%s': %s",
                    buf_ptr(artifact_dir), err_str(err));
            exit(1);
        }
        o_final_path = buf_alloc();
        os_path_join(artifact_dir, final_o_basename, o_final_path);
        if ((err = os_rename(out_obj_path, o_final_path))) {
            fprintf(stderr, "Unable to rename object: %s\n", err_str(err));
            exit(1);
        }
    } else {
        // cache hit
        artifact_dir = buf_alloc();
        os_path_join(o_dir, &digest, artifact_dir);
        o_final_path = buf_alloc();
        os_path_join(artifact_dir, final_o_basename, o_final_path);
    }

    g->link_objects.append(o_final_path);
    g->caches_to_release.append(cache_hash);

    stage2_progress_end(child_prog_node);
}

// returns true if we had any cache misses
static void gen_c_objects(CodeGen *g) {
    Error err;

    if (g->c_source_files.length == 0)
        return;

    Buf *self_exe_path = buf_alloc();
    if ((err = os_self_exe_path(self_exe_path))) {
        fprintf(stderr, "Unable to get self exe path: %s\n", err_str(err));
        exit(1);
    }

    codegen_add_time_event(g, "Compile C Objects");
    const char *c_prog_name = "Compile C Objects";
    codegen_switch_sub_prog_node(g, stage2_progress_start(g->main_progress_node, c_prog_name, strlen(c_prog_name),
            g->c_source_files.length));

    for (size_t c_file_i = 0; c_file_i < g->c_source_files.length; c_file_i += 1) {
        CFile *c_file = g->c_source_files.at(c_file_i);
        gen_c_object(g, self_exe_path, c_file);
    }
}

void codegen_add_object(CodeGen *g, Buf *object_path) {
    g->link_objects.append(object_path);
}

// Must be coordinated with with CIntType enum
static const char *c_int_type_names[] = {
    "short",
    "unsigned short",
    "int",
    "unsigned int",
    "long",
    "unsigned long",
    "long long",
    "unsigned long long",
};

struct GenH {
    ZigList<ZigType *> types_to_declare;
};

static void prepend_c_type_to_decl_list(CodeGen *g, GenH *gen_h, ZigType *type_entry) {
    if (type_entry->gen_h_loop_flag)
        return;
    type_entry->gen_h_loop_flag = true;

    switch (type_entry->id) {
        case ZigTypeIdInvalid:
        case ZigTypeIdMetaType:
        case ZigTypeIdComptimeFloat:
        case ZigTypeIdComptimeInt:
        case ZigTypeIdEnumLiteral:
        case ZigTypeIdUndefined:
        case ZigTypeIdNull:
        case ZigTypeIdBoundFn:
        case ZigTypeIdErrorUnion:
        case ZigTypeIdErrorSet:
        case ZigTypeIdFnFrame:
        case ZigTypeIdAnyFrame:
            zig_unreachable();
        case ZigTypeIdVoid:
        case ZigTypeIdUnreachable:
        case ZigTypeIdBool:
            g->c_want_stdbool = true;
            return;
        case ZigTypeIdInt:
            g->c_want_stdint = true;
            return;
        case ZigTypeIdFloat:
            return;
        case ZigTypeIdOpaque:
            gen_h->types_to_declare.append(type_entry);
            return;
        case ZigTypeIdStruct:
            for (uint32_t i = 0; i < type_entry->data.structure.src_field_count; i += 1) {
                TypeStructField *field = type_entry->data.structure.fields[i];
                prepend_c_type_to_decl_list(g, gen_h, field->type_entry);
            }
            gen_h->types_to_declare.append(type_entry);
            return;
        case ZigTypeIdUnion:
            for (uint32_t i = 0; i < type_entry->data.unionation.src_field_count; i += 1) {
                TypeUnionField *field = &type_entry->data.unionation.fields[i];
                prepend_c_type_to_decl_list(g, gen_h, field->type_entry);
            }
            gen_h->types_to_declare.append(type_entry);
            return;
        case ZigTypeIdEnum:
            prepend_c_type_to_decl_list(g, gen_h, type_entry->data.enumeration.tag_int_type);
            gen_h->types_to_declare.append(type_entry);
            return;
        case ZigTypeIdPointer:
            prepend_c_type_to_decl_list(g, gen_h, type_entry->data.pointer.child_type);
            return;
        case ZigTypeIdArray:
            prepend_c_type_to_decl_list(g, gen_h, type_entry->data.array.child_type);
            return;
        case ZigTypeIdVector:
            prepend_c_type_to_decl_list(g, gen_h, type_entry->data.vector.elem_type);
            return;
        case ZigTypeIdOptional:
            prepend_c_type_to_decl_list(g, gen_h, type_entry->data.maybe.child_type);
            return;
        case ZigTypeIdFn:
            for (size_t i = 0; i < type_entry->data.fn.fn_type_id.param_count; i += 1) {
                prepend_c_type_to_decl_list(g, gen_h, type_entry->data.fn.fn_type_id.param_info[i].type);
            }
            prepend_c_type_to_decl_list(g, gen_h, type_entry->data.fn.fn_type_id.return_type);
            return;
    }
}

static void get_c_type(CodeGen *g, GenH *gen_h, ZigType *type_entry, Buf *out_buf) {
    assert(type_entry);

    for (size_t i = 0; i < array_length(c_int_type_names); i += 1) {
        if (type_entry == g->builtin_types.entry_c_int[i]) {
            buf_init_from_str(out_buf, c_int_type_names[i]);
            return;
        }
    }
    if (type_entry == g->builtin_types.entry_c_longdouble) {
        buf_init_from_str(out_buf, "long double");
        return;
    }
    if (type_entry == g->builtin_types.entry_c_void) {
        buf_init_from_str(out_buf, "void");
        return;
    }
    if (type_entry == g->builtin_types.entry_isize) {
        g->c_want_stdint = true;
        buf_init_from_str(out_buf, "intptr_t");
        return;
    }
    if (type_entry == g->builtin_types.entry_usize) {
        g->c_want_stdint = true;
        buf_init_from_str(out_buf, "uintptr_t");
        return;
    }

    prepend_c_type_to_decl_list(g, gen_h, type_entry);

    switch (type_entry->id) {
        case ZigTypeIdVoid:
            buf_init_from_str(out_buf, "void");
            break;
        case ZigTypeIdBool:
            buf_init_from_str(out_buf, "bool");
            break;
        case ZigTypeIdUnreachable:
            buf_init_from_str(out_buf, "__attribute__((__noreturn__)) void");
            break;
        case ZigTypeIdFloat:
            switch (type_entry->data.floating.bit_count) {
                case 32:
                    buf_init_from_str(out_buf, "float");
                    break;
                case 64:
                    buf_init_from_str(out_buf, "double");
                    break;
                case 80:
                    buf_init_from_str(out_buf, "__float80");
                    break;
                case 128:
                    buf_init_from_str(out_buf, "__float128");
                    break;
                default:
                    zig_unreachable();
            }
            break;
        case ZigTypeIdInt:
            buf_resize(out_buf, 0);
            buf_appendf(out_buf, "%sint%" PRIu32 "_t",
                    type_entry->data.integral.is_signed ? "" : "u",
                    type_entry->data.integral.bit_count);
            break;
        case ZigTypeIdPointer:
            {
                Buf child_buf = BUF_INIT;
                ZigType *child_type = type_entry->data.pointer.child_type;
                get_c_type(g, gen_h, child_type, &child_buf);

                const char *const_str = type_entry->data.pointer.is_const ? "const " : "";
                buf_resize(out_buf, 0);
                buf_appendf(out_buf, "%s%s *", const_str, buf_ptr(&child_buf));
                break;
            }
        case ZigTypeIdOptional:
            {
                ZigType *child_type = type_entry->data.maybe.child_type;
                if (!type_has_bits(child_type)) {
                    buf_init_from_str(out_buf, "bool");
                    return;
                } else if (type_is_nonnull_ptr(child_type)) {
                    return get_c_type(g, gen_h, child_type, out_buf);
                } else {
                    zig_unreachable();
                }
            }
        case ZigTypeIdStruct:
        case ZigTypeIdOpaque:
            {
                buf_init_from_str(out_buf, "struct ");
                buf_append_buf(out_buf, type_h_name(type_entry));
                return;
            }
        case ZigTypeIdUnion:
            {
                buf_init_from_str(out_buf, "union ");
                buf_append_buf(out_buf, type_h_name(type_entry));
                return;
            }
        case ZigTypeIdEnum:
            {
                buf_init_from_str(out_buf, "enum ");
                buf_append_buf(out_buf, type_h_name(type_entry));
                return;
            }
        case ZigTypeIdArray:
            {
                ZigTypeArray *array_data = &type_entry->data.array;

                Buf *child_buf = buf_alloc();
                get_c_type(g, gen_h, array_data->child_type, child_buf);

                buf_resize(out_buf, 0);
                buf_appendf(out_buf, "%s", buf_ptr(child_buf));
                return;
            }
        case ZigTypeIdVector:
            zig_panic("TODO implement get_c_type for vector types");
        case ZigTypeIdErrorUnion:
        case ZigTypeIdErrorSet:
        case ZigTypeIdFn:
            zig_panic("TODO implement get_c_type for more types");
        case ZigTypeIdInvalid:
        case ZigTypeIdMetaType:
        case ZigTypeIdBoundFn:
        case ZigTypeIdComptimeFloat:
        case ZigTypeIdComptimeInt:
        case ZigTypeIdEnumLiteral:
        case ZigTypeIdUndefined:
        case ZigTypeIdNull:
        case ZigTypeIdFnFrame:
        case ZigTypeIdAnyFrame:
            zig_unreachable();
    }
}

static const char *preprocessor_alphabet1 = "_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
static const char *preprocessor_alphabet2 = "_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";

static bool need_to_preprocessor_mangle(Buf *src) {
    for (size_t i = 0; i < buf_len(src); i += 1) {
        const char *alphabet = (i == 0) ? preprocessor_alphabet1 : preprocessor_alphabet2;
        uint8_t byte = buf_ptr(src)[i];
        if (strchr(alphabet, byte) == nullptr) {
            return true;
        }
    }
    return false;
}

static Buf *preprocessor_mangle(Buf *src) {
    if (!need_to_preprocessor_mangle(src)) {
        return buf_create_from_buf(src);
    }
    Buf *result = buf_alloc();
    for (size_t i = 0; i < buf_len(src); i += 1) {
        const char *alphabet = (i == 0) ? preprocessor_alphabet1 : preprocessor_alphabet2;
        uint8_t byte = buf_ptr(src)[i];
        if (strchr(alphabet, byte) == nullptr) {
            // perform escape
            buf_appendf(result, "_%02x_", byte);
        } else {
            buf_append_char(result, byte);
        }
    }
    return result;
}

static void gen_h_file_types(CodeGen* g, GenH* gen_h, Buf* out_buf) {
    for (size_t type_i = 0; type_i < gen_h->types_to_declare.length; type_i += 1) {
        ZigType *type_entry = gen_h->types_to_declare.at(type_i);
        switch (type_entry->id) {
            case ZigTypeIdInvalid:
            case ZigTypeIdMetaType:
            case ZigTypeIdVoid:
            case ZigTypeIdBool:
            case ZigTypeIdUnreachable:
            case ZigTypeIdInt:
            case ZigTypeIdFloat:
            case ZigTypeIdPointer:
            case ZigTypeIdComptimeFloat:
            case ZigTypeIdComptimeInt:
            case ZigTypeIdEnumLiteral:
            case ZigTypeIdArray:
            case ZigTypeIdUndefined:
            case ZigTypeIdNull:
            case ZigTypeIdErrorUnion:
            case ZigTypeIdErrorSet:
            case ZigTypeIdBoundFn:
            case ZigTypeIdOptional:
            case ZigTypeIdFn:
            case ZigTypeIdVector:
            case ZigTypeIdFnFrame:
            case ZigTypeIdAnyFrame:
                zig_unreachable();

            case ZigTypeIdEnum:
                if (type_entry->data.enumeration.layout == ContainerLayoutExtern) {
                    buf_appendf(out_buf, "enum %s {\n", buf_ptr(type_h_name(type_entry)));
                    for (uint32_t field_i = 0; field_i < type_entry->data.enumeration.src_field_count; field_i += 1) {
                        TypeEnumField *enum_field = &type_entry->data.enumeration.fields[field_i];
                        Buf *value_buf = buf_alloc();
                        bigint_append_buf(value_buf, &enum_field->value, 10);
                        buf_appendf(out_buf, "    %s = %s", buf_ptr(enum_field->name), buf_ptr(value_buf));
                        if (field_i != type_entry->data.enumeration.src_field_count - 1) {
                            buf_appendf(out_buf, ",");
                        }
                        buf_appendf(out_buf, "\n");
                    }
                    buf_appendf(out_buf, "};\n\n");
                } else {
                    buf_appendf(out_buf, "enum %s;\n\n", buf_ptr(type_h_name(type_entry)));
                }
                break;
            case ZigTypeIdStruct:
                if (type_entry->data.structure.layout == ContainerLayoutExtern) {
                    buf_appendf(out_buf, "struct %s {\n", buf_ptr(type_h_name(type_entry)));
                    for (uint32_t field_i = 0; field_i < type_entry->data.structure.src_field_count; field_i += 1) {
                        TypeStructField *struct_field = type_entry->data.structure.fields[field_i];

                        Buf *type_name_buf = buf_alloc();
                        get_c_type(g, gen_h, struct_field->type_entry, type_name_buf);

                        if (struct_field->type_entry->id == ZigTypeIdArray) {
                            buf_appendf(out_buf, "    %s %s[%" ZIG_PRI_u64 "];\n", buf_ptr(type_name_buf),
                                    buf_ptr(struct_field->name),
                                    struct_field->type_entry->data.array.len);
                        } else {
                            buf_appendf(out_buf, "    %s %s;\n", buf_ptr(type_name_buf), buf_ptr(struct_field->name));
                        }

                    }
                    buf_appendf(out_buf, "};\n\n");
                } else {
                    buf_appendf(out_buf, "struct %s;\n\n", buf_ptr(type_h_name(type_entry)));
                }
                break;
            case ZigTypeIdUnion:
                if (type_entry->data.unionation.layout == ContainerLayoutExtern) {
                    buf_appendf(out_buf, "union %s {\n", buf_ptr(type_h_name(type_entry)));
                    for (uint32_t field_i = 0; field_i < type_entry->data.unionation.src_field_count; field_i += 1) {
                        TypeUnionField *union_field = &type_entry->data.unionation.fields[field_i];

                        Buf *type_name_buf = buf_alloc();
                        get_c_type(g, gen_h, union_field->type_entry, type_name_buf);
                        buf_appendf(out_buf, "    %s %s;\n", buf_ptr(type_name_buf), buf_ptr(union_field->name));
                    }
                    buf_appendf(out_buf, "};\n\n");
                } else {
                    buf_appendf(out_buf, "union %s;\n\n", buf_ptr(type_h_name(type_entry)));
                }
                break;
            case ZigTypeIdOpaque:
                buf_appendf(out_buf, "struct %s;\n\n", buf_ptr(type_h_name(type_entry)));
                break;
        }
    }
}

static void gen_h_file_functions(CodeGen* g, GenH* gen_h, Buf* out_buf, Buf* export_macro) {
    for (size_t fn_def_i = 0; fn_def_i < g->fn_defs.length; fn_def_i += 1) {
        ZigFn *fn_table_entry = g->fn_defs.at(fn_def_i);

        if (fn_table_entry->export_list.length == 0)
            continue;

        FnTypeId *fn_type_id = &fn_table_entry->type_entry->data.fn.fn_type_id;

        Buf return_type_c = BUF_INIT;
        get_c_type(g, gen_h, fn_type_id->return_type, &return_type_c);

        Buf *symbol_name;
        if (fn_table_entry->export_list.length == 0) {
            symbol_name = &fn_table_entry->symbol_name;
        } else {
            GlobalExport *fn_export = &fn_table_entry->export_list.items[0];
            symbol_name = &fn_export->name;
        }

        if (export_macro != nullptr) {
            buf_appendf(out_buf, "%s %s %s(",
                buf_ptr(export_macro),
                buf_ptr(&return_type_c),
                buf_ptr(symbol_name));
        } else {
            buf_appendf(out_buf, "%s %s(",
                buf_ptr(&return_type_c),
                buf_ptr(symbol_name));
        }

        Buf param_type_c = BUF_INIT;
        if (fn_type_id->param_count > 0) {
            for (size_t param_i = 0; param_i < fn_type_id->param_count; param_i += 1) {
                FnTypeParamInfo *param_info = &fn_type_id->param_info[param_i];
                AstNode *param_decl_node = get_param_decl_node(fn_table_entry, param_i);
                Buf *param_name = param_decl_node->data.param_decl.name;

                const char *comma_str = (param_i == 0) ? "" : ", ";
                const char *restrict_str = param_info->is_noalias ? "restrict" : "";
                get_c_type(g, gen_h, param_info->type, &param_type_c);

                if (param_info->type->id == ZigTypeIdArray) {
                    // Arrays decay to pointers
                    buf_appendf(out_buf, "%s%s%s %s[]", comma_str, buf_ptr(&param_type_c),
                            restrict_str, buf_ptr(param_name));
                } else {
                    buf_appendf(out_buf, "%s%s%s %s", comma_str, buf_ptr(&param_type_c),
                            restrict_str, buf_ptr(param_name));
                }
            }
            buf_appendf(out_buf, ")");
        } else {
            buf_appendf(out_buf, "void)");
        }

        buf_appendf(out_buf, ";\n");
    }
}

static void gen_h_file_variables(CodeGen* g, GenH* gen_h, Buf* h_buf, Buf* export_macro) {
    for (size_t exp_var_i = 0; exp_var_i < g->global_vars.length; exp_var_i += 1) {
        ZigVar* var = g->global_vars.at(exp_var_i)->var;
        if (var->export_list.length == 0)
            continue;

        Buf var_type_c = BUF_INIT;
        get_c_type(g, gen_h, var->var_type, &var_type_c);

        if (export_macro != nullptr) {
            buf_appendf(h_buf, "extern %s %s %s;\n",
                buf_ptr(export_macro),
                buf_ptr(&var_type_c),
                var->name);
        } else {
            buf_appendf(h_buf, "extern %s %s;\n",
                buf_ptr(&var_type_c),
                var->name);
        }
    }
}

static void gen_h_file(CodeGen *g) {
    GenH gen_h_data = {0};
    GenH *gen_h = &gen_h_data;

    assert(!g->is_test_build);
    assert(!g->disable_gen_h);

    Buf *out_h_path = buf_sprintf("%s" OS_SEP "%s.h", buf_ptr(g->output_dir), buf_ptr(g->root_out_name));

    FILE *out_h = fopen(buf_ptr(out_h_path), "wb");
    if (!out_h)
        zig_panic("unable to open %s: %s\n", buf_ptr(out_h_path), strerror(errno));

    Buf *export_macro = nullptr;
    if (g->is_dynamic) {
        export_macro = preprocessor_mangle(buf_sprintf("%s_EXPORT", buf_ptr(g->root_out_name)));
        buf_upcase(export_macro);
    }

    Buf fns_buf = BUF_INIT;
    buf_resize(&fns_buf, 0);
    gen_h_file_functions(g, gen_h, &fns_buf, export_macro);

    Buf vars_buf = BUF_INIT;
    buf_resize(&vars_buf, 0);
    gen_h_file_variables(g, gen_h, &vars_buf, export_macro);

    // Types will be populated by exported functions and variables so it has to run last.
    Buf types_buf = BUF_INIT;
    buf_resize(&types_buf, 0);
    gen_h_file_types(g, gen_h, &types_buf);

    Buf *ifdef_dance_name = preprocessor_mangle(buf_sprintf("%s_H", buf_ptr(g->root_out_name)));
    buf_upcase(ifdef_dance_name);

    fprintf(out_h, "#ifndef %s\n", buf_ptr(ifdef_dance_name));
    fprintf(out_h, "#define %s\n\n", buf_ptr(ifdef_dance_name));

    if (g->c_want_stdbool)
        fprintf(out_h, "#include <stdbool.h>\n");
    if (g->c_want_stdint)
        fprintf(out_h, "#include <stdint.h>\n");

    fprintf(out_h, "\n");

    if (g->is_dynamic) {
        fprintf(out_h, "#if defined(_WIN32)\n");
        fprintf(out_h, "#define %s __declspec(dllimport)\n", buf_ptr(export_macro));
        fprintf(out_h, "#else\n");
        fprintf(out_h, "#define %s __attribute__((visibility (\"default\")))\n",
            buf_ptr(export_macro));
        fprintf(out_h, "#endif\n");
        fprintf(out_h, "\n");
    }

    fprintf(out_h, "%s", buf_ptr(&types_buf));

    fprintf(out_h, "#ifdef __cplusplus\n");
    fprintf(out_h, "extern \"C\" {\n");
    fprintf(out_h, "#endif\n");
    fprintf(out_h, "\n");

    fprintf(out_h, "%s\n", buf_ptr(&fns_buf));

    fprintf(out_h, "#ifdef __cplusplus\n");
    fprintf(out_h, "} // extern \"C\"\n");
    fprintf(out_h, "#endif\n\n");

    fprintf(out_h, "%s\n", buf_ptr(&vars_buf));

    fprintf(out_h, "#endif // %s\n", buf_ptr(ifdef_dance_name));

    if (fclose(out_h))
        zig_panic("unable to close h file: %s", strerror(errno));
}

void codegen_print_timing_report(CodeGen *g, FILE *f) {
    double start_time = g->timing_events.at(0).time;
    double end_time = g->timing_events.last().time;
    double total = end_time - start_time;
    fprintf(f, "%20s%12s%12s%12s%12s\n", "Name", "Start", "End", "Duration", "Percent");
    for (size_t i = 0; i < g->timing_events.length - 1; i += 1) {
        TimeEvent *te = &g->timing_events.at(i);
        TimeEvent *next_te = &g->timing_events.at(i + 1);
        fprintf(f, "%20s%12.4f%12.4f%12.4f%12.4f\n", te->name,
                te->time - start_time,
                next_te->time - start_time,
                next_te->time - te->time,
                (next_te->time - te->time) / total);
    }
    fprintf(f, "%20s%12.4f%12.4f%12.4f%12.4f\n", "Total", 0.0, total, total, 1.0);
}

void codegen_add_time_event(CodeGen *g, const char *name) {
    OsTimeStamp timestamp = os_timestamp_monotonic();
    double seconds = (double)timestamp.sec;
    seconds += ((double)timestamp.nsec) / 1000000000.0;
    g->timing_events.append({seconds, name});
}

static void add_cache_pkg(CodeGen *g, CacheHash *ch, ZigPackage *pkg) {
    if (buf_len(&pkg->root_src_path) == 0)
        return;
    pkg->added_to_cache = true;

    Buf *rel_full_path = buf_alloc();
    os_path_join(&pkg->root_src_dir, &pkg->root_src_path, rel_full_path);
    cache_file(ch, rel_full_path);

    auto it = pkg->package_table.entry_iterator();
    for (;;) {
        auto *entry = it.next();
        if (!entry)
            break;

        if (!pkg->added_to_cache) {
            cache_buf(ch, entry->key);
            add_cache_pkg(g, ch, entry->value);
        }
    }
}

// Called before init()
// is_cache_hit takes into account gen_c_objects
static Error check_cache(CodeGen *g, Buf *manifest_dir, Buf *digest) {
    Error err;

    Buf *compiler_id;
    if ((err = get_compiler_id(&compiler_id)))
        return err;

    CacheHash *ch = &g->cache_hash;
    cache_init(ch, manifest_dir);

    add_cache_pkg(g, ch, g->main_pkg);
    if (g->linker_script != nullptr) {
        cache_file(ch, buf_create_from_str(g->linker_script));
    }
    cache_buf(ch, compiler_id);
    cache_buf(ch, g->root_out_name);
    cache_buf(ch, g->zig_lib_dir);
    cache_buf(ch, g->zig_std_dir);
    cache_list_of_link_lib(ch, g->link_libs_list.items, g->link_libs_list.length);
    cache_list_of_buf(ch, g->darwin_frameworks.items, g->darwin_frameworks.length);
    cache_list_of_buf(ch, g->rpath_list.items, g->rpath_list.length);
    cache_list_of_buf(ch, g->forbidden_libs.items, g->forbidden_libs.length);
    cache_int(ch, g->build_mode);
    cache_int(ch, g->out_type);
    cache_bool(ch, g->zig_target->is_native);
    cache_int(ch, g->zig_target->arch);
    cache_int(ch, g->zig_target->sub_arch);
    cache_int(ch, g->zig_target->vendor);
    cache_int(ch, g->zig_target->os);
    cache_int(ch, g->zig_target->abi);
    if (g->zig_target->glibc_version != nullptr) {
        cache_int(ch, g->zig_target->glibc_version->major);
        cache_int(ch, g->zig_target->glibc_version->minor);
        cache_int(ch, g->zig_target->glibc_version->patch);
    }
    cache_int(ch, detect_subsystem(g));
    cache_bool(ch, g->strip_debug_symbols);
    cache_bool(ch, g->is_test_build);
    if (g->is_test_build) {
        cache_buf_opt(ch, g->test_filter);
        cache_buf_opt(ch, g->test_name_prefix);
    }
    cache_bool(ch, g->link_eh_frame_hdr);
    cache_bool(ch, g->is_single_threaded);
    cache_bool(ch, g->linker_rdynamic);
    cache_bool(ch, g->each_lib_rpath);
    cache_bool(ch, g->disable_gen_h);
    cache_bool(ch, g->bundle_compiler_rt);
    cache_bool(ch, want_valgrind_support(g));
    cache_bool(ch, g->have_pic);
    cache_bool(ch, g->have_dynamic_link);
    cache_bool(ch, g->have_stack_probing);
    cache_bool(ch, g->have_sanitize_c);
    cache_bool(ch, g->is_dummy_so);
    cache_bool(ch, g->function_sections);
    cache_bool(ch, g->enable_dump_analysis);
    cache_bool(ch, g->enable_doc_generation);
    cache_bool(ch, g->disable_bin_generation);
    cache_buf_opt(ch, g->mmacosx_version_min);
    cache_buf_opt(ch, g->mios_version_min);
    cache_usize(ch, g->version_major);
    cache_usize(ch, g->version_minor);
    cache_usize(ch, g->version_patch);
    cache_list_of_str(ch, g->llvm_argv, g->llvm_argv_len);
    cache_list_of_str(ch, g->clang_argv, g->clang_argv_len);
    cache_list_of_str(ch, g->lib_dirs.items, g->lib_dirs.length);
    cache_list_of_str(ch, g->framework_dirs.items, g->framework_dirs.length);
    if (g->libc) {
        cache_buf(ch, &g->libc->include_dir);
        cache_buf(ch, &g->libc->sys_include_dir);
        cache_buf(ch, &g->libc->crt_dir);
        cache_buf(ch, &g->libc->msvc_lib_dir);
        cache_buf(ch, &g->libc->kernel32_lib_dir);
    }
    cache_buf_opt(ch, g->dynamic_linker_path);
    cache_buf_opt(ch, g->version_script_path);
    if (g->llvm_cpu) cache_str(ch, g->llvm_cpu);
    if (g->llvm_features) cache_str(ch, g->llvm_features);

    // gen_c_objects appends objects to g->link_objects which we want to include in the hash
    gen_c_objects(g);
    cache_list_of_file(ch, g->link_objects.items, g->link_objects.length);

    buf_resize(digest, 0);
    if ((err = cache_hit(ch, digest))) {
        if (err != ErrorInvalidFormat)
            return err;
    }

    if (ch->manifest_file_path != nullptr) {
        g->caches_to_release.append(ch);
    }

    return ErrorNone;
}

static bool need_llvm_module(CodeGen *g) {
    return buf_len(&g->main_pkg->root_src_path) != 0;
}

static void resolve_out_paths(CodeGen *g) {
    assert(g->output_dir != nullptr);
    assert(g->root_out_name != nullptr);

    Buf *out_basename = buf_create_from_buf(g->root_out_name);
    Buf *o_basename = buf_create_from_buf(g->root_out_name);
    switch (g->emit_file_type) {
        case EmitFileTypeBinary: {
            switch (g->out_type) {
                case OutTypeUnknown:
                    zig_unreachable();
                case OutTypeObj:
                    if (g->enable_cache && g->link_objects.length == 1 && !need_llvm_module(g)) {
                        buf_init_from_buf(&g->output_file_path, g->link_objects.at(0));
                        return;
                    }
                    if (need_llvm_module(g) && g->link_objects.length != 0 && !g->enable_cache &&
                        buf_eql_buf(o_basename, out_basename))
                    {
                        // make it not collide with main output object
                        buf_append_str(o_basename, ".root");
                    }
                    buf_append_str(o_basename, target_o_file_ext(g->zig_target));
                    buf_append_str(out_basename, target_o_file_ext(g->zig_target));
                    break;
                case OutTypeExe:
                    buf_append_str(o_basename, target_o_file_ext(g->zig_target));
                    buf_append_str(out_basename, target_exe_file_ext(g->zig_target));
                    break;
                case OutTypeLib:
                    buf_append_str(o_basename, target_o_file_ext(g->zig_target));
                    buf_resize(out_basename, 0);
                    buf_append_str(out_basename, target_lib_file_prefix(g->zig_target));
                    buf_append_buf(out_basename, g->root_out_name);
                    buf_append_str(out_basename, target_lib_file_ext(g->zig_target, !g->is_dynamic,
                                g->version_major, g->version_minor, g->version_patch));
                    break;
            }
            break;
        }
        case EmitFileTypeAssembly: {
            const char *asm_ext = target_asm_file_ext(g->zig_target);
            buf_append_str(o_basename, asm_ext);
            buf_append_str(out_basename, asm_ext);
            break;
        }
        case EmitFileTypeLLVMIr: {
            const char *llvm_ir_ext = target_llvm_ir_file_ext(g->zig_target);
            buf_append_str(o_basename, llvm_ir_ext);
            buf_append_str(out_basename, llvm_ir_ext);
            break;
        }
    }

    os_path_join(g->output_dir, o_basename, &g->o_file_output_path);
    os_path_join(g->output_dir, out_basename, &g->output_file_path);
}

void codegen_build_and_link(CodeGen *g) {
    Error err;
    assert(g->out_type != OutTypeUnknown);

    if (!g->enable_cache) {
        if (g->output_dir == nullptr) {
            g->output_dir = buf_create_from_str(".");
        } else if ((err = os_make_path(g->output_dir))) {
            fprintf(stderr, "Unable to create output directory: %s\n", err_str(err));
            exit(1);
        }
    }

    g->have_dynamic_link = detect_dynamic_link(g);
    g->have_pic = detect_pic(g);
    g->is_single_threaded = detect_single_threaded(g);
    g->have_err_ret_tracing = detect_err_ret_tracing(g);
    g->have_sanitize_c = detect_sanitize_c(g);
    detect_libc(g);
    detect_dynamic_linker(g);

    Buf digest = BUF_INIT;
    if (g->enable_cache) {
        Buf *manifest_dir = buf_alloc();
        os_path_join(g->cache_dir, buf_create_from_str(CACHE_HASH_SUBDIR), manifest_dir);

        if ((err = check_cache(g, manifest_dir, &digest))) {
            if (err == ErrorCacheUnavailable) {
                // message already printed
            } else if (err == ErrorNotDir) {
                fprintf(stderr, "Unable to check cache: %s is not a directory\n",
                    buf_ptr(manifest_dir));
            } else {
                fprintf(stderr, "Unable to check cache: %s: %s\n", buf_ptr(manifest_dir), err_str(err));
            }
            exit(1);
        }
    } else {
        // There is a call to this in check_cache
        gen_c_objects(g);
    }

    if (g->enable_cache && buf_len(&digest) != 0) {
        g->output_dir = buf_sprintf("%s" OS_SEP CACHE_OUT_SUBDIR OS_SEP "%s",
                buf_ptr(g->cache_dir), buf_ptr(&digest));
        resolve_out_paths(g);
    } else {
        if (need_llvm_module(g)) {
            init(g);

            codegen_add_time_event(g, "Semantic Analysis");
            const char *progress_name = "Semantic Analysis";
            codegen_switch_sub_prog_node(g, stage2_progress_start(g->main_progress_node,
                    progress_name, strlen(progress_name), 0));

            gen_root_source(g);

        }
        if (g->enable_cache) {
            if (buf_len(&digest) == 0) {
                if ((err = cache_final(&g->cache_hash, &digest))) {
                    fprintf(stderr, "Unable to finalize cache hash: %s\n", err_str(err));
                    exit(1);
                }
            }
            g->output_dir = buf_sprintf("%s" OS_SEP CACHE_OUT_SUBDIR OS_SEP "%s",
                    buf_ptr(g->cache_dir), buf_ptr(&digest));

            if ((err = os_make_path(g->output_dir))) {
                fprintf(stderr, "Unable to create output directory: %s\n", err_str(err));
                exit(1);
            }
        }
        resolve_out_paths(g);

        if (need_llvm_module(g)) {
            codegen_add_time_event(g, "Code Generation");
            {
                const char *progress_name = "Code Generation";
                codegen_switch_sub_prog_node(g, stage2_progress_start(g->main_progress_node,
                        progress_name, strlen(progress_name), 0));
            }

            do_code_gen(g);
            codegen_add_time_event(g, "LLVM Emit Output");
            {
                const char *progress_name = "LLVM Emit Output";
                codegen_switch_sub_prog_node(g, stage2_progress_start(g->main_progress_node,
                        progress_name, strlen(progress_name), 0));
            }
            zig_llvm_emit_output(g);

            if (!g->disable_gen_h && (g->out_type == OutTypeObj || g->out_type == OutTypeLib)) {
                codegen_add_time_event(g, "Generate .h");
                {
                    const char *progress_name = "Generate .h";
                    codegen_switch_sub_prog_node(g, stage2_progress_start(g->main_progress_node,
                            progress_name, strlen(progress_name), 0));
                }
                gen_h_file(g);
            }
        }
        if (g->enable_dump_analysis) {
            const char *analysis_json_filename = buf_ptr(buf_sprintf("%s" OS_SEP "%s-analysis.json",
                        buf_ptr(g->output_dir), buf_ptr(g->root_out_name)));
            FILE *f = fopen(analysis_json_filename, "wb");
            if (f == nullptr) {
                fprintf(stderr, "Unable to open '%s': %s\n", analysis_json_filename, strerror(errno));
                exit(1);
            }
            zig_print_analysis_dump(g, f, " ", "\n");
            if (fclose(f) != 0) {
                fprintf(stderr, "Unable to write '%s': %s\n", analysis_json_filename, strerror(errno));
                exit(1);
            }
        }
        if (g->enable_doc_generation) {
            Buf *doc_dir_path = buf_sprintf("%s" OS_SEP "docs", buf_ptr(g->output_dir));
            if ((err = os_make_path(doc_dir_path))) {
                fprintf(stderr, "Unable to create directory %s: %s\n", buf_ptr(doc_dir_path), err_str(err));
                exit(1);
            }
            Buf *index_html_src_path = buf_sprintf("%s" OS_SEP "special" OS_SEP "docs" OS_SEP "index.html",
                    buf_ptr(g->zig_std_dir));
            Buf *index_html_dest_path = buf_sprintf("%s" OS_SEP "index.html", buf_ptr(doc_dir_path));
            Buf *main_js_src_path = buf_sprintf("%s" OS_SEP "special" OS_SEP "docs" OS_SEP "main.js",
                    buf_ptr(g->zig_std_dir));
            Buf *main_js_dest_path = buf_sprintf("%s" OS_SEP "main.js", buf_ptr(doc_dir_path));

            if ((err = os_copy_file(index_html_src_path, index_html_dest_path))) {
                fprintf(stderr, "Unable to copy %s to %s: %s\n", buf_ptr(index_html_src_path),
                        buf_ptr(index_html_dest_path), err_str(err));
                exit(1);
            }
            if ((err = os_copy_file(main_js_src_path, main_js_dest_path))) {
                fprintf(stderr, "Unable to copy %s to %s: %s\n", buf_ptr(main_js_src_path),
                        buf_ptr(main_js_dest_path), err_str(err));
                exit(1);
            }
            const char *data_js_filename = buf_ptr(buf_sprintf("%s" OS_SEP "data.js", buf_ptr(doc_dir_path)));
            FILE *f = fopen(data_js_filename, "wb");
            if (f == nullptr) {
                fprintf(stderr, "Unable to open '%s': %s\n", data_js_filename, strerror(errno));
                exit(1);
            }
            fprintf(f, "zigAnalysis=");
            zig_print_analysis_dump(g, f, "", "");
            fprintf(f, ";");
            if (fclose(f) != 0) {
                fprintf(stderr, "Unable to write '%s': %s\n", data_js_filename, strerror(errno));
                exit(1);
            }
        }

        // If we're outputting assembly or llvm IR we skip linking.
        // If we're making a library or executable we must link.
        // If there is more than one object, we have to link them (with -r).
        // Finally, if we didn't make an object from zig source, and we don't have caching enabled,
        // then we have an object from C source that we must copy to the output dir which we do with a -r link.
        if (!g->disable_bin_generation && g->emit_file_type == EmitFileTypeBinary &&
                (g->out_type != OutTypeObj || g->link_objects.length > 1 ||
                    (!need_llvm_module(g) && !g->enable_cache)))
        {
            codegen_link(g);
        }
    }

    codegen_release_caches(g);
    codegen_add_time_event(g, "Done");
    codegen_switch_sub_prog_node(g, nullptr);
}

void codegen_release_caches(CodeGen *g) {
    while (g->caches_to_release.length != 0) {
        cache_release(g->caches_to_release.pop());
    }
}

ZigPackage *codegen_create_package(CodeGen *g, const char *root_src_dir, const char *root_src_path,
        const char *pkg_path)
{
    init(g);
    ZigPackage *pkg = new_package(root_src_dir, root_src_path, pkg_path);
    if (g->std_package != nullptr) {
        assert(g->compile_var_package != nullptr);
        pkg->package_table.put(buf_create_from_str("std"), g->std_package);

        pkg->package_table.put(buf_create_from_str("root"), g->root_pkg);

        pkg->package_table.put(buf_create_from_str("builtin"), g->compile_var_package);
    }
    return pkg;
}

CodeGen *create_child_codegen(CodeGen *parent_gen, Buf *root_src_path, OutType out_type,
        ZigLibCInstallation *libc, const char *name, Stage2ProgressNode *parent_progress_node)
{
    Stage2ProgressNode *child_progress_node = stage2_progress_start(
            parent_progress_node ? parent_progress_node : parent_gen->sub_progress_node,
            name, strlen(name), 0);

    CodeGen *child_gen = codegen_create(nullptr, root_src_path, parent_gen->zig_target, out_type,
        parent_gen->build_mode, parent_gen->zig_lib_dir, libc, get_global_cache_dir(), false, child_progress_node);
    child_gen->root_out_name = buf_create_from_str(name);
    child_gen->disable_gen_h = true;
    child_gen->want_stack_check = WantStackCheckDisabled;
    child_gen->want_sanitize_c = WantCSanitizeDisabled;
    child_gen->verbose_tokenize = parent_gen->verbose_tokenize;
    child_gen->verbose_ast = parent_gen->verbose_ast;
    child_gen->verbose_link = parent_gen->verbose_link;
    child_gen->verbose_ir = parent_gen->verbose_ir;
    child_gen->verbose_llvm_ir = parent_gen->verbose_llvm_ir;
    child_gen->verbose_cimport = parent_gen->verbose_cimport;
    child_gen->verbose_cc = parent_gen->verbose_cc;
    child_gen->llvm_argv = parent_gen->llvm_argv;
    child_gen->dynamic_linker_path = parent_gen->dynamic_linker_path;

    codegen_set_strip(child_gen, parent_gen->strip_debug_symbols);
    child_gen->want_pic = parent_gen->have_pic ? WantPICEnabled : WantPICDisabled;
    child_gen->valgrind_support = ValgrindSupportDisabled;

    codegen_set_errmsg_color(child_gen, parent_gen->err_color);

    codegen_set_mmacosx_version_min(child_gen, parent_gen->mmacosx_version_min);
    codegen_set_mios_version_min(child_gen, parent_gen->mios_version_min);

    child_gen->enable_cache = true;

    return child_gen;
}

CodeGen *codegen_create(Buf *main_pkg_path, Buf *root_src_path, const ZigTarget *target,
    OutType out_type, BuildMode build_mode, Buf *override_lib_dir,
    ZigLibCInstallation *libc, Buf *cache_dir, bool is_test_build, Stage2ProgressNode *progress_node)
{
    CodeGen *g = allocate<CodeGen>(1);
    g->main_progress_node = progress_node;

    codegen_add_time_event(g, "Initialize");
    {
        const char *progress_name = "Initialize";
        codegen_switch_sub_prog_node(g, stage2_progress_start(g->main_progress_node,
                progress_name, strlen(progress_name), 0));
    }

    g->subsystem = TargetSubsystemAuto;
    g->libc = libc;
    g->zig_target = target;
    g->cache_dir = cache_dir;

    if (override_lib_dir == nullptr) {
        g->zig_lib_dir = get_zig_lib_dir();
    } else {
        g->zig_lib_dir = override_lib_dir;
    }

    g->zig_std_dir = buf_alloc();
    os_path_join(g->zig_lib_dir, buf_create_from_str("std"), g->zig_std_dir);

    g->zig_c_headers_dir = buf_alloc();
    os_path_join(g->zig_lib_dir, buf_create_from_str("include"), g->zig_c_headers_dir);

    g->build_mode = build_mode;
    g->out_type = out_type;
    g->import_table.init(32);
    g->builtin_fn_table.init(32);
    g->primitive_type_table.init(32);
    g->type_table.init(32);
    g->fn_type_table.init(32);
    g->error_table.init(16);
    g->generic_table.init(16);
    g->llvm_fn_table.init(16);
    g->memoized_fn_eval_table.init(16);
    g->exported_symbol_names.init(8);
    g->external_prototypes.init(8);
    g->string_literals_table.init(16);
    g->type_info_cache.init(32);
    g->one_possible_values.init(32);
    g->is_test_build = is_test_build;
    g->is_single_threaded = false;
    buf_resize(&g->global_asm, 0);

    for (size_t i = 0; i < array_length(symbols_that_llvm_depends_on); i += 1) {
        g->external_prototypes.put(buf_create_from_str(symbols_that_llvm_depends_on[i]), nullptr);
    }

    if (root_src_path) {
        Buf *root_pkg_path;
        Buf *rel_root_src_path;
        if (main_pkg_path == nullptr) {
            Buf *src_basename = buf_alloc();
            Buf *src_dir = buf_alloc();
            os_path_split(root_src_path, src_dir, src_basename);

            if (buf_len(src_basename) == 0) {
                fprintf(stderr, "Invalid root source path: %s\n", buf_ptr(root_src_path));
                exit(1);
            }
            root_pkg_path = src_dir;
            rel_root_src_path = src_basename;
        } else {
            Buf resolved_root_src_path = os_path_resolve(&root_src_path, 1);
            Buf resolved_main_pkg_path = os_path_resolve(&main_pkg_path, 1);

            if (!buf_starts_with_buf(&resolved_root_src_path, &resolved_main_pkg_path)) {
                fprintf(stderr, "Root source path '%s' outside main package path '%s'",
                        buf_ptr(root_src_path), buf_ptr(main_pkg_path));
                exit(1);
            }
            root_pkg_path = main_pkg_path;
            rel_root_src_path = buf_create_from_mem(
                    buf_ptr(&resolved_root_src_path) + buf_len(&resolved_main_pkg_path) + 1,
                    buf_len(&resolved_root_src_path) - buf_len(&resolved_main_pkg_path) - 1);
        }

        g->main_pkg = new_package(buf_ptr(root_pkg_path), buf_ptr(rel_root_src_path), "");
        g->std_package = new_package(buf_ptr(g->zig_std_dir), "std.zig", "std");
        g->main_pkg->package_table.put(buf_create_from_str("std"), g->std_package);
    } else {
        g->main_pkg = new_package(".", "", "");
    }

    g->zig_std_special_dir = buf_alloc();
    os_path_join(g->zig_std_dir, buf_sprintf("special"), g->zig_std_special_dir);

    assert(target != nullptr);
    if (!target->is_native) {
        g->each_lib_rpath = false;
    } else {
        g->each_lib_rpath = true;

        if (target_os_is_darwin(g->zig_target->os)) {
            init_darwin_native(g);
        }

    }

    if (target_os_requires_libc(g->zig_target->os)) {
        g->libc_link_lib = create_link_lib(buf_create_from_str("c"));
        g->link_libs_list.append(g->libc_link_lib);
    }

    target_triple_llvm(&g->llvm_triple_str, g->zig_target);
    g->pointer_size_bytes = target_arch_pointer_bit_width(g->zig_target->arch) / 8;

    if (!target_has_debug_info(g->zig_target)) {
        g->strip_debug_symbols = true;
    }

    return g;
}

bool codegen_fn_has_err_ret_tracing_arg(CodeGen *g, ZigType *return_type) {
    return g->have_err_ret_tracing &&
        (return_type->id == ZigTypeIdErrorUnion ||
         return_type->id == ZigTypeIdErrorSet);
}

bool codegen_fn_has_err_ret_tracing_stack(CodeGen *g, ZigFn *fn, bool is_async) {
    if (is_async) {
        return g->have_err_ret_tracing && (fn->calls_or_awaits_errorable_fn ||
            codegen_fn_has_err_ret_tracing_arg(g, fn->type_entry->data.fn.fn_type_id.return_type));
    } else {
        return g->have_err_ret_tracing && fn->calls_or_awaits_errorable_fn &&
            !codegen_fn_has_err_ret_tracing_arg(g, fn->type_entry->data.fn.fn_type_id.return_type);
    }
}

void codegen_switch_sub_prog_node(CodeGen *g, Stage2ProgressNode *node) {
    if (g->sub_progress_node != nullptr) {
        stage2_progress_end(g->sub_progress_node);
    }
    g->sub_progress_node = node;
}

ZigValue *CodeGen::Intern::for_undefined() {
#ifdef ZIG_ENABLE_MEM_PROFILE
    memprof_intern_count.x_undefined += 1;
#endif
    return &this->x_undefined;
}

ZigValue *CodeGen::Intern::for_void() {
#ifdef ZIG_ENABLE_MEM_PROFILE
    memprof_intern_count.x_void += 1;
#endif
    return &this->x_void;
}

ZigValue *CodeGen::Intern::for_null() {
#ifdef ZIG_ENABLE_MEM_PROFILE
    memprof_intern_count.x_null += 1;
#endif
    return &this->x_null;
}

ZigValue *CodeGen::Intern::for_unreachable() {
#ifdef ZIG_ENABLE_MEM_PROFILE
    memprof_intern_count.x_unreachable += 1;
#endif
    return &this->x_unreachable;
}

ZigValue *CodeGen::Intern::for_zero_byte() {
#ifdef ZIG_ENABLE_MEM_PROFILE
    memprof_intern_count.zero_byte += 1;
#endif
    return &this->zero_byte;
}
