/*
 * Copyright (c) 2016 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 "error.hpp"
#include "ir.hpp"
#include "ir_print.hpp"
#include "os.hpp"
#include "range_set.hpp"
#include "softfloat.hpp"
#include "translate_c.hpp"
#include "util.hpp"

struct IrExecContext {
    ZigList<ConstExprValue *> mem_slot_list;
};

struct IrBuilder {
    CodeGen *codegen;
    IrExecutable *exec;
    IrBasicBlock *current_basic_block;
};

struct IrAnalyze {
    CodeGen *codegen;
    IrBuilder old_irb;
    IrBuilder new_irb;
    IrExecContext exec_context;
    size_t old_bb_index;
    size_t instruction_index;
    ZigType *explicit_return_type;
    AstNode *explicit_return_type_source_node;
    ZigList<IrInstruction *> src_implicit_return_type_list;
    IrBasicBlock *const_predecessor_bb;
};

enum ConstCastResultId {
    ConstCastResultIdOk,
    ConstCastResultIdInvalid,
    ConstCastResultIdErrSet,
    ConstCastResultIdErrSetGlobal,
    ConstCastResultIdPointerChild,
    ConstCastResultIdSliceChild,
    ConstCastResultIdOptionalChild,
    ConstCastResultIdErrorUnionPayload,
    ConstCastResultIdErrorUnionErrorSet,
    ConstCastResultIdFnAlign,
    ConstCastResultIdFnCC,
    ConstCastResultIdFnVarArgs,
    ConstCastResultIdFnIsGeneric,
    ConstCastResultIdFnReturnType,
    ConstCastResultIdFnArgCount,
    ConstCastResultIdFnGenericArgCount,
    ConstCastResultIdFnArg,
    ConstCastResultIdFnArgNoAlias,
    ConstCastResultIdType,
    ConstCastResultIdUnresolvedInferredErrSet,
    ConstCastResultIdAsyncAllocatorType,
    ConstCastResultIdBadAllowsZero,
};

struct ConstCastOnly;
struct ConstCastArg {
    size_t arg_index;
    ZigType *actual_param_type;
    ZigType *expected_param_type;
    ConstCastOnly *child;
};

struct ConstCastArgNoAlias {
    size_t arg_index;
};

struct ConstCastOptionalMismatch;
struct ConstCastPointerMismatch;
struct ConstCastSliceMismatch;
struct ConstCastErrUnionErrSetMismatch;
struct ConstCastErrUnionPayloadMismatch;
struct ConstCastErrSetMismatch;
struct ConstCastTypeMismatch;
struct ConstCastBadAllowsZero;

struct ConstCastOnly {
    ConstCastResultId id;
    union {
        ConstCastErrSetMismatch *error_set_mismatch;
        ConstCastPointerMismatch *pointer_mismatch;
        ConstCastSliceMismatch *slice_mismatch;
        ConstCastOptionalMismatch *optional;
        ConstCastErrUnionPayloadMismatch *error_union_payload;
        ConstCastErrUnionErrSetMismatch *error_union_error_set;
        ConstCastTypeMismatch *type_mismatch;
        ConstCastOnly *return_type;
        ConstCastOnly *async_allocator_type;
        ConstCastOnly *null_wrap_ptr_child;
        ConstCastArg fn_arg;
        ConstCastArgNoAlias arg_no_alias;
        ConstCastBadAllowsZero *bad_allows_zero;
    } data;
};

struct ConstCastTypeMismatch {
    ZigType *wanted_type;
    ZigType *actual_type;
};

struct ConstCastOptionalMismatch {
    ConstCastOnly child;
    ZigType *wanted_child;
    ZigType *actual_child;
};

struct ConstCastPointerMismatch {
    ConstCastOnly child;
    ZigType *wanted_child;
    ZigType *actual_child;
};

struct ConstCastSliceMismatch {
    ConstCastOnly child;
    ZigType *wanted_child;
    ZigType *actual_child;
};

struct ConstCastErrUnionErrSetMismatch {
    ConstCastOnly child;
    ZigType *wanted_err_set;
    ZigType *actual_err_set;
};

struct ConstCastErrUnionPayloadMismatch {
    ConstCastOnly child;
    ZigType *wanted_payload;
    ZigType *actual_payload;
};

struct ConstCastErrSetMismatch {
    ZigList<ErrorTableEntry *> missing_errors;
};

struct ConstCastBadAllowsZero {
    ZigType *wanted_type;
    ZigType *actual_type;
};


enum UndefAllowed {
    UndefOk,
    UndefBad,
};

static IrInstruction *ir_gen_node(IrBuilder *irb, AstNode *node, Scope *scope);
static IrInstruction *ir_gen_node_extra(IrBuilder *irb, AstNode *node, Scope *scope, LVal lval);
static IrInstruction *ir_analyze_instruction(IrAnalyze *ira, IrInstruction *instruction);
static IrInstruction *ir_implicit_cast(IrAnalyze *ira, IrInstruction *value, ZigType *expected_type);
static IrInstruction *ir_get_deref(IrAnalyze *ira, IrInstruction *source_instruction, IrInstruction *ptr);
static ErrorMsg *exec_add_error_node(CodeGen *codegen, IrExecutable *exec, AstNode *source_node, Buf *msg);
static IrInstruction *ir_analyze_container_field_ptr(IrAnalyze *ira, Buf *field_name,
    IrInstruction *source_instr, IrInstruction *container_ptr, ZigType *container_type);
static IrInstruction *ir_get_var_ptr(IrAnalyze *ira, IrInstruction *instruction, ZigVar *var);
static ZigType *ir_resolve_atomic_operand_type(IrAnalyze *ira, IrInstruction *op);
static IrInstruction *ir_lval_wrap(IrBuilder *irb, Scope *scope, IrInstruction *value, LVal lval);
static ZigType *adjust_ptr_align(CodeGen *g, ZigType *ptr_type, uint32_t new_align);
static ZigType *adjust_slice_align(CodeGen *g, ZigType *slice_type, uint32_t new_align);
static Error buf_read_value_bytes(IrAnalyze *ira, CodeGen *codegen, AstNode *source_node, uint8_t *buf, ConstExprValue *val);
static void buf_write_value_bytes(CodeGen *codegen, uint8_t *buf, ConstExprValue *val);
static Error ir_read_const_ptr(IrAnalyze *ira, CodeGen *codegen, AstNode *source_node,
        ConstExprValue *out_val, ConstExprValue *ptr_val);
static IrInstruction *ir_analyze_ptr_cast(IrAnalyze *ira, IrInstruction *source_instr, IrInstruction *ptr,
        ZigType *dest_type, IrInstruction *dest_type_src, bool safety_check_on);
static ConstExprValue *ir_resolve_const(IrAnalyze *ira, IrInstruction *value, UndefAllowed undef_allowed);
static void copy_const_val(ConstExprValue *dest, ConstExprValue *src, bool same_global_refs);
static Error resolve_ptr_align(IrAnalyze *ira, ZigType *ty, uint32_t *result_align);
static void ir_add_alloca(IrAnalyze *ira, IrInstruction *instruction, ZigType *type_entry);
static IrInstruction *ir_analyze_int_to_ptr(IrAnalyze *ira, IrInstruction *source_instr, IrInstruction *target,
        ZigType *ptr_type);
static IrInstruction *ir_analyze_bit_cast(IrAnalyze *ira, IrInstruction *source_instr, IrInstruction *value,
        ZigType *dest_type);

static ConstExprValue *const_ptr_pointee_unchecked(CodeGen *g, ConstExprValue *const_val) {
    assert(get_src_ptr_type(const_val->type) != nullptr);
    assert(const_val->special == ConstValSpecialStatic);
    ConstExprValue *result;
    switch (const_val->data.x_ptr.special) {
        case ConstPtrSpecialInvalid:
            zig_unreachable();
        case ConstPtrSpecialRef:
            result = const_val->data.x_ptr.data.ref.pointee;
            break;
        case ConstPtrSpecialBaseArray: {
            ConstExprValue *array_val = const_val->data.x_ptr.data.base_array.array_val;
            expand_undef_array(g, array_val);
            result = &array_val->data.x_array.data.s_none.elements[const_val->data.x_ptr.data.base_array.elem_index];
            break;
        }
        case ConstPtrSpecialBaseStruct: {
            ConstExprValue *struct_val = const_val->data.x_ptr.data.base_struct.struct_val;
            result = &struct_val->data.x_struct.fields[const_val->data.x_ptr.data.base_struct.field_index];
            break;
        }
        case ConstPtrSpecialBaseErrorUnionCode:
            result = const_val->data.x_ptr.data.base_err_union_code.err_union_val->data.x_err_union.error_set;
            break;
        case ConstPtrSpecialBaseErrorUnionPayload:
            result = const_val->data.x_ptr.data.base_err_union_payload.err_union_val->data.x_err_union.payload;
            break;
        case ConstPtrSpecialBaseOptionalPayload:
            result = const_val->data.x_ptr.data.base_optional_payload.optional_val->data.x_optional;
            break;
        case ConstPtrSpecialNull:
            result = const_val;
            break;
        case ConstPtrSpecialHardCodedAddr:
            zig_unreachable();
        case ConstPtrSpecialDiscard:
            zig_unreachable();
        case ConstPtrSpecialFunction:
            zig_unreachable();
    }
    assert(result != nullptr);
    return result;
}

static bool is_opt_err_set(ZigType *ty) {
    return ty->id == ZigTypeIdErrorSet ||
        (ty->id == ZigTypeIdOptional && ty->data.maybe.child_type->id == ZigTypeIdErrorSet);
}

// This function returns true when you can change the type of a ConstExprValue and the
// value remains meaningful.
static bool types_have_same_zig_comptime_repr(ZigType *a, ZigType *b) {
    if (a == b)
        return true;

    if (get_codegen_ptr_type(a) != nullptr && get_codegen_ptr_type(b) != nullptr)
        return true;

    if (is_opt_err_set(a) && is_opt_err_set(b))
        return true;

    if (a->id != b->id)
        return false;

    switch (a->id) {
        case ZigTypeIdInvalid:
        case ZigTypeIdUnreachable:
            zig_unreachable();
        case ZigTypeIdMetaType:
        case ZigTypeIdVoid:
        case ZigTypeIdBool:
        case ZigTypeIdComptimeFloat:
        case ZigTypeIdComptimeInt:
        case ZigTypeIdPointer:
        case ZigTypeIdUndefined:
        case ZigTypeIdNull:
        case ZigTypeIdNamespace:
        case ZigTypeIdBoundFn:
        case ZigTypeIdErrorSet:
        case ZigTypeIdOpaque:
            return true;
        case ZigTypeIdFloat:
            return a->data.floating.bit_count == b->data.floating.bit_count;
        case ZigTypeIdInt:
            return a->data.integral.is_signed == b->data.integral.is_signed;
        case ZigTypeIdArray:
        case ZigTypeIdStruct:
        case ZigTypeIdOptional:
        case ZigTypeIdErrorUnion:
        case ZigTypeIdEnum:
        case ZigTypeIdUnion:
        case ZigTypeIdFn:
        case ZigTypeIdArgTuple:
        case ZigTypeIdPromise:
        case ZigTypeIdVector:
            return false;
    }
    zig_unreachable();
}

static bool ir_should_inline(IrExecutable *exec, Scope *scope) {
    if (exec->is_inline)
        return true;

    while (scope != nullptr) {
        if (scope->id == ScopeIdCompTime)
            return true;
        if (scope->id == ScopeIdFnDef)
            break;
        scope = scope->parent;
    }
    return false;
}

static void ir_instruction_append(IrBasicBlock *basic_block, IrInstruction *instruction) {
    assert(basic_block);
    assert(instruction);
    basic_block->instruction_list.append(instruction);
}

static size_t exec_next_debug_id(IrExecutable *exec) {
    size_t result = exec->next_debug_id;
    exec->next_debug_id += 1;
    return result;
}

static size_t exec_next_mem_slot(IrExecutable *exec) {
    size_t result = exec->mem_slot_count;
    exec->mem_slot_count += 1;
    return result;
}

static ZigFn *exec_fn_entry(IrExecutable *exec) {
    return exec->fn_entry;
}

static Buf *exec_c_import_buf(IrExecutable *exec) {
    return exec->c_import_buf;
}

static bool value_is_comptime(ConstExprValue *const_val) {
    return const_val->special != ConstValSpecialRuntime;
}

static bool instr_is_comptime(IrInstruction *instruction) {
    return value_is_comptime(&instruction->value);
}

static bool instr_is_unreachable(IrInstruction *instruction) {
    return instruction->value.type && instruction->value.type->id == ZigTypeIdUnreachable;
}

static void ir_link_new_bb(IrBasicBlock *new_bb, IrBasicBlock *old_bb) {
    new_bb->other = old_bb;
    old_bb->other = new_bb;
}

static void ir_ref_bb(IrBasicBlock *bb) {
    bb->ref_count += 1;
}

static void ir_ref_instruction(IrInstruction *instruction, IrBasicBlock *cur_bb) {
    assert(instruction->id != IrInstructionIdInvalid);
    instruction->ref_count += 1;
    if (instruction->owner_bb != cur_bb && !instr_is_comptime(instruction))
        ir_ref_bb(instruction->owner_bb);
}

static void ir_ref_var(ZigVar *var) {
    var->ref_count += 1;
}

static IrBasicBlock *ir_create_basic_block(IrBuilder *irb, Scope *scope, const char *name_hint) {
    IrBasicBlock *result = allocate<IrBasicBlock>(1);
    result->scope = scope;
    result->name_hint = name_hint;
    result->debug_id = exec_next_debug_id(irb->exec);
    return result;
}

static IrBasicBlock *ir_build_bb_from(IrBuilder *irb, IrBasicBlock *other_bb) {
    IrBasicBlock *new_bb = ir_create_basic_block(irb, other_bb->scope, other_bb->name_hint);
    ir_link_new_bb(new_bb, other_bb);
    return new_bb;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionDeclVarSrc *) {
    return IrInstructionIdDeclVarSrc;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionDeclVarGen *) {
    return IrInstructionIdDeclVarGen;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionCondBr *) {
    return IrInstructionIdCondBr;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionBr *) {
    return IrInstructionIdBr;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionSwitchBr *) {
    return IrInstructionIdSwitchBr;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionSwitchVar *) {
    return IrInstructionIdSwitchVar;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionSwitchTarget *) {
    return IrInstructionIdSwitchTarget;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionPhi *) {
    return IrInstructionIdPhi;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionUnOp *) {
    return IrInstructionIdUnOp;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionBinOp *) {
    return IrInstructionIdBinOp;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionExport *) {
    return IrInstructionIdExport;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionLoadPtr *) {
    return IrInstructionIdLoadPtr;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionLoadPtrGen *) {
    return IrInstructionIdLoadPtrGen;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionStorePtr *) {
    return IrInstructionIdStorePtr;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionFieldPtr *) {
    return IrInstructionIdFieldPtr;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionStructFieldPtr *) {
    return IrInstructionIdStructFieldPtr;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionUnionFieldPtr *) {
    return IrInstructionIdUnionFieldPtr;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionElemPtr *) {
    return IrInstructionIdElemPtr;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionVarPtr *) {
    return IrInstructionIdVarPtr;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionCall *) {
    return IrInstructionIdCall;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionConst *) {
    return IrInstructionIdConst;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionReturn *) {
    return IrInstructionIdReturn;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionCast *) {
    return IrInstructionIdCast;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionResizeSlice *) {
    return IrInstructionIdResizeSlice;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionContainerInitList *) {
    return IrInstructionIdContainerInitList;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionContainerInitFields *) {
    return IrInstructionIdContainerInitFields;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionUnreachable *) {
    return IrInstructionIdUnreachable;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionTypeOf *) {
    return IrInstructionIdTypeOf;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionToPtrType *) {
    return IrInstructionIdToPtrType;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionPtrTypeChild *) {
    return IrInstructionIdPtrTypeChild;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionSetCold *) {
    return IrInstructionIdSetCold;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionSetRuntimeSafety *) {
    return IrInstructionIdSetRuntimeSafety;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionSetFloatMode *) {
    return IrInstructionIdSetFloatMode;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionArrayType *) {
    return IrInstructionIdArrayType;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionPromiseType *) {
    return IrInstructionIdPromiseType;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionSliceType *) {
    return IrInstructionIdSliceType;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionAsm *) {
    return IrInstructionIdAsm;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionSizeOf *) {
    return IrInstructionIdSizeOf;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionTestNonNull *) {
    return IrInstructionIdTestNonNull;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionOptionalUnwrapPtr *) {
    return IrInstructionIdOptionalUnwrapPtr;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionClz *) {
    return IrInstructionIdClz;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionCtz *) {
    return IrInstructionIdCtz;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionPopCount *) {
    return IrInstructionIdPopCount;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionUnionTag *) {
    return IrInstructionIdUnionTag;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionImport *) {
    return IrInstructionIdImport;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionCImport *) {
    return IrInstructionIdCImport;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionCInclude *) {
    return IrInstructionIdCInclude;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionCDefine *) {
    return IrInstructionIdCDefine;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionCUndef *) {
    return IrInstructionIdCUndef;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionRef *) {
    return IrInstructionIdRef;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionStructInit *) {
    return IrInstructionIdStructInit;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionUnionInit *) {
    return IrInstructionIdUnionInit;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionCompileErr *) {
    return IrInstructionIdCompileErr;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionCompileLog *) {
    return IrInstructionIdCompileLog;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionErrName *) {
    return IrInstructionIdErrName;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionEmbedFile *) {
    return IrInstructionIdEmbedFile;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionCmpxchgSrc *) {
    return IrInstructionIdCmpxchgSrc;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionCmpxchgGen *) {
    return IrInstructionIdCmpxchgGen;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionFence *) {
    return IrInstructionIdFence;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionTruncate *) {
    return IrInstructionIdTruncate;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionIntCast *) {
    return IrInstructionIdIntCast;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionFloatCast *) {
    return IrInstructionIdFloatCast;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionErrSetCast *) {
    return IrInstructionIdErrSetCast;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionToBytes *) {
    return IrInstructionIdToBytes;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionFromBytes *) {
    return IrInstructionIdFromBytes;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionIntToFloat *) {
    return IrInstructionIdIntToFloat;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionFloatToInt *) {
    return IrInstructionIdFloatToInt;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionBoolToInt *) {
    return IrInstructionIdBoolToInt;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionIntType *) {
    return IrInstructionIdIntType;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionVectorType *) {
    return IrInstructionIdVectorType;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionBoolNot *) {
    return IrInstructionIdBoolNot;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionMemset *) {
    return IrInstructionIdMemset;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionMemcpy *) {
    return IrInstructionIdMemcpy;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionSlice *) {
    return IrInstructionIdSlice;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionMemberCount *) {
    return IrInstructionIdMemberCount;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionMemberType *) {
    return IrInstructionIdMemberType;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionMemberName *) {
    return IrInstructionIdMemberName;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionBreakpoint *) {
    return IrInstructionIdBreakpoint;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionReturnAddress *) {
    return IrInstructionIdReturnAddress;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionFrameAddress *) {
    return IrInstructionIdFrameAddress;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionHandle *) {
    return IrInstructionIdHandle;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionAlignOf *) {
    return IrInstructionIdAlignOf;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionOverflowOp *) {
    return IrInstructionIdOverflowOp;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionTestErr *) {
    return IrInstructionIdTestErr;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionUnwrapErrCode *) {
    return IrInstructionIdUnwrapErrCode;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionUnwrapErrPayload *) {
    return IrInstructionIdUnwrapErrPayload;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionOptionalWrap *) {
    return IrInstructionIdOptionalWrap;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionErrWrapPayload *) {
    return IrInstructionIdErrWrapPayload;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionErrWrapCode *) {
    return IrInstructionIdErrWrapCode;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionFnProto *) {
    return IrInstructionIdFnProto;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionTestComptime *) {
    return IrInstructionIdTestComptime;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionPtrCastSrc *) {
    return IrInstructionIdPtrCastSrc;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionPtrCastGen *) {
    return IrInstructionIdPtrCastGen;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionBitCast *) {
    return IrInstructionIdBitCast;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionBitCastGen *) {
    return IrInstructionIdBitCastGen;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionWidenOrShorten *) {
    return IrInstructionIdWidenOrShorten;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionPtrToInt *) {
    return IrInstructionIdPtrToInt;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionIntToPtr *) {
    return IrInstructionIdIntToPtr;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionIntToEnum *) {
    return IrInstructionIdIntToEnum;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionEnumToInt *) {
    return IrInstructionIdEnumToInt;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionIntToErr *) {
    return IrInstructionIdIntToErr;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionErrToInt *) {
    return IrInstructionIdErrToInt;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionCheckSwitchProngs *) {
    return IrInstructionIdCheckSwitchProngs;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionCheckStatementIsVoid *) {
    return IrInstructionIdCheckStatementIsVoid;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionTypeName *) {
    return IrInstructionIdTypeName;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionDeclRef *) {
    return IrInstructionIdDeclRef;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionPanic *) {
    return IrInstructionIdPanic;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionTagName *) {
    return IrInstructionIdTagName;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionTagType *) {
    return IrInstructionIdTagType;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionFieldParentPtr *) {
    return IrInstructionIdFieldParentPtr;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionByteOffsetOf *) {
    return IrInstructionIdByteOffsetOf;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionBitOffsetOf *) {
    return IrInstructionIdBitOffsetOf;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionTypeInfo *) {
    return IrInstructionIdTypeInfo;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionTypeId *) {
    return IrInstructionIdTypeId;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionSetEvalBranchQuota *) {
    return IrInstructionIdSetEvalBranchQuota;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionPtrType *) {
    return IrInstructionIdPtrType;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionAlignCast *) {
    return IrInstructionIdAlignCast;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionOpaqueType *) {
    return IrInstructionIdOpaqueType;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionSetAlignStack *) {
    return IrInstructionIdSetAlignStack;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionArgType *) {
    return IrInstructionIdArgType;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionErrorReturnTrace *) {
    return IrInstructionIdErrorReturnTrace;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionErrorUnion *) {
    return IrInstructionIdErrorUnion;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionCancel *) {
    return IrInstructionIdCancel;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionGetImplicitAllocator *) {
    return IrInstructionIdGetImplicitAllocator;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionCoroId *) {
    return IrInstructionIdCoroId;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionCoroAlloc *) {
    return IrInstructionIdCoroAlloc;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionCoroSize *) {
    return IrInstructionIdCoroSize;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionCoroBegin *) {
    return IrInstructionIdCoroBegin;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionCoroAllocFail *) {
    return IrInstructionIdCoroAllocFail;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionCoroSuspend *) {
    return IrInstructionIdCoroSuspend;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionCoroEnd *) {
    return IrInstructionIdCoroEnd;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionCoroFree *) {
    return IrInstructionIdCoroFree;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionCoroResume *) {
    return IrInstructionIdCoroResume;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionCoroSave *) {
    return IrInstructionIdCoroSave;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionCoroPromise *) {
    return IrInstructionIdCoroPromise;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionCoroAllocHelper *) {
    return IrInstructionIdCoroAllocHelper;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionAtomicRmw *) {
    return IrInstructionIdAtomicRmw;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionAtomicLoad *) {
    return IrInstructionIdAtomicLoad;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionPromiseResultType *) {
    return IrInstructionIdPromiseResultType;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionAwaitBookkeeping *) {
    return IrInstructionIdAwaitBookkeeping;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionSaveErrRetAddr *) {
    return IrInstructionIdSaveErrRetAddr;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionAddImplicitReturnType *) {
    return IrInstructionIdAddImplicitReturnType;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionMergeErrRetTraces *) {
    return IrInstructionIdMergeErrRetTraces;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionMarkErrRetTracePtr *) {
    return IrInstructionIdMarkErrRetTracePtr;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionSqrt *) {
    return IrInstructionIdSqrt;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionBswap *) {
    return IrInstructionIdBswap;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionBitReverse *) {
    return IrInstructionIdBitReverse;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionCheckRuntimeScope *) {
    return IrInstructionIdCheckRuntimeScope;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionVectorToArray *) {
    return IrInstructionIdVectorToArray;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionArrayToVector *) {
    return IrInstructionIdArrayToVector;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionAssertZero *) {
    return IrInstructionIdAssertZero;
}

template<typename T>
static T *ir_create_instruction(IrBuilder *irb, Scope *scope, AstNode *source_node) {
    T *special_instruction = allocate<T>(1);
    special_instruction->base.id = ir_instruction_id(special_instruction);
    special_instruction->base.scope = scope;
    special_instruction->base.source_node = source_node;
    special_instruction->base.debug_id = exec_next_debug_id(irb->exec);
    special_instruction->base.owner_bb = irb->current_basic_block;
    special_instruction->base.value.global_refs = allocate<ConstGlobalRefs>(1);
    return special_instruction;
}

template<typename T>
static T *ir_build_instruction(IrBuilder *irb, Scope *scope, AstNode *source_node) {
    T *special_instruction = ir_create_instruction<T>(irb, scope, source_node);
    ir_instruction_append(irb->current_basic_block, &special_instruction->base);
    return special_instruction;
}

static IrInstruction *ir_build_cast(IrBuilder *irb, Scope *scope, AstNode *source_node, ZigType *dest_type,
    IrInstruction *value, CastOp cast_op)
{
    IrInstructionCast *cast_instruction = ir_build_instruction<IrInstructionCast>(irb, scope, source_node);
    cast_instruction->dest_type = dest_type;
    cast_instruction->value = value;
    cast_instruction->cast_op = cast_op;

    ir_ref_instruction(value, irb->current_basic_block);

    return &cast_instruction->base;
}

static IrInstruction *ir_build_cond_br(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *condition,
        IrBasicBlock *then_block, IrBasicBlock *else_block, IrInstruction *is_comptime)
{
    IrInstructionCondBr *cond_br_instruction = ir_build_instruction<IrInstructionCondBr>(irb, scope, source_node);
    cond_br_instruction->base.value.type = irb->codegen->builtin_types.entry_unreachable;
    cond_br_instruction->base.value.special = ConstValSpecialStatic;
    cond_br_instruction->condition = condition;
    cond_br_instruction->then_block = then_block;
    cond_br_instruction->else_block = else_block;
    cond_br_instruction->is_comptime = is_comptime;

    ir_ref_instruction(condition, irb->current_basic_block);
    ir_ref_bb(then_block);
    ir_ref_bb(else_block);
    if (is_comptime != nullptr) ir_ref_instruction(is_comptime, irb->current_basic_block);

    return &cond_br_instruction->base;
}

static IrInstruction *ir_build_return(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *return_value) {
    IrInstructionReturn *return_instruction = ir_build_instruction<IrInstructionReturn>(irb, scope, source_node);
    return_instruction->base.value.type = irb->codegen->builtin_types.entry_unreachable;
    return_instruction->base.value.special = ConstValSpecialStatic;
    return_instruction->value = return_value;

    ir_ref_instruction(return_value, irb->current_basic_block);

    return &return_instruction->base;
}

static IrInstruction *ir_build_const_void(IrBuilder *irb, Scope *scope, AstNode *source_node) {
    IrInstructionConst *const_instruction = ir_build_instruction<IrInstructionConst>(irb, scope, source_node);
    const_instruction->base.value.type = irb->codegen->builtin_types.entry_void;
    const_instruction->base.value.special = ConstValSpecialStatic;
    return &const_instruction->base;
}

static IrInstruction *ir_build_const_undefined(IrBuilder *irb, Scope *scope, AstNode *source_node) {
    IrInstructionConst *const_instruction = ir_build_instruction<IrInstructionConst>(irb, scope, source_node);
    const_instruction->base.value.special = ConstValSpecialUndef;
    const_instruction->base.value.type = irb->codegen->builtin_types.entry_undef;
    return &const_instruction->base;
}

static IrInstruction *ir_build_const_uint(IrBuilder *irb, Scope *scope, AstNode *source_node, uint64_t value) {
    IrInstructionConst *const_instruction = ir_build_instruction<IrInstructionConst>(irb, scope, source_node);
    const_instruction->base.value.type = irb->codegen->builtin_types.entry_num_lit_int;
    const_instruction->base.value.special = ConstValSpecialStatic;
    bigint_init_unsigned(&const_instruction->base.value.data.x_bigint, value);
    return &const_instruction->base;
}

static IrInstruction *ir_build_const_bigint(IrBuilder *irb, Scope *scope, AstNode *source_node, BigInt *bigint) {
    IrInstructionConst *const_instruction = ir_build_instruction<IrInstructionConst>(irb, scope, source_node);
    const_instruction->base.value.type = irb->codegen->builtin_types.entry_num_lit_int;
    const_instruction->base.value.special = ConstValSpecialStatic;
    bigint_init_bigint(&const_instruction->base.value.data.x_bigint, bigint);
    return &const_instruction->base;
}

static IrInstruction *ir_build_const_bigfloat(IrBuilder *irb, Scope *scope, AstNode *source_node, BigFloat *bigfloat) {
    IrInstructionConst *const_instruction = ir_build_instruction<IrInstructionConst>(irb, scope, source_node);
    const_instruction->base.value.type = irb->codegen->builtin_types.entry_num_lit_float;
    const_instruction->base.value.special = ConstValSpecialStatic;
    bigfloat_init_bigfloat(&const_instruction->base.value.data.x_bigfloat, bigfloat);
    return &const_instruction->base;
}

static IrInstruction *ir_build_const_null(IrBuilder *irb, Scope *scope, AstNode *source_node) {
    IrInstructionConst *const_instruction = ir_build_instruction<IrInstructionConst>(irb, scope, source_node);
    const_instruction->base.value.type = irb->codegen->builtin_types.entry_null;
    const_instruction->base.value.special = ConstValSpecialStatic;
    return &const_instruction->base;
}

static IrInstruction *ir_build_const_usize(IrBuilder *irb, Scope *scope, AstNode *source_node, uint64_t value) {
    IrInstructionConst *const_instruction = ir_build_instruction<IrInstructionConst>(irb, scope, source_node);
    const_instruction->base.value.type = irb->codegen->builtin_types.entry_usize;
    const_instruction->base.value.special = ConstValSpecialStatic;
    bigint_init_unsigned(&const_instruction->base.value.data.x_bigint, value);
    return &const_instruction->base;
}

static IrInstruction *ir_build_const_u8(IrBuilder *irb, Scope *scope, AstNode *source_node, uint8_t value) {
    IrInstructionConst *const_instruction = ir_build_instruction<IrInstructionConst>(irb, scope, source_node);
    const_instruction->base.value.type = irb->codegen->builtin_types.entry_u8;
    const_instruction->base.value.special = ConstValSpecialStatic;
    bigint_init_unsigned(&const_instruction->base.value.data.x_bigint, value);
    return &const_instruction->base;
}

static IrInstruction *ir_create_const_type(IrBuilder *irb, Scope *scope, AstNode *source_node,
        ZigType *type_entry)
{
    IrInstructionConst *const_instruction = ir_create_instruction<IrInstructionConst>(irb, scope, source_node);
    const_instruction->base.value.type = irb->codegen->builtin_types.entry_type;
    const_instruction->base.value.special = ConstValSpecialStatic;
    const_instruction->base.value.data.x_type = type_entry;
    return &const_instruction->base;
}

static IrInstruction *ir_build_const_type(IrBuilder *irb, Scope *scope, AstNode *source_node,
        ZigType *type_entry)
{
    IrInstruction *instruction = ir_create_const_type(irb, scope, source_node, type_entry);
    ir_instruction_append(irb->current_basic_block, instruction);
    return instruction;
}

static IrInstruction *ir_create_const_fn(IrBuilder *irb, Scope *scope, AstNode *source_node, ZigFn *fn_entry) {
    IrInstructionConst *const_instruction = ir_create_instruction<IrInstructionConst>(irb, scope, source_node);
    const_instruction->base.value.type = fn_entry->type_entry;
    const_instruction->base.value.special = ConstValSpecialStatic;
    const_instruction->base.value.data.x_ptr.data.fn.fn_entry = fn_entry;
    const_instruction->base.value.data.x_ptr.mut = ConstPtrMutComptimeConst;
    const_instruction->base.value.data.x_ptr.special = ConstPtrSpecialFunction;
    return &const_instruction->base;
}

static IrInstruction *ir_build_const_import(IrBuilder *irb, Scope *scope, AstNode *source_node, ImportTableEntry *import) {
    IrInstructionConst *const_instruction = ir_build_instruction<IrInstructionConst>(irb, scope, source_node);
    const_instruction->base.value.type = irb->codegen->builtin_types.entry_namespace;
    const_instruction->base.value.special = ConstValSpecialStatic;
    const_instruction->base.value.data.x_import = import;
    return &const_instruction->base;
}

static IrInstruction *ir_build_const_bool(IrBuilder *irb, Scope *scope, AstNode *source_node, bool value) {
    IrInstructionConst *const_instruction = ir_build_instruction<IrInstructionConst>(irb, scope, source_node);
    const_instruction->base.value.type = irb->codegen->builtin_types.entry_bool;
    const_instruction->base.value.special = ConstValSpecialStatic;
    const_instruction->base.value.data.x_bool = value;
    return &const_instruction->base;
}

static IrInstruction *ir_build_const_bound_fn(IrBuilder *irb, Scope *scope, AstNode *source_node,
    ZigFn *fn_entry, IrInstruction *first_arg)
{
    IrInstructionConst *const_instruction = ir_build_instruction<IrInstructionConst>(irb, scope, source_node);
    const_instruction->base.value.type = get_bound_fn_type(irb->codegen, fn_entry);
    const_instruction->base.value.special = ConstValSpecialStatic;
    const_instruction->base.value.data.x_bound_fn.fn = fn_entry;
    const_instruction->base.value.data.x_bound_fn.first_arg = first_arg;
    return &const_instruction->base;
}

static IrInstruction *ir_create_const_str_lit(IrBuilder *irb, Scope *scope, AstNode *source_node, Buf *str) {
    IrInstructionConst *const_instruction = ir_create_instruction<IrInstructionConst>(irb, scope, source_node);
    init_const_str_lit(irb->codegen, &const_instruction->base.value, str);

    return &const_instruction->base;
}
static IrInstruction *ir_build_const_str_lit(IrBuilder *irb, Scope *scope, AstNode *source_node, Buf *str) {
    IrInstruction *instruction = ir_create_const_str_lit(irb, scope, source_node, str);
    ir_instruction_append(irb->current_basic_block, instruction);
    return instruction;
}

static IrInstruction *ir_build_const_c_str_lit(IrBuilder *irb, Scope *scope, AstNode *source_node, Buf *str) {
    IrInstructionConst *const_instruction = ir_build_instruction<IrInstructionConst>(irb, scope, source_node);
    init_const_c_str_lit(irb->codegen, &const_instruction->base.value, str);
    return &const_instruction->base;
}

static IrInstruction *ir_build_bin_op(IrBuilder *irb, Scope *scope, AstNode *source_node, IrBinOp op_id,
        IrInstruction *op1, IrInstruction *op2, bool safety_check_on)
{
    IrInstructionBinOp *bin_op_instruction = ir_build_instruction<IrInstructionBinOp>(irb, scope, source_node);
    bin_op_instruction->op_id = op_id;
    bin_op_instruction->op1 = op1;
    bin_op_instruction->op2 = op2;
    bin_op_instruction->safety_check_on = safety_check_on;

    ir_ref_instruction(op1, irb->current_basic_block);
    ir_ref_instruction(op2, irb->current_basic_block);

    return &bin_op_instruction->base;
}

static IrInstruction *ir_build_var_ptr_x(IrBuilder *irb, Scope *scope, AstNode *source_node, ZigVar *var,
        ScopeFnDef *crossed_fndef_scope)
{
    IrInstructionVarPtr *instruction = ir_build_instruction<IrInstructionVarPtr>(irb, scope, source_node);
    instruction->var = var;
    instruction->crossed_fndef_scope = crossed_fndef_scope;

    ir_ref_var(var);

    return &instruction->base;
}

static IrInstruction *ir_build_var_ptr(IrBuilder *irb, Scope *scope, AstNode *source_node, ZigVar *var) {
    return ir_build_var_ptr_x(irb, scope, source_node, var, nullptr);
}

static IrInstruction *ir_build_elem_ptr(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *array_ptr,
        IrInstruction *elem_index, bool safety_check_on, PtrLen ptr_len)
{
    IrInstructionElemPtr *instruction = ir_build_instruction<IrInstructionElemPtr>(irb, scope, source_node);
    instruction->array_ptr = array_ptr;
    instruction->elem_index = elem_index;
    instruction->safety_check_on = safety_check_on;
    instruction->ptr_len = ptr_len;

    ir_ref_instruction(array_ptr, irb->current_basic_block);
    ir_ref_instruction(elem_index, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_field_ptr_instruction(IrBuilder *irb, Scope *scope, AstNode *source_node,
    IrInstruction *container_ptr, IrInstruction *field_name_expr)
{
    IrInstructionFieldPtr *instruction = ir_build_instruction<IrInstructionFieldPtr>(irb, scope, source_node);
    instruction->container_ptr = container_ptr;
    instruction->field_name_buffer = nullptr;
    instruction->field_name_expr = field_name_expr;

    ir_ref_instruction(container_ptr, irb->current_basic_block);
    ir_ref_instruction(field_name_expr, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_field_ptr(IrBuilder *irb, Scope *scope, AstNode *source_node,
    IrInstruction *container_ptr, Buf *field_name)
{
    IrInstructionFieldPtr *instruction = ir_build_instruction<IrInstructionFieldPtr>(irb, scope, source_node);
    instruction->container_ptr = container_ptr;
    instruction->field_name_buffer = field_name;
    instruction->field_name_expr = nullptr;

    ir_ref_instruction(container_ptr, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_struct_field_ptr(IrBuilder *irb, Scope *scope, AstNode *source_node,
    IrInstruction *struct_ptr, TypeStructField *field)
{
    IrInstructionStructFieldPtr *instruction = ir_build_instruction<IrInstructionStructFieldPtr>(irb, scope, source_node);
    instruction->struct_ptr = struct_ptr;
    instruction->field = field;

    ir_ref_instruction(struct_ptr, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_union_field_ptr(IrBuilder *irb, Scope *scope, AstNode *source_node,
    IrInstruction *union_ptr, TypeUnionField *field)
{
    IrInstructionUnionFieldPtr *instruction = ir_build_instruction<IrInstructionUnionFieldPtr>(irb, scope, source_node);
    instruction->union_ptr = union_ptr;
    instruction->field = field;

    ir_ref_instruction(union_ptr, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_call(IrBuilder *irb, Scope *scope, AstNode *source_node,
        ZigFn *fn_entry, IrInstruction *fn_ref, size_t arg_count, IrInstruction **args,
        bool is_comptime, FnInline fn_inline, bool is_async, IrInstruction *async_allocator,
        IrInstruction *new_stack)
{
    IrInstructionCall *call_instruction = ir_build_instruction<IrInstructionCall>(irb, scope, source_node);
    call_instruction->fn_entry = fn_entry;
    call_instruction->fn_ref = fn_ref;
    call_instruction->is_comptime = is_comptime;
    call_instruction->fn_inline = fn_inline;
    call_instruction->args = args;
    call_instruction->arg_count = arg_count;
    call_instruction->is_async = is_async;
    call_instruction->async_allocator = async_allocator;
    call_instruction->new_stack = new_stack;

    if (fn_ref != nullptr) ir_ref_instruction(fn_ref, irb->current_basic_block);
    for (size_t i = 0; i < arg_count; i += 1)
        ir_ref_instruction(args[i], irb->current_basic_block);
    if (async_allocator != nullptr) ir_ref_instruction(async_allocator, irb->current_basic_block);
    if (new_stack != nullptr) ir_ref_instruction(new_stack, irb->current_basic_block);

    return &call_instruction->base;
}

static IrInstruction *ir_build_phi(IrBuilder *irb, Scope *scope, AstNode *source_node,
        size_t incoming_count, IrBasicBlock **incoming_blocks, IrInstruction **incoming_values)
{
    assert(incoming_count != 0);
    assert(incoming_count != SIZE_MAX);

    IrInstructionPhi *phi_instruction = ir_build_instruction<IrInstructionPhi>(irb, scope, source_node);
    phi_instruction->incoming_count = incoming_count;
    phi_instruction->incoming_blocks = incoming_blocks;
    phi_instruction->incoming_values = incoming_values;

    for (size_t i = 0; i < incoming_count; i += 1) {
        ir_ref_bb(incoming_blocks[i]);
        ir_ref_instruction(incoming_values[i], irb->current_basic_block);
    }

    return &phi_instruction->base;
}

static IrInstruction *ir_create_br(IrBuilder *irb, Scope *scope, AstNode *source_node,
        IrBasicBlock *dest_block, IrInstruction *is_comptime)
{
    IrInstructionBr *br_instruction = ir_create_instruction<IrInstructionBr>(irb, scope, source_node);
    br_instruction->base.value.type = irb->codegen->builtin_types.entry_unreachable;
    br_instruction->base.value.special = ConstValSpecialStatic;
    br_instruction->dest_block = dest_block;
    br_instruction->is_comptime = is_comptime;

    ir_ref_bb(dest_block);
    if (is_comptime) ir_ref_instruction(is_comptime, irb->current_basic_block);

    return &br_instruction->base;
}

static IrInstruction *ir_build_br(IrBuilder *irb, Scope *scope, AstNode *source_node,
        IrBasicBlock *dest_block, IrInstruction *is_comptime)
{
    IrInstruction *instruction = ir_create_br(irb, scope, source_node, dest_block, is_comptime);
    ir_instruction_append(irb->current_basic_block, instruction);
    return instruction;
}

static IrInstruction *ir_build_ptr_type(IrBuilder *irb, Scope *scope, AstNode *source_node,
        IrInstruction *child_type, bool is_const, bool is_volatile, PtrLen ptr_len,
        IrInstruction *align_value, uint32_t bit_offset_start, uint32_t host_int_bytes)
{
    IrInstructionPtrType *ptr_type_of_instruction = ir_build_instruction<IrInstructionPtrType>(irb, scope, source_node);
    ptr_type_of_instruction->align_value = align_value;
    ptr_type_of_instruction->child_type = child_type;
    ptr_type_of_instruction->is_const = is_const;
    ptr_type_of_instruction->is_volatile = is_volatile;
    ptr_type_of_instruction->ptr_len = ptr_len;
    ptr_type_of_instruction->bit_offset_start = bit_offset_start;
    ptr_type_of_instruction->host_int_bytes = host_int_bytes;

    if (align_value) ir_ref_instruction(align_value, irb->current_basic_block);
    ir_ref_instruction(child_type, irb->current_basic_block);

    return &ptr_type_of_instruction->base;
}

static IrInstruction *ir_build_un_op_lval(IrBuilder *irb, Scope *scope, AstNode *source_node, IrUnOp op_id,
        IrInstruction *value, LVal lval)
{
    IrInstructionUnOp *instruction = ir_build_instruction<IrInstructionUnOp>(irb, scope, source_node);
    instruction->op_id = op_id;
    instruction->value = value;
    instruction->lval = lval;

    ir_ref_instruction(value, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_un_op(IrBuilder *irb, Scope *scope, AstNode *source_node, IrUnOp op_id,
        IrInstruction *value)
{
    return ir_build_un_op_lval(irb, scope, source_node, op_id, value, LValNone);
}

static IrInstruction *ir_build_container_init_list(IrBuilder *irb, Scope *scope, AstNode *source_node,
        IrInstruction *container_type, size_t item_count, IrInstruction **items)
{
    IrInstructionContainerInitList *container_init_list_instruction =
        ir_build_instruction<IrInstructionContainerInitList>(irb, scope, source_node);
    container_init_list_instruction->container_type = container_type;
    container_init_list_instruction->item_count = item_count;
    container_init_list_instruction->items = items;

    if (container_type != nullptr) ir_ref_instruction(container_type, irb->current_basic_block);
    for (size_t i = 0; i < item_count; i += 1) {
        ir_ref_instruction(items[i], irb->current_basic_block);
    }

    return &container_init_list_instruction->base;
}

static IrInstruction *ir_build_container_init_fields(IrBuilder *irb, Scope *scope, AstNode *source_node,
        IrInstruction *container_type, size_t field_count, IrInstructionContainerInitFieldsField *fields)
{
    IrInstructionContainerInitFields *container_init_fields_instruction =
        ir_build_instruction<IrInstructionContainerInitFields>(irb, scope, source_node);
    container_init_fields_instruction->container_type = container_type;
    container_init_fields_instruction->field_count = field_count;
    container_init_fields_instruction->fields = fields;

    ir_ref_instruction(container_type, irb->current_basic_block);
    for (size_t i = 0; i < field_count; i += 1) {
        ir_ref_instruction(fields[i].value, irb->current_basic_block);
    }

    return &container_init_fields_instruction->base;
}

static IrInstruction *ir_build_struct_init(IrBuilder *irb, Scope *scope, AstNode *source_node,
        ZigType *struct_type, size_t field_count, IrInstructionStructInitField *fields)
{
    IrInstructionStructInit *struct_init_instruction = ir_build_instruction<IrInstructionStructInit>(irb, scope, source_node);
    struct_init_instruction->struct_type = struct_type;
    struct_init_instruction->field_count = field_count;
    struct_init_instruction->fields = fields;

    for (size_t i = 0; i < field_count; i += 1)
        ir_ref_instruction(fields[i].value, irb->current_basic_block);

    return &struct_init_instruction->base;
}

static IrInstruction *ir_build_union_init(IrBuilder *irb, Scope *scope, AstNode *source_node,
        ZigType *union_type, TypeUnionField *field, IrInstruction *init_value)
{
    IrInstructionUnionInit *union_init_instruction = ir_build_instruction<IrInstructionUnionInit>(irb, scope, source_node);
    union_init_instruction->union_type = union_type;
    union_init_instruction->field = field;
    union_init_instruction->init_value = init_value;

    ir_ref_instruction(init_value, irb->current_basic_block);

    return &union_init_instruction->base;
}

static IrInstruction *ir_build_unreachable(IrBuilder *irb, Scope *scope, AstNode *source_node) {
    IrInstructionUnreachable *unreachable_instruction =
        ir_build_instruction<IrInstructionUnreachable>(irb, scope, source_node);
    unreachable_instruction->base.value.special = ConstValSpecialStatic;
    unreachable_instruction->base.value.type = irb->codegen->builtin_types.entry_unreachable;
    return &unreachable_instruction->base;
}

static IrInstruction *ir_build_store_ptr(IrBuilder *irb, Scope *scope, AstNode *source_node,
        IrInstruction *ptr, IrInstruction *value)
{
    IrInstructionStorePtr *instruction = ir_build_instruction<IrInstructionStorePtr>(irb, scope, source_node);
    instruction->base.value.special = ConstValSpecialStatic;
    instruction->base.value.type = irb->codegen->builtin_types.entry_void;
    instruction->ptr = ptr;
    instruction->value = value;

    ir_ref_instruction(ptr, irb->current_basic_block);
    ir_ref_instruction(value, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_var_decl_src(IrBuilder *irb, Scope *scope, AstNode *source_node,
        ZigVar *var, IrInstruction *var_type, IrInstruction *align_value, IrInstruction *init_value)
{
    IrInstructionDeclVarSrc *decl_var_instruction = ir_build_instruction<IrInstructionDeclVarSrc>(irb, scope, source_node);
    decl_var_instruction->base.value.special = ConstValSpecialStatic;
    decl_var_instruction->base.value.type = irb->codegen->builtin_types.entry_void;
    decl_var_instruction->var = var;
    decl_var_instruction->var_type = var_type;
    decl_var_instruction->align_value = align_value;
    decl_var_instruction->init_value = init_value;

    if (var_type != nullptr) ir_ref_instruction(var_type, irb->current_basic_block);
    if (align_value != nullptr) ir_ref_instruction(align_value, irb->current_basic_block);
    ir_ref_instruction(init_value, irb->current_basic_block);

    return &decl_var_instruction->base;
}

static IrInstruction *ir_build_var_decl_gen(IrAnalyze *ira, IrInstruction *source_instruction,
        ZigVar *var, IrInstruction *init_value)
{
    IrInstructionDeclVarGen *decl_var_instruction = ir_build_instruction<IrInstructionDeclVarGen>(&ira->new_irb,
            source_instruction->scope, source_instruction->source_node);
    decl_var_instruction->base.value.special = ConstValSpecialStatic;
    decl_var_instruction->base.value.type = ira->codegen->builtin_types.entry_void;
    decl_var_instruction->var = var;
    decl_var_instruction->init_value = init_value;

    ir_ref_instruction(init_value, ira->new_irb.current_basic_block);

    return &decl_var_instruction->base;
}

static IrInstruction *ir_build_resize_slice(IrAnalyze *ira, IrInstruction *source_instruction,
        IrInstruction *operand, ZigType *ty)
{
    IrInstructionResizeSlice *instruction = ir_build_instruction<IrInstructionResizeSlice>(&ira->new_irb,
            source_instruction->scope, source_instruction->source_node);
    instruction->base.value.type = ty;
    instruction->operand = operand;

    ir_ref_instruction(operand, ira->new_irb.current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_export(IrBuilder *irb, Scope *scope, AstNode *source_node,
        IrInstruction *name, IrInstruction *target, IrInstruction *linkage)
{
    IrInstructionExport *export_instruction = ir_build_instruction<IrInstructionExport>(
            irb, scope, source_node);
    export_instruction->base.value.special = ConstValSpecialStatic;
    export_instruction->base.value.type = irb->codegen->builtin_types.entry_void;
    export_instruction->name = name;
    export_instruction->target = target;
    export_instruction->linkage = linkage;

    ir_ref_instruction(name, irb->current_basic_block);
    ir_ref_instruction(target, irb->current_basic_block);
    if (linkage) ir_ref_instruction(linkage, irb->current_basic_block);

    return &export_instruction->base;
}

static IrInstruction *ir_build_load_ptr(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *ptr) {
    IrInstructionLoadPtr *instruction = ir_build_instruction<IrInstructionLoadPtr>(irb, scope, source_node);
    instruction->ptr = ptr;

    ir_ref_instruction(ptr, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_typeof(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *value) {
    IrInstructionTypeOf *instruction = ir_build_instruction<IrInstructionTypeOf>(irb, scope, source_node);
    instruction->value = value;

    ir_ref_instruction(value, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_to_ptr_type(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *value) {
    IrInstructionToPtrType *instruction = ir_build_instruction<IrInstructionToPtrType>(irb, scope, source_node);
    instruction->value = value;

    ir_ref_instruction(value, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_ptr_type_child(IrBuilder *irb, Scope *scope, AstNode *source_node,
    IrInstruction *value)
{
    IrInstructionPtrTypeChild *instruction = ir_build_instruction<IrInstructionPtrTypeChild>(
        irb, scope, source_node);
    instruction->value = value;

    ir_ref_instruction(value, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_set_cold(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *is_cold) {
    IrInstructionSetCold *instruction = ir_build_instruction<IrInstructionSetCold>(irb, scope, source_node);
    instruction->is_cold = is_cold;

    ir_ref_instruction(is_cold, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_set_runtime_safety(IrBuilder *irb, Scope *scope, AstNode *source_node,
        IrInstruction *safety_on)
{
    IrInstructionSetRuntimeSafety *instruction = ir_build_instruction<IrInstructionSetRuntimeSafety>(irb, scope, source_node);
    instruction->safety_on = safety_on;

    ir_ref_instruction(safety_on, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_set_float_mode(IrBuilder *irb, Scope *scope, AstNode *source_node,
        IrInstruction *mode_value)
{
    IrInstructionSetFloatMode *instruction = ir_build_instruction<IrInstructionSetFloatMode>(irb, scope, source_node);
    instruction->mode_value = mode_value;

    ir_ref_instruction(mode_value, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_array_type(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *size,
        IrInstruction *child_type)
{
    IrInstructionArrayType *instruction = ir_build_instruction<IrInstructionArrayType>(irb, scope, source_node);
    instruction->size = size;
    instruction->child_type = child_type;

    ir_ref_instruction(size, irb->current_basic_block);
    ir_ref_instruction(child_type, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_promise_type(IrBuilder *irb, Scope *scope, AstNode *source_node,
        IrInstruction *payload_type)
{
    IrInstructionPromiseType *instruction = ir_build_instruction<IrInstructionPromiseType>(irb, scope, source_node);
    instruction->payload_type = payload_type;

    if (payload_type != nullptr) ir_ref_instruction(payload_type, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_slice_type(IrBuilder *irb, Scope *scope, AstNode *source_node,
        IrInstruction *child_type, bool is_const, bool is_volatile, IrInstruction *align_value)
{
    IrInstructionSliceType *instruction = ir_build_instruction<IrInstructionSliceType>(irb, scope, source_node);
    instruction->is_const = is_const;
    instruction->is_volatile = is_volatile;
    instruction->child_type = child_type;
    instruction->align_value = align_value;

    ir_ref_instruction(child_type, irb->current_basic_block);
    if (align_value) ir_ref_instruction(align_value, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_asm(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction **input_list,
        IrInstruction **output_types, ZigVar **output_vars, size_t return_count, bool has_side_effects)
{
    IrInstructionAsm *instruction = ir_build_instruction<IrInstructionAsm>(irb, scope, source_node);
    instruction->input_list = input_list;
    instruction->output_types = output_types;
    instruction->output_vars = output_vars;
    instruction->return_count = return_count;
    instruction->has_side_effects = has_side_effects;

    assert(source_node->type == NodeTypeAsmExpr);
    for (size_t i = 0; i < source_node->data.asm_expr.output_list.length; i += 1) {
        IrInstruction *output_type = output_types[i];
        if (output_type) ir_ref_instruction(output_type, irb->current_basic_block);
    }

    for (size_t i = 0; i < source_node->data.asm_expr.input_list.length; i += 1) {
        IrInstruction *input_value = input_list[i];
        ir_ref_instruction(input_value, irb->current_basic_block);
    }

    return &instruction->base;
}

static IrInstruction *ir_build_size_of(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *type_value) {
    IrInstructionSizeOf *instruction = ir_build_instruction<IrInstructionSizeOf>(irb, scope, source_node);
    instruction->type_value = type_value;

    ir_ref_instruction(type_value, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_test_nonnull(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *value) {
    IrInstructionTestNonNull *instruction = ir_build_instruction<IrInstructionTestNonNull>(irb, scope, source_node);
    instruction->value = value;

    ir_ref_instruction(value, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_optional_unwrap_ptr(IrBuilder *irb, Scope *scope, AstNode *source_node,
        IrInstruction *base_ptr, bool safety_check_on)
{
    IrInstructionOptionalUnwrapPtr *instruction = ir_build_instruction<IrInstructionOptionalUnwrapPtr>(irb, scope, source_node);
    instruction->base_ptr = base_ptr;
    instruction->safety_check_on = safety_check_on;

    ir_ref_instruction(base_ptr, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_maybe_wrap(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *value) {
    IrInstructionOptionalWrap *instruction = ir_build_instruction<IrInstructionOptionalWrap>(irb, scope, source_node);
    instruction->value = value;

    ir_ref_instruction(value, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_err_wrap_payload(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *value) {
    IrInstructionErrWrapPayload *instruction = ir_build_instruction<IrInstructionErrWrapPayload>(irb, scope, source_node);
    instruction->value = value;

    ir_ref_instruction(value, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_err_wrap_code(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *value) {
    IrInstructionErrWrapCode *instruction = ir_build_instruction<IrInstructionErrWrapCode>(irb, scope, source_node);
    instruction->value = value;

    ir_ref_instruction(value, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_clz(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *value) {
    IrInstructionClz *instruction = ir_build_instruction<IrInstructionClz>(irb, scope, source_node);
    instruction->value = value;

    ir_ref_instruction(value, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_ctz(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *value) {
    IrInstructionCtz *instruction = ir_build_instruction<IrInstructionCtz>(irb, scope, source_node);
    instruction->value = value;

    ir_ref_instruction(value, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_pop_count(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *value) {
    IrInstructionPopCount *instruction = ir_build_instruction<IrInstructionPopCount>(irb, scope, source_node);
    instruction->value = value;

    ir_ref_instruction(value, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_switch_br(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *target_value,
        IrBasicBlock *else_block, size_t case_count, IrInstructionSwitchBrCase *cases, IrInstruction *is_comptime,
        IrInstruction *switch_prongs_void)
{
    IrInstructionSwitchBr *instruction = ir_build_instruction<IrInstructionSwitchBr>(irb, scope, source_node);
    instruction->base.value.type = irb->codegen->builtin_types.entry_unreachable;
    instruction->base.value.special = ConstValSpecialStatic;
    instruction->target_value = target_value;
    instruction->else_block = else_block;
    instruction->case_count = case_count;
    instruction->cases = cases;
    instruction->is_comptime = is_comptime;
    instruction->switch_prongs_void = switch_prongs_void;

    ir_ref_instruction(target_value, irb->current_basic_block);
    if (is_comptime) ir_ref_instruction(is_comptime, irb->current_basic_block);
    ir_ref_bb(else_block);
    if (switch_prongs_void) ir_ref_instruction(switch_prongs_void, irb->current_basic_block);

    for (size_t i = 0; i < case_count; i += 1) {
        ir_ref_instruction(cases[i].value, irb->current_basic_block);
        ir_ref_bb(cases[i].block);
    }

    return &instruction->base;
}

static IrInstruction *ir_build_switch_target(IrBuilder *irb, Scope *scope, AstNode *source_node,
        IrInstruction *target_value_ptr)
{
    IrInstructionSwitchTarget *instruction = ir_build_instruction<IrInstructionSwitchTarget>(irb, scope, source_node);
    instruction->target_value_ptr = target_value_ptr;

    ir_ref_instruction(target_value_ptr, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_switch_var(IrBuilder *irb, Scope *scope, AstNode *source_node,
        IrInstruction *target_value_ptr, IrInstruction *prong_value)
{
    IrInstructionSwitchVar *instruction = ir_build_instruction<IrInstructionSwitchVar>(irb, scope, source_node);
    instruction->target_value_ptr = target_value_ptr;
    instruction->prong_value = prong_value;

    ir_ref_instruction(target_value_ptr, irb->current_basic_block);
    ir_ref_instruction(prong_value, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_union_tag(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *value) {
    IrInstructionUnionTag *instruction = ir_build_instruction<IrInstructionUnionTag>(irb, scope, source_node);
    instruction->value = value;

    ir_ref_instruction(value, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_import(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *name) {
    IrInstructionImport *instruction = ir_build_instruction<IrInstructionImport>(irb, scope, source_node);
    instruction->name = name;

    ir_ref_instruction(name, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_ref(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *value,
        bool is_const, bool is_volatile)
{
    IrInstructionRef *instruction = ir_build_instruction<IrInstructionRef>(irb, scope, source_node);
    instruction->value = value;
    instruction->is_const = is_const;
    instruction->is_volatile = is_volatile;

    ir_ref_instruction(value, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_compile_err(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *msg) {
    IrInstructionCompileErr *instruction = ir_build_instruction<IrInstructionCompileErr>(irb, scope, source_node);
    instruction->msg = msg;

    ir_ref_instruction(msg, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_compile_log(IrBuilder *irb, Scope *scope, AstNode *source_node,
        size_t msg_count, IrInstruction **msg_list)
{
    IrInstructionCompileLog *instruction = ir_build_instruction<IrInstructionCompileLog>(irb, scope, source_node);
    instruction->msg_count = msg_count;
    instruction->msg_list = msg_list;

    for (size_t i = 0; i < msg_count; i += 1) {
        ir_ref_instruction(msg_list[i], irb->current_basic_block);
    }

    return &instruction->base;
}

static IrInstruction *ir_build_err_name(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *value) {
    IrInstructionErrName *instruction = ir_build_instruction<IrInstructionErrName>(irb, scope, source_node);
    instruction->value = value;

    ir_ref_instruction(value, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_c_import(IrBuilder *irb, Scope *scope, AstNode *source_node) {
    IrInstructionCImport *instruction = ir_build_instruction<IrInstructionCImport>(irb, scope, source_node);
    return &instruction->base;
}

static IrInstruction *ir_build_c_include(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *name) {
    IrInstructionCInclude *instruction = ir_build_instruction<IrInstructionCInclude>(irb, scope, source_node);
    instruction->name = name;

    ir_ref_instruction(name, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_c_define(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *name, IrInstruction *value) {
    IrInstructionCDefine *instruction = ir_build_instruction<IrInstructionCDefine>(irb, scope, source_node);
    instruction->name = name;
    instruction->value = value;

    ir_ref_instruction(name, irb->current_basic_block);
    ir_ref_instruction(value, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_c_undef(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *name) {
    IrInstructionCUndef *instruction = ir_build_instruction<IrInstructionCUndef>(irb, scope, source_node);
    instruction->name = name;

    ir_ref_instruction(name, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_embed_file(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *name) {
    IrInstructionEmbedFile *instruction = ir_build_instruction<IrInstructionEmbedFile>(irb, scope, source_node);
    instruction->name = name;

    ir_ref_instruction(name, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_cmpxchg_src(IrBuilder *irb, Scope *scope, AstNode *source_node,
    IrInstruction *type_value, IrInstruction *ptr, IrInstruction *cmp_value, IrInstruction *new_value,
    IrInstruction *success_order_value, IrInstruction *failure_order_value,
    bool is_weak)
{
    IrInstructionCmpxchgSrc *instruction = ir_build_instruction<IrInstructionCmpxchgSrc>(irb, scope, source_node);
    instruction->type_value = type_value;
    instruction->ptr = ptr;
    instruction->cmp_value = cmp_value;
    instruction->new_value = new_value;
    instruction->success_order_value = success_order_value;
    instruction->failure_order_value = failure_order_value;
    instruction->is_weak = is_weak;

    ir_ref_instruction(type_value, irb->current_basic_block);
    ir_ref_instruction(ptr, irb->current_basic_block);
    ir_ref_instruction(cmp_value, irb->current_basic_block);
    ir_ref_instruction(new_value, irb->current_basic_block);
    ir_ref_instruction(success_order_value, irb->current_basic_block);
    ir_ref_instruction(failure_order_value, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_cmpxchg_gen(IrAnalyze *ira, IrInstruction *source_instruction,
    IrInstruction *ptr, IrInstruction *cmp_value, IrInstruction *new_value,
    AtomicOrder success_order, AtomicOrder failure_order, bool is_weak)
{
    IrInstructionCmpxchgGen *instruction = ir_build_instruction<IrInstructionCmpxchgGen>(&ira->new_irb,
            source_instruction->scope, source_instruction->source_node);
    instruction->ptr = ptr;
    instruction->cmp_value = cmp_value;
    instruction->new_value = new_value;
    instruction->success_order = success_order;
    instruction->failure_order = failure_order;
    instruction->is_weak = is_weak;

    ir_ref_instruction(ptr, ira->new_irb.current_basic_block);
    ir_ref_instruction(cmp_value, ira->new_irb.current_basic_block);
    ir_ref_instruction(new_value, ira->new_irb.current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_fence(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *order_value, AtomicOrder order) {
    IrInstructionFence *instruction = ir_build_instruction<IrInstructionFence>(irb, scope, source_node);
    instruction->order_value = order_value;
    instruction->order = order;

    ir_ref_instruction(order_value, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_truncate(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *dest_type, IrInstruction *target) {
    IrInstructionTruncate *instruction = ir_build_instruction<IrInstructionTruncate>(irb, scope, source_node);
    instruction->dest_type = dest_type;
    instruction->target = target;

    ir_ref_instruction(dest_type, irb->current_basic_block);
    ir_ref_instruction(target, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_int_cast(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *dest_type, IrInstruction *target) {
    IrInstructionIntCast *instruction = ir_build_instruction<IrInstructionIntCast>(irb, scope, source_node);
    instruction->dest_type = dest_type;
    instruction->target = target;

    ir_ref_instruction(dest_type, irb->current_basic_block);
    ir_ref_instruction(target, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_float_cast(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *dest_type, IrInstruction *target) {
    IrInstructionFloatCast *instruction = ir_build_instruction<IrInstructionFloatCast>(irb, scope, source_node);
    instruction->dest_type = dest_type;
    instruction->target = target;

    ir_ref_instruction(dest_type, irb->current_basic_block);
    ir_ref_instruction(target, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_err_set_cast(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *dest_type, IrInstruction *target) {
    IrInstructionErrSetCast *instruction = ir_build_instruction<IrInstructionErrSetCast>(irb, scope, source_node);
    instruction->dest_type = dest_type;
    instruction->target = target;

    ir_ref_instruction(dest_type, irb->current_basic_block);
    ir_ref_instruction(target, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_to_bytes(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *target) {
    IrInstructionToBytes *instruction = ir_build_instruction<IrInstructionToBytes>(irb, scope, source_node);
    instruction->target = target;

    ir_ref_instruction(target, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_from_bytes(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *dest_child_type, IrInstruction *target) {
    IrInstructionFromBytes *instruction = ir_build_instruction<IrInstructionFromBytes>(irb, scope, source_node);
    instruction->dest_child_type = dest_child_type;
    instruction->target = target;

    ir_ref_instruction(dest_child_type, irb->current_basic_block);
    ir_ref_instruction(target, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_int_to_float(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *dest_type, IrInstruction *target) {
    IrInstructionIntToFloat *instruction = ir_build_instruction<IrInstructionIntToFloat>(irb, scope, source_node);
    instruction->dest_type = dest_type;
    instruction->target = target;

    ir_ref_instruction(dest_type, irb->current_basic_block);
    ir_ref_instruction(target, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_float_to_int(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *dest_type, IrInstruction *target) {
    IrInstructionFloatToInt *instruction = ir_build_instruction<IrInstructionFloatToInt>(irb, scope, source_node);
    instruction->dest_type = dest_type;
    instruction->target = target;

    ir_ref_instruction(dest_type, irb->current_basic_block);
    ir_ref_instruction(target, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_bool_to_int(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *target) {
    IrInstructionBoolToInt *instruction = ir_build_instruction<IrInstructionBoolToInt>(irb, scope, source_node);
    instruction->target = target;

    ir_ref_instruction(target, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_int_type(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *is_signed, IrInstruction *bit_count) {
    IrInstructionIntType *instruction = ir_build_instruction<IrInstructionIntType>(irb, scope, source_node);
    instruction->is_signed = is_signed;
    instruction->bit_count = bit_count;

    ir_ref_instruction(is_signed, irb->current_basic_block);
    ir_ref_instruction(bit_count, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_vector_type(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *len,
        IrInstruction *elem_type)
{
    IrInstructionVectorType *instruction = ir_build_instruction<IrInstructionVectorType>(irb, scope, source_node);
    instruction->len = len;
    instruction->elem_type = elem_type;

    ir_ref_instruction(len, irb->current_basic_block);
    ir_ref_instruction(elem_type, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_bool_not(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *value) {
    IrInstructionBoolNot *instruction = ir_build_instruction<IrInstructionBoolNot>(irb, scope, source_node);
    instruction->value = value;

    ir_ref_instruction(value, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_memset(IrBuilder *irb, Scope *scope, AstNode *source_node,
    IrInstruction *dest_ptr, IrInstruction *byte, IrInstruction *count)
{
    IrInstructionMemset *instruction = ir_build_instruction<IrInstructionMemset>(irb, scope, source_node);
    instruction->dest_ptr = dest_ptr;
    instruction->byte = byte;
    instruction->count = count;

    ir_ref_instruction(dest_ptr, irb->current_basic_block);
    ir_ref_instruction(byte, irb->current_basic_block);
    ir_ref_instruction(count, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_memcpy(IrBuilder *irb, Scope *scope, AstNode *source_node,
    IrInstruction *dest_ptr, IrInstruction *src_ptr, IrInstruction *count)
{
    IrInstructionMemcpy *instruction = ir_build_instruction<IrInstructionMemcpy>(irb, scope, source_node);
    instruction->dest_ptr = dest_ptr;
    instruction->src_ptr = src_ptr;
    instruction->count = count;

    ir_ref_instruction(dest_ptr, irb->current_basic_block);
    ir_ref_instruction(src_ptr, irb->current_basic_block);
    ir_ref_instruction(count, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_slice(IrBuilder *irb, Scope *scope, AstNode *source_node,
    IrInstruction *ptr, IrInstruction *start, IrInstruction *end, bool safety_check_on)
{
    IrInstructionSlice *instruction = ir_build_instruction<IrInstructionSlice>(irb, scope, source_node);
    instruction->ptr = ptr;
    instruction->start = start;
    instruction->end = end;
    instruction->safety_check_on = safety_check_on;

    ir_ref_instruction(ptr, irb->current_basic_block);
    ir_ref_instruction(start, irb->current_basic_block);
    if (end) ir_ref_instruction(end, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_member_count(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *container) {
    IrInstructionMemberCount *instruction = ir_build_instruction<IrInstructionMemberCount>(irb, scope, source_node);
    instruction->container = container;

    ir_ref_instruction(container, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_member_type(IrBuilder *irb, Scope *scope, AstNode *source_node,
        IrInstruction *container_type, IrInstruction *member_index)
{
    IrInstructionMemberType *instruction = ir_build_instruction<IrInstructionMemberType>(irb, scope, source_node);
    instruction->container_type = container_type;
    instruction->member_index = member_index;

    ir_ref_instruction(container_type, irb->current_basic_block);
    ir_ref_instruction(member_index, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_member_name(IrBuilder *irb, Scope *scope, AstNode *source_node,
        IrInstruction *container_type, IrInstruction *member_index)
{
    IrInstructionMemberName *instruction = ir_build_instruction<IrInstructionMemberName>(irb, scope, source_node);
    instruction->container_type = container_type;
    instruction->member_index = member_index;

    ir_ref_instruction(container_type, irb->current_basic_block);
    ir_ref_instruction(member_index, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_breakpoint(IrBuilder *irb, Scope *scope, AstNode *source_node) {
    IrInstructionBreakpoint *instruction = ir_build_instruction<IrInstructionBreakpoint>(irb, scope, source_node);
    return &instruction->base;
}

static IrInstruction *ir_build_return_address(IrBuilder *irb, Scope *scope, AstNode *source_node) {
    IrInstructionReturnAddress *instruction = ir_build_instruction<IrInstructionReturnAddress>(irb, scope, source_node);
    return &instruction->base;
}

static IrInstruction *ir_build_frame_address(IrBuilder *irb, Scope *scope, AstNode *source_node) {
    IrInstructionFrameAddress *instruction = ir_build_instruction<IrInstructionFrameAddress>(irb, scope, source_node);
    return &instruction->base;
}

static IrInstruction *ir_build_handle(IrBuilder *irb, Scope *scope, AstNode *source_node) {
    IrInstructionHandle *instruction = ir_build_instruction<IrInstructionHandle>(irb, scope, source_node);
    return &instruction->base;
}

static IrInstruction *ir_build_overflow_op(IrBuilder *irb, Scope *scope, AstNode *source_node,
        IrOverflowOp op, IrInstruction *type_value, IrInstruction *op1, IrInstruction *op2,
        IrInstruction *result_ptr, ZigType *result_ptr_type)
{
    IrInstructionOverflowOp *instruction = ir_build_instruction<IrInstructionOverflowOp>(irb, scope, source_node);
    instruction->op = op;
    instruction->type_value = type_value;
    instruction->op1 = op1;
    instruction->op2 = op2;
    instruction->result_ptr = result_ptr;
    instruction->result_ptr_type = result_ptr_type;

    ir_ref_instruction(type_value, irb->current_basic_block);
    ir_ref_instruction(op1, irb->current_basic_block);
    ir_ref_instruction(op2, irb->current_basic_block);
    ir_ref_instruction(result_ptr, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_align_of(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *type_value) {
    IrInstructionAlignOf *instruction = ir_build_instruction<IrInstructionAlignOf>(irb, scope, source_node);
    instruction->type_value = type_value;

    ir_ref_instruction(type_value, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_test_err(IrBuilder *irb, Scope *scope, AstNode *source_node,
    IrInstruction *value)
{
    IrInstructionTestErr *instruction = ir_build_instruction<IrInstructionTestErr>(irb, scope, source_node);
    instruction->value = value;

    ir_ref_instruction(value, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_unwrap_err_code(IrBuilder *irb, Scope *scope, AstNode *source_node,
    IrInstruction *err_union)
{
    IrInstructionUnwrapErrCode *instruction = ir_build_instruction<IrInstructionUnwrapErrCode>(irb, scope, source_node);
    instruction->err_union = err_union;

    ir_ref_instruction(err_union, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_unwrap_err_payload(IrBuilder *irb, Scope *scope, AstNode *source_node,
    IrInstruction *value, bool safety_check_on)
{
    IrInstructionUnwrapErrPayload *instruction = ir_build_instruction<IrInstructionUnwrapErrPayload>(irb, scope, source_node);
    instruction->value = value;
    instruction->safety_check_on = safety_check_on;

    ir_ref_instruction(value, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_fn_proto(IrBuilder *irb, Scope *scope, AstNode *source_node,
    IrInstruction **param_types, IrInstruction *align_value, IrInstruction *return_type,
    IrInstruction *async_allocator_type_value, bool is_var_args)
{
    IrInstructionFnProto *instruction = ir_build_instruction<IrInstructionFnProto>(irb, scope, source_node);
    instruction->param_types = param_types;
    instruction->align_value = align_value;
    instruction->return_type = return_type;
    instruction->async_allocator_type_value = async_allocator_type_value;
    instruction->is_var_args = is_var_args;

    assert(source_node->type == NodeTypeFnProto);
    size_t param_count = source_node->data.fn_proto.params.length;
    if (is_var_args) param_count -= 1;
    for (size_t i = 0; i < param_count; i += 1) {
        if (param_types[i] != nullptr) ir_ref_instruction(param_types[i], irb->current_basic_block);
    }
    if (align_value != nullptr) ir_ref_instruction(align_value, irb->current_basic_block);
    if (async_allocator_type_value != nullptr) ir_ref_instruction(async_allocator_type_value, irb->current_basic_block);
    ir_ref_instruction(return_type, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_test_comptime(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *value) {
    IrInstructionTestComptime *instruction = ir_build_instruction<IrInstructionTestComptime>(irb, scope, source_node);
    instruction->value = value;

    ir_ref_instruction(value, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_ptr_cast_src(IrBuilder *irb, Scope *scope, AstNode *source_node,
        IrInstruction *dest_type, IrInstruction *ptr, bool safety_check_on)
{
    IrInstructionPtrCastSrc *instruction = ir_build_instruction<IrInstructionPtrCastSrc>(
            irb, scope, source_node);
    instruction->dest_type = dest_type;
    instruction->ptr = ptr;
    instruction->safety_check_on = safety_check_on;

    ir_ref_instruction(dest_type, irb->current_basic_block);
    ir_ref_instruction(ptr, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_ptr_cast_gen(IrAnalyze *ira, IrInstruction *source_instruction,
        ZigType *ptr_type, IrInstruction *ptr, bool safety_check_on)
{
    IrInstructionPtrCastGen *instruction = ir_build_instruction<IrInstructionPtrCastGen>(
            &ira->new_irb, source_instruction->scope, source_instruction->source_node);
    instruction->base.value.type = ptr_type;
    instruction->ptr = ptr;
    instruction->safety_check_on = safety_check_on;

    ir_ref_instruction(ptr, ira->new_irb.current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_load_ptr_gen(IrAnalyze *ira, IrInstruction *source_instruction,
        IrInstruction *ptr, ZigType *ty)
{
    IrInstructionLoadPtrGen *instruction = ir_build_instruction<IrInstructionLoadPtrGen>(
            &ira->new_irb, source_instruction->scope, source_instruction->source_node);
    instruction->base.value.type = ty;
    instruction->ptr = ptr;

    ir_ref_instruction(ptr, ira->new_irb.current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_bit_cast(IrBuilder *irb, Scope *scope, AstNode *source_node,
        IrInstruction *dest_type, IrInstruction *value)
{
    IrInstructionBitCast *instruction = ir_build_instruction<IrInstructionBitCast>(
            irb, scope, source_node);
    instruction->dest_type = dest_type;
    instruction->value = value;

    ir_ref_instruction(dest_type, irb->current_basic_block);
    ir_ref_instruction(value, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_bit_cast_gen(IrAnalyze *ira, IrInstruction *source_instruction,
        IrInstruction *operand, ZigType *ty)
{
    IrInstructionBitCastGen *instruction = ir_build_instruction<IrInstructionBitCastGen>(
            &ira->new_irb, source_instruction->scope, source_instruction->source_node);
    instruction->base.value.type = ty;
    instruction->operand = operand;

    ir_ref_instruction(operand, ira->new_irb.current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_widen_or_shorten(IrBuilder *irb, Scope *scope, AstNode *source_node,
        IrInstruction *target)
{
    IrInstructionWidenOrShorten *instruction = ir_build_instruction<IrInstructionWidenOrShorten>(
            irb, scope, source_node);
    instruction->target = target;

    ir_ref_instruction(target, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_int_to_ptr(IrBuilder *irb, Scope *scope, AstNode *source_node,
        IrInstruction *dest_type, IrInstruction *target)
{
    IrInstructionIntToPtr *instruction = ir_build_instruction<IrInstructionIntToPtr>(
            irb, scope, source_node);
    instruction->dest_type = dest_type;
    instruction->target = target;

    if (dest_type) ir_ref_instruction(dest_type, irb->current_basic_block);
    ir_ref_instruction(target, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_ptr_to_int(IrBuilder *irb, Scope *scope, AstNode *source_node,
        IrInstruction *target)
{
    IrInstructionPtrToInt *instruction = ir_build_instruction<IrInstructionPtrToInt>(
            irb, scope, source_node);
    instruction->target = target;

    ir_ref_instruction(target, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_int_to_enum(IrBuilder *irb, Scope *scope, AstNode *source_node,
        IrInstruction *dest_type, IrInstruction *target)
{
    IrInstructionIntToEnum *instruction = ir_build_instruction<IrInstructionIntToEnum>(
            irb, scope, source_node);
    instruction->dest_type = dest_type;
    instruction->target = target;

    if (dest_type) ir_ref_instruction(dest_type, irb->current_basic_block);
    ir_ref_instruction(target, irb->current_basic_block);

    return &instruction->base;
}



static IrInstruction *ir_build_enum_to_int(IrBuilder *irb, Scope *scope, AstNode *source_node,
        IrInstruction *target)
{
    IrInstructionEnumToInt *instruction = ir_build_instruction<IrInstructionEnumToInt>(
            irb, scope, source_node);
    instruction->target = target;

    ir_ref_instruction(target, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_int_to_err(IrBuilder *irb, Scope *scope, AstNode *source_node,
        IrInstruction *target)
{
    IrInstructionIntToErr *instruction = ir_build_instruction<IrInstructionIntToErr>(
            irb, scope, source_node);
    instruction->target = target;

    ir_ref_instruction(target, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_err_to_int(IrBuilder *irb, Scope *scope, AstNode *source_node,
        IrInstruction *target)
{
    IrInstructionErrToInt *instruction = ir_build_instruction<IrInstructionErrToInt>(
            irb, scope, source_node);
    instruction->target = target;

    ir_ref_instruction(target, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_check_switch_prongs(IrBuilder *irb, Scope *scope, AstNode *source_node,
        IrInstruction *target_value, IrInstructionCheckSwitchProngsRange *ranges, size_t range_count,
        bool have_else_prong)
{
    IrInstructionCheckSwitchProngs *instruction = ir_build_instruction<IrInstructionCheckSwitchProngs>(
            irb, scope, source_node);
    instruction->target_value = target_value;
    instruction->ranges = ranges;
    instruction->range_count = range_count;
    instruction->have_else_prong = have_else_prong;

    ir_ref_instruction(target_value, irb->current_basic_block);
    for (size_t i = 0; i < range_count; i += 1) {
        ir_ref_instruction(ranges[i].start, irb->current_basic_block);
        ir_ref_instruction(ranges[i].end, irb->current_basic_block);
    }

    return &instruction->base;
}

static IrInstruction *ir_build_check_statement_is_void(IrBuilder *irb, Scope *scope, AstNode *source_node,
    IrInstruction* statement_value)
{
    IrInstructionCheckStatementIsVoid *instruction = ir_build_instruction<IrInstructionCheckStatementIsVoid>(
            irb, scope, source_node);
    instruction->statement_value = statement_value;

    ir_ref_instruction(statement_value, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_type_name(IrBuilder *irb, Scope *scope, AstNode *source_node,
        IrInstruction *type_value)
{
    IrInstructionTypeName *instruction = ir_build_instruction<IrInstructionTypeName>(
            irb, scope, source_node);
    instruction->type_value = type_value;

    ir_ref_instruction(type_value, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_decl_ref(IrBuilder *irb, Scope *scope, AstNode *source_node,
        Tld *tld, LVal lval)
{
    IrInstructionDeclRef *instruction = ir_build_instruction<IrInstructionDeclRef>(
            irb, scope, source_node);
    instruction->tld = tld;
    instruction->lval = lval;

    return &instruction->base;
}

static IrInstruction *ir_build_panic(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *msg) {
    IrInstructionPanic *instruction = ir_build_instruction<IrInstructionPanic>(irb, scope, source_node);
    instruction->base.value.special = ConstValSpecialStatic;
    instruction->base.value.type = irb->codegen->builtin_types.entry_unreachable;
    instruction->msg = msg;

    ir_ref_instruction(msg, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_tag_name(IrBuilder *irb, Scope *scope, AstNode *source_node,
        IrInstruction *target)
{
    IrInstructionTagName *instruction = ir_build_instruction<IrInstructionTagName>(irb, scope, source_node);
    instruction->target = target;

    ir_ref_instruction(target, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_tag_type(IrBuilder *irb, Scope *scope, AstNode *source_node,
        IrInstruction *target)
{
    IrInstructionTagType *instruction = ir_build_instruction<IrInstructionTagType>(irb, scope, source_node);
    instruction->target = target;

    ir_ref_instruction(target, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_field_parent_ptr(IrBuilder *irb, Scope *scope, AstNode *source_node,
        IrInstruction *type_value, IrInstruction *field_name, IrInstruction *field_ptr, TypeStructField *field)
{
    IrInstructionFieldParentPtr *instruction = ir_build_instruction<IrInstructionFieldParentPtr>(
            irb, scope, source_node);
    instruction->type_value = type_value;
    instruction->field_name = field_name;
    instruction->field_ptr = field_ptr;
    instruction->field = field;

    ir_ref_instruction(type_value, irb->current_basic_block);
    ir_ref_instruction(field_name, irb->current_basic_block);
    ir_ref_instruction(field_ptr, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_byte_offset_of(IrBuilder *irb, Scope *scope, AstNode *source_node,
        IrInstruction *type_value, IrInstruction *field_name)
{
    IrInstructionByteOffsetOf *instruction = ir_build_instruction<IrInstructionByteOffsetOf>(irb, scope, source_node);
    instruction->type_value = type_value;
    instruction->field_name = field_name;

    ir_ref_instruction(type_value, irb->current_basic_block);
    ir_ref_instruction(field_name, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_bit_offset_of(IrBuilder *irb, Scope *scope, AstNode *source_node,
        IrInstruction *type_value, IrInstruction *field_name)
{
    IrInstructionBitOffsetOf *instruction = ir_build_instruction<IrInstructionBitOffsetOf>(irb, scope, source_node);
    instruction->type_value = type_value;
    instruction->field_name = field_name;

    ir_ref_instruction(type_value, irb->current_basic_block);
    ir_ref_instruction(field_name, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_type_info(IrBuilder *irb, Scope *scope, AstNode *source_node,
        IrInstruction *type_value) {
    IrInstructionTypeInfo *instruction = ir_build_instruction<IrInstructionTypeInfo>(irb, scope, source_node);
    instruction->type_value = type_value;

    ir_ref_instruction(type_value, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_type_id(IrBuilder *irb, Scope *scope, AstNode *source_node,
        IrInstruction *type_value)
{
    IrInstructionTypeId *instruction = ir_build_instruction<IrInstructionTypeId>(irb, scope, source_node);
    instruction->type_value = type_value;

    ir_ref_instruction(type_value, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_set_eval_branch_quota(IrBuilder *irb, Scope *scope, AstNode *source_node,
        IrInstruction *new_quota)
{
    IrInstructionSetEvalBranchQuota *instruction = ir_build_instruction<IrInstructionSetEvalBranchQuota>(irb, scope, source_node);
    instruction->new_quota = new_quota;

    ir_ref_instruction(new_quota, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_align_cast(IrBuilder *irb, Scope *scope, AstNode *source_node,
        IrInstruction *align_bytes, IrInstruction *target)
{
    IrInstructionAlignCast *instruction = ir_build_instruction<IrInstructionAlignCast>(irb, scope, source_node);
    instruction->align_bytes = align_bytes;
    instruction->target = target;

    if (align_bytes) ir_ref_instruction(align_bytes, irb->current_basic_block);
    ir_ref_instruction(target, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_opaque_type(IrBuilder *irb, Scope *scope, AstNode *source_node) {
    IrInstructionOpaqueType *instruction = ir_build_instruction<IrInstructionOpaqueType>(irb, scope, source_node);

    return &instruction->base;
}

static IrInstruction *ir_build_set_align_stack(IrBuilder *irb, Scope *scope, AstNode *source_node,
        IrInstruction *align_bytes)
{
    IrInstructionSetAlignStack *instruction = ir_build_instruction<IrInstructionSetAlignStack>(irb, scope, source_node);
    instruction->align_bytes = align_bytes;

    ir_ref_instruction(align_bytes, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_arg_type(IrBuilder *irb, Scope *scope, AstNode *source_node,
        IrInstruction *fn_type, IrInstruction *arg_index)
{
    IrInstructionArgType *instruction = ir_build_instruction<IrInstructionArgType>(irb, scope, source_node);
    instruction->fn_type = fn_type;
    instruction->arg_index = arg_index;

    ir_ref_instruction(fn_type, irb->current_basic_block);
    ir_ref_instruction(arg_index, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_error_return_trace(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstructionErrorReturnTrace::Optional optional) {
    IrInstructionErrorReturnTrace *instruction = ir_build_instruction<IrInstructionErrorReturnTrace>(irb, scope, source_node);
    instruction->optional = optional;

    return &instruction->base;
}

static IrInstruction *ir_build_error_union(IrBuilder *irb, Scope *scope, AstNode *source_node,
        IrInstruction *err_set, IrInstruction *payload)
{
    IrInstructionErrorUnion *instruction = ir_build_instruction<IrInstructionErrorUnion>(irb, scope, source_node);
    instruction->err_set = err_set;
    instruction->payload = payload;

    ir_ref_instruction(err_set, irb->current_basic_block);
    ir_ref_instruction(payload, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_cancel(IrBuilder *irb, Scope *scope, AstNode *source_node,
        IrInstruction *target)
{
    IrInstructionCancel *instruction = ir_build_instruction<IrInstructionCancel>(irb, scope, source_node);
    instruction->target = target;

    ir_ref_instruction(target, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_get_implicit_allocator(IrBuilder *irb, Scope *scope, AstNode *source_node,
        ImplicitAllocatorId id)
{
    IrInstructionGetImplicitAllocator *instruction = ir_build_instruction<IrInstructionGetImplicitAllocator>(irb, scope, source_node);
    instruction->id = id;

    return &instruction->base;
}

static IrInstruction *ir_build_coro_id(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *promise_ptr) {
    IrInstructionCoroId *instruction = ir_build_instruction<IrInstructionCoroId>(irb, scope, source_node);
    instruction->promise_ptr = promise_ptr;

    ir_ref_instruction(promise_ptr, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_coro_alloc(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *coro_id) {
    IrInstructionCoroAlloc *instruction = ir_build_instruction<IrInstructionCoroAlloc>(irb, scope, source_node);
    instruction->coro_id = coro_id;

    ir_ref_instruction(coro_id, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_coro_size(IrBuilder *irb, Scope *scope, AstNode *source_node) {
    IrInstructionCoroSize *instruction = ir_build_instruction<IrInstructionCoroSize>(irb, scope, source_node);

    return &instruction->base;
}

static IrInstruction *ir_build_coro_begin(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *coro_id, IrInstruction *coro_mem_ptr) {
    IrInstructionCoroBegin *instruction = ir_build_instruction<IrInstructionCoroBegin>(irb, scope, source_node);
    instruction->coro_id = coro_id;
    instruction->coro_mem_ptr = coro_mem_ptr;

    ir_ref_instruction(coro_id, irb->current_basic_block);
    ir_ref_instruction(coro_mem_ptr, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_coro_alloc_fail(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *err_val) {
    IrInstructionCoroAllocFail *instruction = ir_build_instruction<IrInstructionCoroAllocFail>(irb, scope, source_node);
    instruction->base.value.type = irb->codegen->builtin_types.entry_unreachable;
    instruction->base.value.special = ConstValSpecialStatic;
    instruction->err_val = err_val;

    ir_ref_instruction(err_val, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_coro_suspend(IrBuilder *irb, Scope *scope, AstNode *source_node,
        IrInstruction *save_point, IrInstruction *is_final)
{
    IrInstructionCoroSuspend *instruction = ir_build_instruction<IrInstructionCoroSuspend>(irb, scope, source_node);
    instruction->save_point = save_point;
    instruction->is_final = is_final;

    if (save_point != nullptr) ir_ref_instruction(save_point, irb->current_basic_block);
    ir_ref_instruction(is_final, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_coro_end(IrBuilder *irb, Scope *scope, AstNode *source_node) {
    IrInstructionCoroEnd *instruction = ir_build_instruction<IrInstructionCoroEnd>(irb, scope, source_node);
    return &instruction->base;
}

static IrInstruction *ir_build_coro_free(IrBuilder *irb, Scope *scope, AstNode *source_node,
        IrInstruction *coro_id, IrInstruction *coro_handle)
{
    IrInstructionCoroFree *instruction = ir_build_instruction<IrInstructionCoroFree>(irb, scope, source_node);
    instruction->coro_id = coro_id;
    instruction->coro_handle = coro_handle;

    ir_ref_instruction(coro_id, irb->current_basic_block);
    ir_ref_instruction(coro_handle, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_coro_resume(IrBuilder *irb, Scope *scope, AstNode *source_node,
        IrInstruction *awaiter_handle)
{
    IrInstructionCoroResume *instruction = ir_build_instruction<IrInstructionCoroResume>(irb, scope, source_node);
    instruction->awaiter_handle = awaiter_handle;

    ir_ref_instruction(awaiter_handle, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_coro_save(IrBuilder *irb, Scope *scope, AstNode *source_node,
        IrInstruction *coro_handle)
{
    IrInstructionCoroSave *instruction = ir_build_instruction<IrInstructionCoroSave>(irb, scope, source_node);
    instruction->coro_handle = coro_handle;

    ir_ref_instruction(coro_handle, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_coro_promise(IrBuilder *irb, Scope *scope, AstNode *source_node,
        IrInstruction *coro_handle)
{
    IrInstructionCoroPromise *instruction = ir_build_instruction<IrInstructionCoroPromise>(irb, scope, source_node);
    instruction->coro_handle = coro_handle;

    ir_ref_instruction(coro_handle, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_coro_alloc_helper(IrBuilder *irb, Scope *scope, AstNode *source_node,
        IrInstruction *alloc_fn, IrInstruction *coro_size)
{
    IrInstructionCoroAllocHelper *instruction = ir_build_instruction<IrInstructionCoroAllocHelper>(irb, scope, source_node);
    instruction->alloc_fn = alloc_fn;
    instruction->coro_size = coro_size;

    ir_ref_instruction(alloc_fn, irb->current_basic_block);
    ir_ref_instruction(coro_size, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_atomic_rmw(IrBuilder *irb, Scope *scope, AstNode *source_node,
        IrInstruction *operand_type, IrInstruction *ptr, IrInstruction *op, IrInstruction *operand,
        IrInstruction *ordering, AtomicRmwOp resolved_op, AtomicOrder resolved_ordering)
{
    IrInstructionAtomicRmw *instruction = ir_build_instruction<IrInstructionAtomicRmw>(irb, scope, source_node);
    instruction->operand_type = operand_type;
    instruction->ptr = ptr;
    instruction->op = op;
    instruction->operand = operand;
    instruction->ordering = ordering;
    instruction->resolved_op = resolved_op;
    instruction->resolved_ordering = resolved_ordering;

    if (operand_type != nullptr) ir_ref_instruction(operand_type, irb->current_basic_block);
    ir_ref_instruction(ptr, irb->current_basic_block);
    if (op != nullptr) ir_ref_instruction(op, irb->current_basic_block);
    ir_ref_instruction(operand, irb->current_basic_block);
    if (ordering != nullptr) ir_ref_instruction(ordering, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_atomic_load(IrBuilder *irb, Scope *scope, AstNode *source_node,
        IrInstruction *operand_type, IrInstruction *ptr,
        IrInstruction *ordering, AtomicOrder resolved_ordering)
{
    IrInstructionAtomicLoad *instruction = ir_build_instruction<IrInstructionAtomicLoad>(irb, scope, source_node);
    instruction->operand_type = operand_type;
    instruction->ptr = ptr;
    instruction->ordering = ordering;
    instruction->resolved_ordering = resolved_ordering;

    if (operand_type != nullptr) ir_ref_instruction(operand_type, irb->current_basic_block);
    ir_ref_instruction(ptr, irb->current_basic_block);
    if (ordering != nullptr) ir_ref_instruction(ordering, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_promise_result_type(IrBuilder *irb, Scope *scope, AstNode *source_node,
        IrInstruction *promise_type)
{
    IrInstructionPromiseResultType *instruction = ir_build_instruction<IrInstructionPromiseResultType>(irb, scope, source_node);
    instruction->promise_type = promise_type;

    ir_ref_instruction(promise_type, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_await_bookkeeping(IrBuilder *irb, Scope *scope, AstNode *source_node,
        IrInstruction *promise_result_type)
{
    IrInstructionAwaitBookkeeping *instruction = ir_build_instruction<IrInstructionAwaitBookkeeping>(irb, scope, source_node);
    instruction->promise_result_type = promise_result_type;

    ir_ref_instruction(promise_result_type, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_save_err_ret_addr(IrBuilder *irb, Scope *scope, AstNode *source_node) {
    IrInstructionSaveErrRetAddr *instruction = ir_build_instruction<IrInstructionSaveErrRetAddr>(irb, scope, source_node);
    return &instruction->base;
}

static IrInstruction *ir_build_add_implicit_return_type(IrBuilder *irb, Scope *scope, AstNode *source_node,
        IrInstruction *value)
{
    IrInstructionAddImplicitReturnType *instruction = ir_build_instruction<IrInstructionAddImplicitReturnType>(irb, scope, source_node);
    instruction->value = value;

    ir_ref_instruction(value, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_merge_err_ret_traces(IrBuilder *irb, Scope *scope, AstNode *source_node,
        IrInstruction *coro_promise_ptr, IrInstruction *src_err_ret_trace_ptr, IrInstruction *dest_err_ret_trace_ptr)
{
    IrInstructionMergeErrRetTraces *instruction = ir_build_instruction<IrInstructionMergeErrRetTraces>(irb, scope, source_node);
    instruction->coro_promise_ptr = coro_promise_ptr;
    instruction->src_err_ret_trace_ptr = src_err_ret_trace_ptr;
    instruction->dest_err_ret_trace_ptr = dest_err_ret_trace_ptr;

    ir_ref_instruction(coro_promise_ptr, irb->current_basic_block);
    ir_ref_instruction(src_err_ret_trace_ptr, irb->current_basic_block);
    ir_ref_instruction(dest_err_ret_trace_ptr, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_mark_err_ret_trace_ptr(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *err_ret_trace_ptr) {
    IrInstructionMarkErrRetTracePtr *instruction = ir_build_instruction<IrInstructionMarkErrRetTracePtr>(irb, scope, source_node);
    instruction->err_ret_trace_ptr = err_ret_trace_ptr;

    ir_ref_instruction(err_ret_trace_ptr, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_sqrt(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *type, IrInstruction *op) {
    IrInstructionSqrt *instruction = ir_build_instruction<IrInstructionSqrt>(irb, scope, source_node);
    instruction->type = type;
    instruction->op = op;

    if (type != nullptr) ir_ref_instruction(type, irb->current_basic_block);
    ir_ref_instruction(op, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_bswap(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *type, IrInstruction *op) {
    IrInstructionBswap *instruction = ir_build_instruction<IrInstructionBswap>(irb, scope, source_node);
    instruction->type = type;
    instruction->op = op;

    if (type != nullptr) ir_ref_instruction(type, irb->current_basic_block);
    ir_ref_instruction(op, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_bit_reverse(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *type, IrInstruction *op) {
    IrInstructionBitReverse *instruction = ir_build_instruction<IrInstructionBitReverse>(irb, scope, source_node);
    instruction->type = type;
    instruction->op = op;

    if (type != nullptr) ir_ref_instruction(type, irb->current_basic_block);
    ir_ref_instruction(op, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_check_runtime_scope(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *scope_is_comptime, IrInstruction *is_comptime) {
    IrInstructionCheckRuntimeScope *instruction = ir_build_instruction<IrInstructionCheckRuntimeScope>(irb, scope, source_node);
    instruction->scope_is_comptime = scope_is_comptime;
    instruction->is_comptime = is_comptime;

    ir_ref_instruction(scope_is_comptime, irb->current_basic_block);
    ir_ref_instruction(is_comptime, irb->current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_vector_to_array(IrAnalyze *ira, IrInstruction *source_instruction,
        IrInstruction *vector, ZigType *result_type)
{
    IrInstructionVectorToArray *instruction = ir_build_instruction<IrInstructionVectorToArray>(&ira->new_irb,
        source_instruction->scope, source_instruction->source_node);
    instruction->base.value.type = result_type;
    instruction->vector = vector;

    ir_ref_instruction(vector, ira->new_irb.current_basic_block);

    ir_add_alloca(ira, &instruction->base, result_type);

    return &instruction->base;
}

static IrInstruction *ir_build_array_to_vector(IrAnalyze *ira, IrInstruction *source_instruction,
        IrInstruction *array, ZigType *result_type)
{
    IrInstructionArrayToVector *instruction = ir_build_instruction<IrInstructionArrayToVector>(&ira->new_irb,
        source_instruction->scope, source_instruction->source_node);
    instruction->base.value.type = result_type;
    instruction->array = array;

    ir_ref_instruction(array, ira->new_irb.current_basic_block);

    return &instruction->base;
}

static IrInstruction *ir_build_assert_zero(IrAnalyze *ira, IrInstruction *source_instruction,
        IrInstruction *target)
{
    IrInstructionAssertZero *instruction = ir_build_instruction<IrInstructionAssertZero>(&ira->new_irb,
        source_instruction->scope, source_instruction->source_node);
    instruction->base.value.type = ira->codegen->builtin_types.entry_void;
    instruction->target = target;

    ir_ref_instruction(target, ira->new_irb.current_basic_block);

    return &instruction->base;
}

static void ir_count_defers(IrBuilder *irb, Scope *inner_scope, Scope *outer_scope, size_t *results) {
    results[ReturnKindUnconditional] = 0;
    results[ReturnKindError] = 0;

    Scope *scope = inner_scope;

    while (scope != outer_scope) {
        assert(scope);
        switch (scope->id) {
            case ScopeIdDefer: {
                AstNode *defer_node = scope->source_node;
                assert(defer_node->type == NodeTypeDefer);
                ReturnKind defer_kind = defer_node->data.defer.kind;
                results[defer_kind] += 1;
                scope = scope->parent;
                continue;
            }
            case ScopeIdDecls:
            case ScopeIdFnDef:
                return;
            case ScopeIdBlock:
            case ScopeIdVarDecl:
            case ScopeIdLoop:
            case ScopeIdSuspend:
            case ScopeIdCompTime:
            case ScopeIdRuntime:
                scope = scope->parent;
                continue;
            case ScopeIdDeferExpr:
            case ScopeIdCImport:
            case ScopeIdCoroPrelude:
                zig_unreachable();
        }
    }
}

static IrInstruction *ir_mark_gen(IrInstruction *instruction) {
    instruction->is_gen = true;
    return instruction;
}

static bool ir_gen_defers_for_block(IrBuilder *irb, Scope *inner_scope, Scope *outer_scope, bool gen_error_defers) {
    Scope *scope = inner_scope;
    bool is_noreturn = false;
    while (scope != outer_scope) {
        if (!scope)
            return is_noreturn;

        switch (scope->id) {
            case ScopeIdDefer: {
                AstNode *defer_node = scope->source_node;
                assert(defer_node->type == NodeTypeDefer);
                ReturnKind defer_kind = defer_node->data.defer.kind;
                if (defer_kind == ReturnKindUnconditional ||
                    (gen_error_defers && defer_kind == ReturnKindError))
                {
                    AstNode *defer_expr_node = defer_node->data.defer.expr;
                    Scope *defer_expr_scope = defer_node->data.defer.expr_scope;
                    IrInstruction *defer_expr_value = ir_gen_node(irb, defer_expr_node, defer_expr_scope);
                    if (defer_expr_value != irb->codegen->invalid_instruction) {
                        if (defer_expr_value->value.type != nullptr &&
                                defer_expr_value->value.type->id == ZigTypeIdUnreachable)
                        {
                            is_noreturn = true;
                        } else {
                            ir_mark_gen(ir_build_check_statement_is_void(irb, defer_expr_scope, defer_expr_node,
                                        defer_expr_value));
                        }
                    }
                }
                scope = scope->parent;
                continue;
            }
            case ScopeIdDecls:
            case ScopeIdFnDef:
                return is_noreturn;
            case ScopeIdBlock:
            case ScopeIdVarDecl:
            case ScopeIdLoop:
            case ScopeIdSuspend:
            case ScopeIdCompTime:
            case ScopeIdRuntime:
                scope = scope->parent;
                continue;
            case ScopeIdDeferExpr:
            case ScopeIdCImport:
            case ScopeIdCoroPrelude:
                zig_unreachable();
        }
    }
    return is_noreturn;
}

static void ir_set_cursor_at_end(IrBuilder *irb, IrBasicBlock *basic_block) {
    assert(basic_block);

    irb->current_basic_block = basic_block;
}

static void ir_set_cursor_at_end_and_append_block(IrBuilder *irb, IrBasicBlock *basic_block) {
    irb->exec->basic_block_list.append(basic_block);
    ir_set_cursor_at_end(irb, basic_block);
}

static ScopeSuspend *get_scope_suspend(Scope *scope) {
    while (scope) {
        if (scope->id == ScopeIdSuspend)
            return (ScopeSuspend *)scope;
        if (scope->id == ScopeIdFnDef)
            return nullptr;

        scope = scope->parent;
    }
    return nullptr;
}

static ScopeDeferExpr *get_scope_defer_expr(Scope *scope) {
    while (scope) {
        if (scope->id == ScopeIdDeferExpr)
            return (ScopeDeferExpr *)scope;
        if (scope->id == ScopeIdFnDef)
            return nullptr;

        scope = scope->parent;
    }
    return nullptr;
}

static bool exec_is_async(IrExecutable *exec) {
    ZigFn *fn_entry = exec_fn_entry(exec);
    return fn_entry != nullptr && fn_entry->type_entry->data.fn.fn_type_id.cc == CallingConventionAsync;
}

static IrInstruction *ir_gen_async_return(IrBuilder *irb, Scope *scope, AstNode *node, IrInstruction *return_value,
    bool is_generated_code)
{
    ir_mark_gen(ir_build_add_implicit_return_type(irb, scope, node, return_value));

    bool is_async = exec_is_async(irb->exec);
    if (!is_async) {
        IrInstruction *return_inst = ir_build_return(irb, scope, node, return_value);
        return_inst->is_gen = is_generated_code;
        return return_inst;
    }

    IrBasicBlock *suspended_block = ir_create_basic_block(irb, scope, "Suspended");
    IrBasicBlock *not_suspended_block = ir_create_basic_block(irb, scope, "NotSuspended");
    IrBasicBlock *store_awaiter_block = ir_create_basic_block(irb, scope, "StoreAwaiter");
    IrBasicBlock *check_canceled_block = ir_create_basic_block(irb, scope, "CheckCanceled");

    IrInstruction *inverted_ptr_mask = ir_build_const_usize(irb, scope, node, 0x7); // 0b111
    IrInstruction *ptr_mask = ir_build_un_op(irb, scope, node, IrUnOpBinNot, inverted_ptr_mask); // 0b111...000
    IrInstruction *is_canceled_mask = ir_build_const_usize(irb, scope, node, 0x1); // 0b001
    IrInstruction *is_suspended_mask = ir_build_const_usize(irb, scope, node, 0x2); // 0b010
    IrInstruction *promise_type_val = ir_build_const_type(irb, scope, node, irb->codegen->builtin_types.entry_promise);
    IrInstruction *is_comptime = ir_build_const_bool(irb, scope, node, false);
    IrInstruction *zero = ir_build_const_usize(irb, scope, node, 0);

    ir_build_store_ptr(irb, scope, node, irb->exec->coro_result_field_ptr, return_value);
    IrInstruction *usize_type_val = ir_build_const_type(irb, scope, node, irb->codegen->builtin_types.entry_usize);
    IrInstruction *prev_atomic_value = ir_build_atomic_rmw(irb, scope, node,
            usize_type_val, irb->exec->atomic_state_field_ptr, nullptr, ptr_mask, nullptr,
            AtomicRmwOp_or, AtomicOrderSeqCst);

    IrInstruction *is_suspended_value = ir_build_bin_op(irb, scope, node, IrBinOpBinAnd, prev_atomic_value, is_suspended_mask, false);
    IrInstruction *is_suspended_bool = ir_build_bin_op(irb, scope, node, IrBinOpCmpNotEq, is_suspended_value, zero, false);
    ir_build_cond_br(irb, scope, node, is_suspended_bool, suspended_block, not_suspended_block, is_comptime);

    ir_set_cursor_at_end_and_append_block(irb, suspended_block);
    ir_build_unreachable(irb, scope, node);

    ir_set_cursor_at_end_and_append_block(irb, not_suspended_block);
    IrInstruction *await_handle_addr = ir_build_bin_op(irb, scope, node, IrBinOpBinAnd, prev_atomic_value, ptr_mask, false);
    // if we ever add null checking safety to the ptrtoint instruction, it needs to be disabled here
    IrInstruction *have_await_handle = ir_build_bin_op(irb, scope, node, IrBinOpCmpNotEq, await_handle_addr, zero, false);
    ir_build_cond_br(irb, scope, node, have_await_handle, store_awaiter_block, check_canceled_block, is_comptime);

    ir_set_cursor_at_end_and_append_block(irb, store_awaiter_block);
    IrInstruction *await_handle = ir_build_int_to_ptr(irb, scope, node, promise_type_val, await_handle_addr);
    ir_build_store_ptr(irb, scope, node, irb->exec->await_handle_var_ptr, await_handle);
    ir_build_br(irb, scope, node, irb->exec->coro_normal_final, is_comptime);

    ir_set_cursor_at_end_and_append_block(irb, check_canceled_block);
    IrInstruction *is_canceled_value = ir_build_bin_op(irb, scope, node, IrBinOpBinAnd, prev_atomic_value, is_canceled_mask, false);
    IrInstruction *is_canceled_bool = ir_build_bin_op(irb, scope, node, IrBinOpCmpNotEq, is_canceled_value, zero, false);
    return ir_build_cond_br(irb, scope, node, is_canceled_bool, irb->exec->coro_final_cleanup_block, irb->exec->coro_early_final, is_comptime);
}

static IrInstruction *ir_gen_return(IrBuilder *irb, Scope *scope, AstNode *node, LVal lval) {
    assert(node->type == NodeTypeReturnExpr);

    ZigFn *fn_entry = exec_fn_entry(irb->exec);
    if (!fn_entry) {
        add_node_error(irb->codegen, node, buf_sprintf("return expression outside function definition"));
        return irb->codegen->invalid_instruction;
    }

    ScopeDeferExpr *scope_defer_expr = get_scope_defer_expr(scope);
    if (scope_defer_expr) {
        if (!scope_defer_expr->reported_err) {
            add_node_error(irb->codegen, node, buf_sprintf("cannot return from defer expression"));
            scope_defer_expr->reported_err = true;
        }
        return irb->codegen->invalid_instruction;
    }

    Scope *outer_scope = irb->exec->begin_scope;

    AstNode *expr_node = node->data.return_expr.expr;
    switch (node->data.return_expr.kind) {
        case ReturnKindUnconditional:
            {
                IrInstruction *return_value;
                if (expr_node) {
                    // Temporarily set this so that if we return a type it gets the name of the function
                    ZigFn *prev_name_fn = irb->exec->name_fn;
                    irb->exec->name_fn = exec_fn_entry(irb->exec);
                    return_value = ir_gen_node(irb, expr_node, scope);
                    irb->exec->name_fn = prev_name_fn;
                    if (return_value == irb->codegen->invalid_instruction)
                        return irb->codegen->invalid_instruction;
                } else {
                    return_value = ir_build_const_void(irb, scope, node);
                }

                size_t defer_counts[2];
                ir_count_defers(irb, scope, outer_scope, defer_counts);
                bool have_err_defers = defer_counts[ReturnKindError] > 0;
                if (have_err_defers || irb->codegen->have_err_ret_tracing) {
                    IrBasicBlock *err_block = ir_create_basic_block(irb, scope, "ErrRetErr");
                    IrBasicBlock *ok_block = ir_create_basic_block(irb, scope, "ErrRetOk");
                    if (!have_err_defers) {
                        ir_gen_defers_for_block(irb, scope, outer_scope, false);
                    }

                    IrInstruction *is_err = ir_build_test_err(irb, scope, node, return_value);

                    bool should_inline = ir_should_inline(irb->exec, scope);
                    IrInstruction *is_comptime;
                    if (should_inline) {
                        is_comptime = ir_build_const_bool(irb, scope, node, true);
                    } else {
                        is_comptime = ir_build_test_comptime(irb, scope, node, is_err);
                    }

                    ir_mark_gen(ir_build_cond_br(irb, scope, node, is_err, err_block, ok_block, is_comptime));
                    IrBasicBlock *ret_stmt_block = ir_create_basic_block(irb, scope, "RetStmt");

                    ir_set_cursor_at_end_and_append_block(irb, err_block);
                    if (have_err_defers) {
                        ir_gen_defers_for_block(irb, scope, outer_scope, true);
                    }
                    if (irb->codegen->have_err_ret_tracing && !should_inline) {
                        ir_build_save_err_ret_addr(irb, scope, node);
                    }
                    ir_build_br(irb, scope, node, ret_stmt_block, is_comptime);

                    ir_set_cursor_at_end_and_append_block(irb, ok_block);
                    if (have_err_defers) {
                        ir_gen_defers_for_block(irb, scope, outer_scope, false);
                    }
                    ir_build_br(irb, scope, node, ret_stmt_block, is_comptime);

                    ir_set_cursor_at_end_and_append_block(irb, ret_stmt_block);
                    return ir_gen_async_return(irb, scope, node, return_value, false);
                } else {
                    // generate unconditional defers
                    ir_gen_defers_for_block(irb, scope, outer_scope, false);
                    return ir_gen_async_return(irb, scope, node, return_value, false);
                }
            }
        case ReturnKindError:
            {
                assert(expr_node);
                IrInstruction *err_union_ptr = ir_gen_node_extra(irb, expr_node, scope, LValPtr);
                if (err_union_ptr == irb->codegen->invalid_instruction)
                    return irb->codegen->invalid_instruction;
                IrInstruction *err_union_val = ir_build_load_ptr(irb, scope, node, err_union_ptr);
                IrInstruction *is_err_val = ir_build_test_err(irb, scope, node, err_union_val);

                IrBasicBlock *return_block = ir_create_basic_block(irb, scope, "ErrRetReturn");
                IrBasicBlock *continue_block = ir_create_basic_block(irb, scope, "ErrRetContinue");
                IrInstruction *is_comptime;
                bool should_inline = ir_should_inline(irb->exec, scope);
                if (should_inline) {
                    is_comptime = ir_build_const_bool(irb, scope, node, true);
                } else {
                    is_comptime = ir_build_test_comptime(irb, scope, node, is_err_val);
                }
                ir_mark_gen(ir_build_cond_br(irb, scope, node, is_err_val, return_block, continue_block, is_comptime));

                ir_set_cursor_at_end_and_append_block(irb, return_block);
                if (!ir_gen_defers_for_block(irb, scope, outer_scope, true)) {
                    IrInstruction *err_val = ir_build_unwrap_err_code(irb, scope, node, err_union_ptr);
                    if (irb->codegen->have_err_ret_tracing && !should_inline) {
                        ir_build_save_err_ret_addr(irb, scope, node);
                    }
                    ir_gen_async_return(irb, scope, node, err_val, false);
                }

                ir_set_cursor_at_end_and_append_block(irb, continue_block);
                IrInstruction *unwrapped_ptr = ir_build_unwrap_err_payload(irb, scope, node, err_union_ptr, false);
                if (lval == LValPtr)
                    return unwrapped_ptr;
                else
                    return ir_build_load_ptr(irb, scope, node, unwrapped_ptr);
            }
    }
    zig_unreachable();
}

static ZigVar *create_local_var(CodeGen *codegen, AstNode *node, Scope *parent_scope,
        Buf *name, bool src_is_const, bool gen_is_const, bool is_shadowable, IrInstruction *is_comptime,
        bool skip_name_check)
{
    ZigVar *variable_entry = allocate<ZigVar>(1);
    variable_entry->parent_scope = parent_scope;
    variable_entry->shadowable = is_shadowable;
    variable_entry->mem_slot_index = SIZE_MAX;
    variable_entry->is_comptime = is_comptime;
    variable_entry->src_arg_index = SIZE_MAX;
    variable_entry->const_value = create_const_vals(1);

    if (is_comptime != nullptr) {
        is_comptime->ref_count += 1;
    }

    if (name) {
        buf_init_from_buf(&variable_entry->name, name);

        if (!skip_name_check) {
            ZigVar *existing_var = find_variable(codegen, parent_scope, name, nullptr);
            if (existing_var && !existing_var->shadowable) {
                ErrorMsg *msg = add_node_error(codegen, node,
                        buf_sprintf("redeclaration of variable '%s'", buf_ptr(name)));
                add_error_note(codegen, msg, existing_var->decl_node, buf_sprintf("previous declaration is here"));
                variable_entry->var_type = codegen->builtin_types.entry_invalid;
            } else {
                ZigType *type;
                if (get_primitive_type(codegen, name, &type) != ErrorPrimitiveTypeNotFound) {
                    add_node_error(codegen, node,
                            buf_sprintf("variable shadows primitive type '%s'", buf_ptr(name)));
                    variable_entry->var_type = codegen->builtin_types.entry_invalid;
                } else {
                    Tld *tld = find_decl(codegen, parent_scope, name);
                    if (tld != nullptr) {
                        ErrorMsg *msg = add_node_error(codegen, node,
                                buf_sprintf("redefinition of '%s'", buf_ptr(name)));
                        add_error_note(codegen, msg, tld->source_node, buf_sprintf("previous definition is here"));
                        variable_entry->var_type = codegen->builtin_types.entry_invalid;
                    }
                }
            }
        }
    } else {
        assert(is_shadowable);
        // TODO make this name not actually be in scope. user should be able to make a variable called "_anon"
        // might already be solved, let's just make sure it has test coverage
        // maybe we put a prefix on this so the debug info doesn't clobber user debug info for same named variables
        buf_init_from_str(&variable_entry->name, "_anon");
    }

    variable_entry->src_is_const = src_is_const;
    variable_entry->gen_is_const = gen_is_const;
    variable_entry->decl_node = node;
    variable_entry->child_scope = create_var_scope(codegen, node, parent_scope, variable_entry);

    return variable_entry;
}

// Set name to nullptr to make the variable anonymous (not visible to programmer).
// After you call this function var->child_scope has the variable in scope
static ZigVar *ir_create_var(IrBuilder *irb, AstNode *node, Scope *scope, Buf *name,
        bool src_is_const, bool gen_is_const, bool is_shadowable, IrInstruction *is_comptime)
{
    bool is_underscored = name ? buf_eql_str(name, "_") : false;
    ZigVar *var = create_local_var(irb->codegen, node, scope,
            (is_underscored ? nullptr : name), src_is_const, gen_is_const,
            (is_underscored ? true : is_shadowable), is_comptime, false);
    if (is_comptime != nullptr || gen_is_const) {
        var->mem_slot_index = exec_next_mem_slot(irb->exec);
        var->owner_exec = irb->exec;
    }
    assert(var->child_scope);
    return var;
}

static IrInstruction *ir_gen_block(IrBuilder *irb, Scope *parent_scope, AstNode *block_node) {
    assert(block_node->type == NodeTypeBlock);

    ZigList<IrInstruction *> incoming_values = {0};
    ZigList<IrBasicBlock *> incoming_blocks = {0};

    ScopeBlock *scope_block = create_block_scope(irb->codegen, block_node, parent_scope);

    Scope *outer_block_scope = &scope_block->base;
    Scope *child_scope = outer_block_scope;

    ZigFn *fn_entry = scope_fn_entry(parent_scope);
    if (fn_entry && fn_entry->child_scope == parent_scope) {
        fn_entry->def_scope = scope_block;
    }

    if (block_node->data.block.statements.length == 0) {
        // {}
        return ir_build_const_void(irb, child_scope, block_node);
    }

    if (block_node->data.block.name != nullptr) {
        scope_block->incoming_blocks = &incoming_blocks;
        scope_block->incoming_values = &incoming_values;
        scope_block->end_block = ir_create_basic_block(irb, parent_scope, "BlockEnd");
        scope_block->is_comptime = ir_build_const_bool(irb, parent_scope, block_node,
                ir_should_inline(irb->exec, parent_scope));
    }

    bool is_continuation_unreachable = false;
    IrInstruction *noreturn_return_value = nullptr;
    for (size_t i = 0; i < block_node->data.block.statements.length; i += 1) {
        AstNode *statement_node = block_node->data.block.statements.at(i);

        IrInstruction *statement_value = ir_gen_node(irb, statement_node, child_scope);
        is_continuation_unreachable = instr_is_unreachable(statement_value);
        if (is_continuation_unreachable) {
            // keep the last noreturn statement value around in case we need to return it
            noreturn_return_value = statement_value;
        }
        if (statement_node->type == NodeTypeDefer && statement_value != irb->codegen->invalid_instruction) {
            // defer starts a new scope
            child_scope = statement_node->data.defer.child_scope;
            assert(child_scope);
        } else if (statement_value->id == IrInstructionIdDeclVarSrc) {
            // variable declarations start a new scope
            IrInstructionDeclVarSrc *decl_var_instruction = (IrInstructionDeclVarSrc *)statement_value;
            child_scope = decl_var_instruction->var->child_scope;
        } else if (statement_value != irb->codegen->invalid_instruction && !is_continuation_unreachable) {
            // this statement's value must be void
            ir_mark_gen(ir_build_check_statement_is_void(irb, child_scope, statement_node, statement_value));
        }
    }

    if (is_continuation_unreachable) {
        assert(noreturn_return_value != nullptr);
        if (block_node->data.block.name == nullptr || incoming_blocks.length == 0) {
            return noreturn_return_value;
        }

        ir_set_cursor_at_end_and_append_block(irb, scope_block->end_block);
        return ir_build_phi(irb, parent_scope, block_node, incoming_blocks.length, incoming_blocks.items, incoming_values.items);
    } else {
        incoming_blocks.append(irb->current_basic_block);
        incoming_values.append(ir_mark_gen(ir_build_const_void(irb, parent_scope, block_node)));
    }

    if (block_node->data.block.name != nullptr) {
        ir_gen_defers_for_block(irb, child_scope, outer_block_scope, false);
        ir_mark_gen(ir_build_br(irb, parent_scope, block_node, scope_block->end_block, scope_block->is_comptime));
        ir_set_cursor_at_end_and_append_block(irb, scope_block->end_block);
        return ir_build_phi(irb, parent_scope, block_node, incoming_blocks.length, incoming_blocks.items, incoming_values.items);
    } else {
        ir_gen_defers_for_block(irb, child_scope, outer_block_scope, false);
        return ir_mark_gen(ir_mark_gen(ir_build_const_void(irb, child_scope, block_node)));
    }
}

static IrInstruction *ir_gen_bin_op_id(IrBuilder *irb, Scope *scope, AstNode *node, IrBinOp op_id) {
    Scope *inner_scope = scope;
    if (op_id == IrBinOpArrayCat || op_id == IrBinOpArrayMult) {
        inner_scope = create_comptime_scope(irb->codegen, node, scope);
    }

    IrInstruction *op1 = ir_gen_node(irb, node->data.bin_op_expr.op1, inner_scope);
    IrInstruction *op2 = ir_gen_node(irb, node->data.bin_op_expr.op2, inner_scope);

    if (op1 == irb->codegen->invalid_instruction || op2 == irb->codegen->invalid_instruction)
        return irb->codegen->invalid_instruction;

    return ir_build_bin_op(irb, scope, node, op_id, op1, op2, true);
}

static IrInstruction *ir_gen_assign(IrBuilder *irb, Scope *scope, AstNode *node) {
    IrInstruction *lvalue = ir_gen_node_extra(irb, node->data.bin_op_expr.op1, scope, LValPtr);
    IrInstruction *rvalue = ir_gen_node(irb, node->data.bin_op_expr.op2, scope);

    if (lvalue == irb->codegen->invalid_instruction || rvalue == irb->codegen->invalid_instruction)
        return irb->codegen->invalid_instruction;

    ir_build_store_ptr(irb, scope, node, lvalue, rvalue);
    return ir_build_const_void(irb, scope, node);
}

static IrInstruction *ir_gen_assign_op(IrBuilder *irb, Scope *scope, AstNode *node, IrBinOp op_id) {
    IrInstruction *lvalue = ir_gen_node_extra(irb, node->data.bin_op_expr.op1, scope, LValPtr);
    if (lvalue == irb->codegen->invalid_instruction)
        return lvalue;
    IrInstruction *op1 = ir_build_load_ptr(irb, scope, node->data.bin_op_expr.op1, lvalue);
    IrInstruction *op2 = ir_gen_node(irb, node->data.bin_op_expr.op2, scope);
    if (op2 == irb->codegen->invalid_instruction)
        return op2;
    IrInstruction *result = ir_build_bin_op(irb, scope, node, op_id, op1, op2, true);
    ir_build_store_ptr(irb, scope, node, lvalue, result);
    return ir_build_const_void(irb, scope, node);
}

static IrInstruction *ir_gen_bool_or(IrBuilder *irb, Scope *scope, AstNode *node) {
    assert(node->type == NodeTypeBinOpExpr);

    IrInstruction *val1 = ir_gen_node(irb, node->data.bin_op_expr.op1, scope);
    if (val1 == irb->codegen->invalid_instruction)
        return irb->codegen->invalid_instruction;
    IrBasicBlock *post_val1_block = irb->current_basic_block;

    IrInstruction *is_comptime;
    if (ir_should_inline(irb->exec, scope)) {
        is_comptime = ir_build_const_bool(irb, scope, node, true);
    } else {
        is_comptime = ir_build_test_comptime(irb, scope, node, val1);
    }

    // block for when val1 == false
    IrBasicBlock *false_block = ir_create_basic_block(irb, scope, "BoolOrFalse");
    // block for when val1 == true (don't even evaluate the second part)
    IrBasicBlock *true_block = ir_create_basic_block(irb, scope, "BoolOrTrue");

    ir_build_cond_br(irb, scope, node, val1, true_block, false_block, is_comptime);

    ir_set_cursor_at_end_and_append_block(irb, false_block);
    IrInstruction *val2 = ir_gen_node(irb, node->data.bin_op_expr.op2, scope);
    if (val2 == irb->codegen->invalid_instruction)
        return irb->codegen->invalid_instruction;
    IrBasicBlock *post_val2_block = irb->current_basic_block;

    ir_build_br(irb, scope, node, true_block, is_comptime);

    ir_set_cursor_at_end_and_append_block(irb, true_block);

    IrInstruction **incoming_values = allocate<IrInstruction *>(2);
    incoming_values[0] = val1;
    incoming_values[1] = val2;
    IrBasicBlock **incoming_blocks = allocate<IrBasicBlock *>(2);
    incoming_blocks[0] = post_val1_block;
    incoming_blocks[1] = post_val2_block;

    return ir_build_phi(irb, scope, node, 2, incoming_blocks, incoming_values);
}

static IrInstruction *ir_gen_bool_and(IrBuilder *irb, Scope *scope, AstNode *node) {
    assert(node->type == NodeTypeBinOpExpr);

    IrInstruction *val1 = ir_gen_node(irb, node->data.bin_op_expr.op1, scope);
    if (val1 == irb->codegen->invalid_instruction)
        return irb->codegen->invalid_instruction;
    IrBasicBlock *post_val1_block = irb->current_basic_block;

    IrInstruction *is_comptime;
    if (ir_should_inline(irb->exec, scope)) {
        is_comptime = ir_build_const_bool(irb, scope, node, true);
    } else {
        is_comptime = ir_build_test_comptime(irb, scope, node, val1);
    }

    // block for when val1 == true
    IrBasicBlock *true_block = ir_create_basic_block(irb, scope, "BoolAndTrue");
    // block for when val1 == false (don't even evaluate the second part)
    IrBasicBlock *false_block = ir_create_basic_block(irb, scope, "BoolAndFalse");

    ir_build_cond_br(irb, scope, node, val1, true_block, false_block, is_comptime);

    ir_set_cursor_at_end_and_append_block(irb, true_block);
    IrInstruction *val2 = ir_gen_node(irb, node->data.bin_op_expr.op2, scope);
    if (val2 == irb->codegen->invalid_instruction)
        return irb->codegen->invalid_instruction;
    IrBasicBlock *post_val2_block = irb->current_basic_block;

    ir_build_br(irb, scope, node, false_block, is_comptime);

    ir_set_cursor_at_end_and_append_block(irb, false_block);

    IrInstruction **incoming_values = allocate<IrInstruction *>(2);
    incoming_values[0] = val1;
    incoming_values[1] = val2;
    IrBasicBlock **incoming_blocks = allocate<IrBasicBlock *>(2);
    incoming_blocks[0] = post_val1_block;
    incoming_blocks[1] = post_val2_block;

    return ir_build_phi(irb, scope, node, 2, incoming_blocks, incoming_values);
}

static IrInstruction *ir_gen_orelse(IrBuilder *irb, Scope *parent_scope, AstNode *node) {
    assert(node->type == NodeTypeBinOpExpr);

    AstNode *op1_node = node->data.bin_op_expr.op1;
    AstNode *op2_node = node->data.bin_op_expr.op2;

    IrInstruction *maybe_ptr = ir_gen_node_extra(irb, op1_node, parent_scope, LValPtr);
    if (maybe_ptr == irb->codegen->invalid_instruction)
        return irb->codegen->invalid_instruction;

    IrInstruction *maybe_val = ir_build_load_ptr(irb, parent_scope, node, maybe_ptr);
    IrInstruction *is_non_null = ir_build_test_nonnull(irb, parent_scope, node, maybe_val);

    IrInstruction *is_comptime;
    if (ir_should_inline(irb->exec, parent_scope)) {
        is_comptime = ir_build_const_bool(irb, parent_scope, node, true);
    } else {
        is_comptime = ir_build_test_comptime(irb, parent_scope, node, is_non_null);
    }

    IrBasicBlock *ok_block = ir_create_basic_block(irb, parent_scope, "OptionalNonNull");
    IrBasicBlock *null_block = ir_create_basic_block(irb, parent_scope, "OptionalNull");
    IrBasicBlock *end_block = ir_create_basic_block(irb, parent_scope, "OptionalEnd");
    ir_build_cond_br(irb, parent_scope, node, is_non_null, ok_block, null_block, is_comptime);

    ir_set_cursor_at_end_and_append_block(irb, null_block);
    IrInstruction *null_result = ir_gen_node(irb, op2_node, parent_scope);
    if (null_result == irb->codegen->invalid_instruction)
        return irb->codegen->invalid_instruction;
    IrBasicBlock *after_null_block = irb->current_basic_block;
    if (!instr_is_unreachable(null_result))
        ir_mark_gen(ir_build_br(irb, parent_scope, node, end_block, is_comptime));

    ir_set_cursor_at_end_and_append_block(irb, ok_block);
    IrInstruction *unwrapped_ptr = ir_build_optional_unwrap_ptr(irb, parent_scope, node, maybe_ptr, false);
    IrInstruction *unwrapped_payload = ir_build_load_ptr(irb, parent_scope, node, unwrapped_ptr);
    IrBasicBlock *after_ok_block = irb->current_basic_block;
    ir_build_br(irb, parent_scope, node, end_block, is_comptime);

    ir_set_cursor_at_end_and_append_block(irb, end_block);
    IrInstruction **incoming_values = allocate<IrInstruction *>(2);
    incoming_values[0] = null_result;
    incoming_values[1] = unwrapped_payload;
    IrBasicBlock **incoming_blocks = allocate<IrBasicBlock *>(2);
    incoming_blocks[0] = after_null_block;
    incoming_blocks[1] = after_ok_block;
    return ir_build_phi(irb, parent_scope, node, 2, incoming_blocks, incoming_values);
}

static IrInstruction *ir_gen_error_union(IrBuilder *irb, Scope *parent_scope, AstNode *node) {
    assert(node->type == NodeTypeBinOpExpr);

    AstNode *op1_node = node->data.bin_op_expr.op1;
    AstNode *op2_node = node->data.bin_op_expr.op2;

    IrInstruction *err_set = ir_gen_node(irb, op1_node, parent_scope);
    if (err_set == irb->codegen->invalid_instruction)
        return irb->codegen->invalid_instruction;

    IrInstruction *payload = ir_gen_node(irb, op2_node, parent_scope);
    if (payload == irb->codegen->invalid_instruction)
        return irb->codegen->invalid_instruction;

    return ir_build_error_union(irb, parent_scope, node, err_set, payload);
}

static IrInstruction *ir_gen_bin_op(IrBuilder *irb, Scope *scope, AstNode *node) {
    assert(node->type == NodeTypeBinOpExpr);

    BinOpType bin_op_type = node->data.bin_op_expr.bin_op;
    switch (bin_op_type) {
        case BinOpTypeInvalid:
            zig_unreachable();
        case BinOpTypeAssign:
            return ir_gen_assign(irb, scope, node);
        case BinOpTypeAssignTimes:
            return ir_gen_assign_op(irb, scope, node, IrBinOpMult);
        case BinOpTypeAssignTimesWrap:
            return ir_gen_assign_op(irb, scope, node, IrBinOpMultWrap);
        case BinOpTypeAssignDiv:
            return ir_gen_assign_op(irb, scope, node, IrBinOpDivUnspecified);
        case BinOpTypeAssignMod:
            return ir_gen_assign_op(irb, scope, node, IrBinOpRemUnspecified);
        case BinOpTypeAssignPlus:
            return ir_gen_assign_op(irb, scope, node, IrBinOpAdd);
        case BinOpTypeAssignPlusWrap:
            return ir_gen_assign_op(irb, scope, node, IrBinOpAddWrap);
        case BinOpTypeAssignMinus:
            return ir_gen_assign_op(irb, scope, node, IrBinOpSub);
        case BinOpTypeAssignMinusWrap:
            return ir_gen_assign_op(irb, scope, node, IrBinOpSubWrap);
        case BinOpTypeAssignBitShiftLeft:
            return ir_gen_assign_op(irb, scope, node, IrBinOpBitShiftLeftLossy);
        case BinOpTypeAssignBitShiftRight:
            return ir_gen_assign_op(irb, scope, node, IrBinOpBitShiftRightLossy);
        case BinOpTypeAssignBitAnd:
            return ir_gen_assign_op(irb, scope, node, IrBinOpBinAnd);
        case BinOpTypeAssignBitXor:
            return ir_gen_assign_op(irb, scope, node, IrBinOpBinXor);
        case BinOpTypeAssignBitOr:
            return ir_gen_assign_op(irb, scope, node, IrBinOpBinOr);
        case BinOpTypeAssignMergeErrorSets:
            return ir_gen_assign_op(irb, scope, node, IrBinOpMergeErrorSets);
        case BinOpTypeBoolOr:
            return ir_gen_bool_or(irb, scope, node);
        case BinOpTypeBoolAnd:
            return ir_gen_bool_and(irb, scope, node);
        case BinOpTypeCmpEq:
            return ir_gen_bin_op_id(irb, scope, node, IrBinOpCmpEq);
        case BinOpTypeCmpNotEq:
            return ir_gen_bin_op_id(irb, scope, node, IrBinOpCmpNotEq);
        case BinOpTypeCmpLessThan:
            return ir_gen_bin_op_id(irb, scope, node, IrBinOpCmpLessThan);
        case BinOpTypeCmpGreaterThan:
            return ir_gen_bin_op_id(irb, scope, node, IrBinOpCmpGreaterThan);
        case BinOpTypeCmpLessOrEq:
            return ir_gen_bin_op_id(irb, scope, node, IrBinOpCmpLessOrEq);
        case BinOpTypeCmpGreaterOrEq:
            return ir_gen_bin_op_id(irb, scope, node, IrBinOpCmpGreaterOrEq);
        case BinOpTypeBinOr:
            return ir_gen_bin_op_id(irb, scope, node, IrBinOpBinOr);
        case BinOpTypeBinXor:
            return ir_gen_bin_op_id(irb, scope, node, IrBinOpBinXor);
        case BinOpTypeBinAnd:
            return ir_gen_bin_op_id(irb, scope, node, IrBinOpBinAnd);
        case BinOpTypeBitShiftLeft:
            return ir_gen_bin_op_id(irb, scope, node, IrBinOpBitShiftLeftLossy);
        case BinOpTypeBitShiftRight:
            return ir_gen_bin_op_id(irb, scope, node, IrBinOpBitShiftRightLossy);
        case BinOpTypeAdd:
            return ir_gen_bin_op_id(irb, scope, node, IrBinOpAdd);
        case BinOpTypeAddWrap:
            return ir_gen_bin_op_id(irb, scope, node, IrBinOpAddWrap);
        case BinOpTypeSub:
            return ir_gen_bin_op_id(irb, scope, node, IrBinOpSub);
        case BinOpTypeSubWrap:
            return ir_gen_bin_op_id(irb, scope, node, IrBinOpSubWrap);
        case BinOpTypeMult:
            return ir_gen_bin_op_id(irb, scope, node, IrBinOpMult);
        case BinOpTypeMultWrap:
            return ir_gen_bin_op_id(irb, scope, node, IrBinOpMultWrap);
        case BinOpTypeDiv:
            return ir_gen_bin_op_id(irb, scope, node, IrBinOpDivUnspecified);
        case BinOpTypeMod:
            return ir_gen_bin_op_id(irb, scope, node, IrBinOpRemUnspecified);
        case BinOpTypeArrayCat:
            return ir_gen_bin_op_id(irb, scope, node, IrBinOpArrayCat);
        case BinOpTypeArrayMult:
            return ir_gen_bin_op_id(irb, scope, node, IrBinOpArrayMult);
        case BinOpTypeMergeErrorSets:
            return ir_gen_bin_op_id(irb, scope, node, IrBinOpMergeErrorSets);
        case BinOpTypeUnwrapOptional:
            return ir_gen_orelse(irb, scope, node);
        case BinOpTypeErrorUnion:
            return ir_gen_error_union(irb, scope, node);
    }
    zig_unreachable();
}

static IrInstruction *ir_gen_int_lit(IrBuilder *irb, Scope *scope, AstNode *node) {
    assert(node->type == NodeTypeIntLiteral);

    return ir_build_const_bigint(irb, scope, node, node->data.int_literal.bigint);
}

static IrInstruction *ir_gen_float_lit(IrBuilder *irb, Scope *scope, AstNode *node) {
    assert(node->type == NodeTypeFloatLiteral);

    if (node->data.float_literal.overflow) {
        add_node_error(irb->codegen, node, buf_sprintf("float literal out of range of any type"));
        return irb->codegen->invalid_instruction;
    }

    return ir_build_const_bigfloat(irb, scope, node, node->data.float_literal.bigfloat);
}

static IrInstruction *ir_gen_char_lit(IrBuilder *irb, Scope *scope, AstNode *node) {
    assert(node->type == NodeTypeCharLiteral);

    return ir_build_const_uint(irb, scope, node, node->data.char_literal.value);
}

static IrInstruction *ir_gen_null_literal(IrBuilder *irb, Scope *scope, AstNode *node) {
    assert(node->type == NodeTypeNullLiteral);

    return ir_build_const_null(irb, scope, node);
}

static void populate_invalid_variable_in_scope(CodeGen *g, Scope *scope, AstNode *node, Buf *var_name) {
    ScopeDecls *scope_decls = nullptr;
    while (scope != nullptr) {
        if (scope->id == ScopeIdDecls) {
            scope_decls = reinterpret_cast<ScopeDecls *>(scope);
        }
        scope = scope->parent;
    }
    TldVar *tld_var = allocate<TldVar>(1);
    init_tld(&tld_var->base, TldIdVar, var_name, VisibModPub, node, &scope_decls->base);
    tld_var->base.resolution = TldResolutionInvalid;
    tld_var->var = add_variable(g, node, &scope_decls->base, var_name, false, 
            &g->invalid_instruction->value, &tld_var->base, g->builtin_types.entry_invalid);
    scope_decls->decl_table.put(var_name, &tld_var->base);
}

static IrInstruction *ir_gen_symbol(IrBuilder *irb, Scope *scope, AstNode *node, LVal lval) {
    Error err;
    assert(node->type == NodeTypeSymbol);

    Buf *variable_name = node->data.symbol_expr.symbol;

    if (buf_eql_str(variable_name, "_") && lval == LValPtr) {
        IrInstructionConst *const_instruction = ir_build_instruction<IrInstructionConst>(irb, scope, node);
        const_instruction->base.value.type = get_pointer_to_type(irb->codegen,
                irb->codegen->builtin_types.entry_void, false);
        const_instruction->base.value.special = ConstValSpecialStatic;
        const_instruction->base.value.data.x_ptr.special = ConstPtrSpecialDiscard;
        return &const_instruction->base;
    }

    ZigType *primitive_type;
    if ((err = get_primitive_type(irb->codegen, variable_name, &primitive_type))) {
        if (err == ErrorOverflow) {
            add_node_error(irb->codegen, node,
                buf_sprintf("primitive integer type '%s' exceeds maximum bit width of 65535",
                    buf_ptr(variable_name)));
            return irb->codegen->invalid_instruction;
        }
        assert(err == ErrorPrimitiveTypeNotFound);
    } else {
        IrInstruction *value = ir_build_const_type(irb, scope, node, primitive_type);
        if (lval == LValPtr) {
            return ir_build_ref(irb, scope, node, value, false, false);
        } else {
            return value;
        }
    }

    ScopeFnDef *crossed_fndef_scope;
    ZigVar *var = find_variable(irb->codegen, scope, variable_name, &crossed_fndef_scope);
    if (var) {
        IrInstruction *var_ptr = ir_build_var_ptr_x(irb, scope, node, var, crossed_fndef_scope);
        if (lval == LValPtr)
            return var_ptr;
        else
            return ir_build_load_ptr(irb, scope, node, var_ptr);
    }

    Tld *tld = find_decl(irb->codegen, scope, variable_name);
    if (tld)
        return ir_build_decl_ref(irb, scope, node, tld, lval);

    if (node->owner->any_imports_failed) {
        // skip the error message since we had a failing import in this file
        // if an import breaks we don't need redundant undeclared identifier errors
        return irb->codegen->invalid_instruction;
    }

    // put a variable of same name with invalid type in global scope
    // so that future references to this same name will find a variable with an invalid type
    populate_invalid_variable_in_scope(irb->codegen, scope, node, variable_name);
    add_node_error(irb->codegen, node, buf_sprintf("use of undeclared identifier '%s'", buf_ptr(variable_name)));
    return irb->codegen->invalid_instruction;
}

static IrInstruction *ir_gen_array_access(IrBuilder *irb, Scope *scope, AstNode *node, LVal lval) {
    assert(node->type == NodeTypeArrayAccessExpr);

    AstNode *array_ref_node = node->data.array_access_expr.array_ref_expr;
    IrInstruction *array_ref_instruction = ir_gen_node_extra(irb, array_ref_node, scope, LValPtr);
    if (array_ref_instruction == irb->codegen->invalid_instruction)
        return array_ref_instruction;

    AstNode *subscript_node = node->data.array_access_expr.subscript;
    IrInstruction *subscript_instruction = ir_gen_node(irb, subscript_node, scope);
    if (subscript_instruction == irb->codegen->invalid_instruction)
        return subscript_instruction;

    IrInstruction *ptr_instruction = ir_build_elem_ptr(irb, scope, node, array_ref_instruction,
            subscript_instruction, true, PtrLenSingle);
    if (lval == LValPtr)
        return ptr_instruction;

    return ir_build_load_ptr(irb, scope, node, ptr_instruction);
}

static IrInstruction *ir_gen_field_access(IrBuilder *irb, Scope *scope, AstNode *node) {
    assert(node->type == NodeTypeFieldAccessExpr);

    AstNode *container_ref_node = node->data.field_access_expr.struct_expr;
    Buf *field_name = node->data.field_access_expr.field_name;

    IrInstruction *container_ref_instruction = ir_gen_node_extra(irb, container_ref_node, scope, LValPtr);
    if (container_ref_instruction == irb->codegen->invalid_instruction)
        return container_ref_instruction;

    return ir_build_field_ptr(irb, scope, node, container_ref_instruction, field_name);
}

static IrInstruction *ir_gen_overflow_op(IrBuilder *irb, Scope *scope, AstNode *node, IrOverflowOp op) {
    assert(node->type == NodeTypeFnCallExpr);

    AstNode *type_node = node->data.fn_call_expr.params.at(0);
    AstNode *op1_node = node->data.fn_call_expr.params.at(1);
    AstNode *op2_node = node->data.fn_call_expr.params.at(2);
    AstNode *result_ptr_node = node->data.fn_call_expr.params.at(3);


    IrInstruction *type_value = ir_gen_node(irb, type_node, scope);
    if (type_value == irb->codegen->invalid_instruction)
        return irb->codegen->invalid_instruction;

    IrInstruction *op1 = ir_gen_node(irb, op1_node, scope);
    if (op1 == irb->codegen->invalid_instruction)
        return irb->codegen->invalid_instruction;

    IrInstruction *op2 = ir_gen_node(irb, op2_node, scope);
    if (op2 == irb->codegen->invalid_instruction)
        return irb->codegen->invalid_instruction;

    IrInstruction *result_ptr = ir_gen_node(irb, result_ptr_node, scope);
    if (result_ptr == irb->codegen->invalid_instruction)
        return irb->codegen->invalid_instruction;

    return ir_build_overflow_op(irb, scope, node, op, type_value, op1, op2, result_ptr, nullptr);
}

static IrInstruction *ir_gen_this(IrBuilder *irb, Scope *orig_scope, AstNode *node) {
    for (Scope *it_scope = orig_scope; it_scope != nullptr; it_scope = it_scope->parent) {
        if (it_scope->id == ScopeIdDecls) {
            ScopeDecls *decls_scope = (ScopeDecls *)it_scope;
            ZigType *container_type = decls_scope->container_type;
            if (container_type != nullptr) {
                return ir_build_const_type(irb, orig_scope, node, container_type);
            } else {
                return ir_build_const_import(irb, orig_scope, node, decls_scope->import);
            }
        }
    }
    zig_unreachable();
}

static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, Scope *scope, AstNode *node, LVal lval) {
    assert(node->type == NodeTypeFnCallExpr);

    AstNode *fn_ref_expr = node->data.fn_call_expr.fn_ref_expr;
    Buf *name = fn_ref_expr->data.symbol_expr.symbol;
    auto entry = irb->codegen->builtin_fn_table.maybe_get(name);

    if (!entry) { // new built in not found
        add_node_error(irb->codegen, node,
                buf_sprintf("invalid builtin function: '%s'", buf_ptr(name)));
        return irb->codegen->invalid_instruction;
    }

    BuiltinFnEntry *builtin_fn = entry->value;
    size_t actual_param_count = node->data.fn_call_expr.params.length;

    if (builtin_fn->param_count != SIZE_MAX && builtin_fn->param_count != actual_param_count) {
        add_node_error(irb->codegen, node,
                buf_sprintf("expected %" ZIG_PRI_usize " arguments, found %" ZIG_PRI_usize,
                    builtin_fn->param_count, actual_param_count));
        return irb->codegen->invalid_instruction;
    }

    bool is_async = exec_is_async(irb->exec);

    switch (builtin_fn->id) {
        case BuiltinFnIdInvalid:
            zig_unreachable();
        case BuiltinFnIdTypeof:
            {
                AstNode *arg_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg = ir_gen_node(irb, arg_node, scope);
                if (arg == irb->codegen->invalid_instruction)
                    return arg;

                IrInstruction *type_of = ir_build_typeof(irb, scope, node, arg);
                return ir_lval_wrap(irb, scope, type_of, lval);
            }
        case BuiltinFnIdSetCold:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                IrInstruction *set_cold = ir_build_set_cold(irb, scope, node, arg0_value);
                return ir_lval_wrap(irb, scope, set_cold, lval);
            }
        case BuiltinFnIdSetRuntimeSafety:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                IrInstruction *set_safety = ir_build_set_runtime_safety(irb, scope, node, arg0_value);
                return ir_lval_wrap(irb, scope, set_safety, lval);
            }
        case BuiltinFnIdSetFloatMode:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                IrInstruction *set_float_mode = ir_build_set_float_mode(irb, scope, node, arg0_value);
                return ir_lval_wrap(irb, scope, set_float_mode, lval);
            }
        case BuiltinFnIdSizeof:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                IrInstruction *size_of = ir_build_size_of(irb, scope, node, arg0_value);
                return ir_lval_wrap(irb, scope, size_of, lval);
            }
        case BuiltinFnIdCtz:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                IrInstruction *ctz = ir_build_ctz(irb, scope, node, arg0_value);
                return ir_lval_wrap(irb, scope, ctz, lval);
            }
        case BuiltinFnIdPopCount:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                IrInstruction *instr = ir_build_pop_count(irb, scope, node, arg0_value);
                return ir_lval_wrap(irb, scope, instr, lval);
            }
        case BuiltinFnIdClz:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                IrInstruction *clz = ir_build_clz(irb, scope, node, arg0_value);
                return ir_lval_wrap(irb, scope, clz, lval);
            }
        case BuiltinFnIdImport:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                IrInstruction *import = ir_build_import(irb, scope, node, arg0_value);
                return ir_lval_wrap(irb, scope, import, lval);
            }
        case BuiltinFnIdCImport:
            {
                IrInstruction *c_import = ir_build_c_import(irb, scope, node);
                return ir_lval_wrap(irb, scope, c_import, lval);
            }
        case BuiltinFnIdCInclude:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                if (!exec_c_import_buf(irb->exec)) {
                    add_node_error(irb->codegen, node, buf_sprintf("C include valid only inside C import block"));
                    return irb->codegen->invalid_instruction;
                }

                IrInstruction *c_include = ir_build_c_include(irb, scope, node, arg0_value);
                return ir_lval_wrap(irb, scope, c_include, lval);
            }
        case BuiltinFnIdCDefine:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
                IrInstruction *arg1_value = ir_gen_node(irb, arg1_node, scope);
                if (arg1_value == irb->codegen->invalid_instruction)
                    return arg1_value;

                if (!exec_c_import_buf(irb->exec)) {
                    add_node_error(irb->codegen, node, buf_sprintf("C define valid only inside C import block"));
                    return irb->codegen->invalid_instruction;
                }

                IrInstruction *c_define = ir_build_c_define(irb, scope, node, arg0_value, arg1_value);
                return ir_lval_wrap(irb, scope, c_define, lval);
            }
        case BuiltinFnIdCUndef:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                if (!exec_c_import_buf(irb->exec)) {
                    add_node_error(irb->codegen, node, buf_sprintf("C undef valid only inside C import block"));
                    return irb->codegen->invalid_instruction;
                }

                IrInstruction *c_undef = ir_build_c_undef(irb, scope, node, arg0_value);
                return ir_lval_wrap(irb, scope, c_undef, lval);
            }
        case BuiltinFnIdCompileErr:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                IrInstruction *compile_err = ir_build_compile_err(irb, scope, node, arg0_value);
                return ir_lval_wrap(irb, scope, compile_err, lval);
            }
        case BuiltinFnIdCompileLog:
            {
                IrInstruction **args = allocate<IrInstruction*>(actual_param_count);

                for (size_t i = 0; i < actual_param_count; i += 1) {
                    AstNode *arg_node = node->data.fn_call_expr.params.at(i);
                    args[i] = ir_gen_node(irb, arg_node, scope);
                    if (args[i] == irb->codegen->invalid_instruction)
                        return irb->codegen->invalid_instruction;
                }

                IrInstruction *compile_log = ir_build_compile_log(irb, scope, node, actual_param_count, args);
                return ir_lval_wrap(irb, scope, compile_log, lval);
            }
        case BuiltinFnIdErrName:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                IrInstruction *err_name = ir_build_err_name(irb, scope, node, arg0_value);
                return ir_lval_wrap(irb, scope, err_name, lval);
            }
        case BuiltinFnIdEmbedFile:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                IrInstruction *embed_file = ir_build_embed_file(irb, scope, node, arg0_value);
                return ir_lval_wrap(irb, scope, embed_file, lval);
            }
        case BuiltinFnIdCmpxchgWeak:
        case BuiltinFnIdCmpxchgStrong:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
                IrInstruction *arg1_value = ir_gen_node(irb, arg1_node, scope);
                if (arg1_value == irb->codegen->invalid_instruction)
                    return arg1_value;

                AstNode *arg2_node = node->data.fn_call_expr.params.at(2);
                IrInstruction *arg2_value = ir_gen_node(irb, arg2_node, scope);
                if (arg2_value == irb->codegen->invalid_instruction)
                    return arg2_value;

                AstNode *arg3_node = node->data.fn_call_expr.params.at(3);
                IrInstruction *arg3_value = ir_gen_node(irb, arg3_node, scope);
                if (arg3_value == irb->codegen->invalid_instruction)
                    return arg3_value;

                AstNode *arg4_node = node->data.fn_call_expr.params.at(4);
                IrInstruction *arg4_value = ir_gen_node(irb, arg4_node, scope);
                if (arg4_value == irb->codegen->invalid_instruction)
                    return arg4_value;

                AstNode *arg5_node = node->data.fn_call_expr.params.at(5);
                IrInstruction *arg5_value = ir_gen_node(irb, arg5_node, scope);
                if (arg5_value == irb->codegen->invalid_instruction)
                    return arg5_value;

                IrInstruction *cmpxchg = ir_build_cmpxchg_src(irb, scope, node, arg0_value, arg1_value,
                    arg2_value, arg3_value, arg4_value, arg5_value, (builtin_fn->id == BuiltinFnIdCmpxchgWeak));
                return ir_lval_wrap(irb, scope, cmpxchg, lval);
            }
        case BuiltinFnIdFence:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                IrInstruction *fence = ir_build_fence(irb, scope, node, arg0_value, AtomicOrderUnordered);
                return ir_lval_wrap(irb, scope, fence, lval);
            }
        case BuiltinFnIdDivExact:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
                IrInstruction *arg1_value = ir_gen_node(irb, arg1_node, scope);
                if (arg1_value == irb->codegen->invalid_instruction)
                    return arg1_value;

                IrInstruction *bin_op = ir_build_bin_op(irb, scope, node, IrBinOpDivExact, arg0_value, arg1_value, true);
                return ir_lval_wrap(irb, scope, bin_op, lval);
            }
        case BuiltinFnIdDivTrunc:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
                IrInstruction *arg1_value = ir_gen_node(irb, arg1_node, scope);
                if (arg1_value == irb->codegen->invalid_instruction)
                    return arg1_value;

                IrInstruction *bin_op = ir_build_bin_op(irb, scope, node, IrBinOpDivTrunc, arg0_value, arg1_value, true);
                return ir_lval_wrap(irb, scope, bin_op, lval);
            }
        case BuiltinFnIdDivFloor:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
                IrInstruction *arg1_value = ir_gen_node(irb, arg1_node, scope);
                if (arg1_value == irb->codegen->invalid_instruction)
                    return arg1_value;

                IrInstruction *bin_op = ir_build_bin_op(irb, scope, node, IrBinOpDivFloor, arg0_value, arg1_value, true);
                return ir_lval_wrap(irb, scope, bin_op, lval);
            }
        case BuiltinFnIdRem:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
                IrInstruction *arg1_value = ir_gen_node(irb, arg1_node, scope);
                if (arg1_value == irb->codegen->invalid_instruction)
                    return arg1_value;

                IrInstruction *bin_op = ir_build_bin_op(irb, scope, node, IrBinOpRemRem, arg0_value, arg1_value, true);
                return ir_lval_wrap(irb, scope, bin_op, lval);
            }
        case BuiltinFnIdMod:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
                IrInstruction *arg1_value = ir_gen_node(irb, arg1_node, scope);
                if (arg1_value == irb->codegen->invalid_instruction)
                    return arg1_value;

                IrInstruction *bin_op = ir_build_bin_op(irb, scope, node, IrBinOpRemMod, arg0_value, arg1_value, true);
                return ir_lval_wrap(irb, scope, bin_op, lval);
            }
        case BuiltinFnIdSqrt:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
                IrInstruction *arg1_value = ir_gen_node(irb, arg1_node, scope);
                if (arg1_value == irb->codegen->invalid_instruction)
                    return arg1_value;

                IrInstruction *ir_sqrt = ir_build_sqrt(irb, scope, node, arg0_value, arg1_value);
                return ir_lval_wrap(irb, scope, ir_sqrt, lval);
            }
        case BuiltinFnIdTruncate:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
                IrInstruction *arg1_value = ir_gen_node(irb, arg1_node, scope);
                if (arg1_value == irb->codegen->invalid_instruction)
                    return arg1_value;

                IrInstruction *truncate = ir_build_truncate(irb, scope, node, arg0_value, arg1_value);
                return ir_lval_wrap(irb, scope, truncate, lval);
            }
        case BuiltinFnIdIntCast:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
                IrInstruction *arg1_value = ir_gen_node(irb, arg1_node, scope);
                if (arg1_value == irb->codegen->invalid_instruction)
                    return arg1_value;

                IrInstruction *result = ir_build_int_cast(irb, scope, node, arg0_value, arg1_value);
                return ir_lval_wrap(irb, scope, result, lval);
            }
        case BuiltinFnIdFloatCast:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
                IrInstruction *arg1_value = ir_gen_node(irb, arg1_node, scope);
                if (arg1_value == irb->codegen->invalid_instruction)
                    return arg1_value;

                IrInstruction *result = ir_build_float_cast(irb, scope, node, arg0_value, arg1_value);
                return ir_lval_wrap(irb, scope, result, lval);
            }
        case BuiltinFnIdErrSetCast:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
                IrInstruction *arg1_value = ir_gen_node(irb, arg1_node, scope);
                if (arg1_value == irb->codegen->invalid_instruction)
                    return arg1_value;

                IrInstruction *result = ir_build_err_set_cast(irb, scope, node, arg0_value, arg1_value);
                return ir_lval_wrap(irb, scope, result, lval);
            }
        case BuiltinFnIdFromBytes:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
                IrInstruction *arg1_value = ir_gen_node(irb, arg1_node, scope);
                if (arg1_value == irb->codegen->invalid_instruction)
                    return arg1_value;

                IrInstruction *result = ir_build_from_bytes(irb, scope, node, arg0_value, arg1_value);
                return ir_lval_wrap(irb, scope, result, lval);
            }
        case BuiltinFnIdToBytes:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                IrInstruction *result = ir_build_to_bytes(irb, scope, node, arg0_value);
                return ir_lval_wrap(irb, scope, result, lval);
            }
        case BuiltinFnIdIntToFloat:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
                IrInstruction *arg1_value = ir_gen_node(irb, arg1_node, scope);
                if (arg1_value == irb->codegen->invalid_instruction)
                    return arg1_value;

                IrInstruction *result = ir_build_int_to_float(irb, scope, node, arg0_value, arg1_value);
                return ir_lval_wrap(irb, scope, result, lval);
            }
        case BuiltinFnIdFloatToInt:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
                IrInstruction *arg1_value = ir_gen_node(irb, arg1_node, scope);
                if (arg1_value == irb->codegen->invalid_instruction)
                    return arg1_value;

                IrInstruction *result = ir_build_float_to_int(irb, scope, node, arg0_value, arg1_value);
                return ir_lval_wrap(irb, scope, result, lval);
            }
        case BuiltinFnIdErrToInt:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                IrInstruction *result = ir_build_err_to_int(irb, scope, node, arg0_value);
                return ir_lval_wrap(irb, scope, result, lval);
            }
        case BuiltinFnIdIntToErr:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                IrInstruction *result = ir_build_int_to_err(irb, scope, node, arg0_value);
                return ir_lval_wrap(irb, scope, result, lval);
            }
        case BuiltinFnIdBoolToInt:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                IrInstruction *result = ir_build_bool_to_int(irb, scope, node, arg0_value);
                return ir_lval_wrap(irb, scope, result, lval);
            }
        case BuiltinFnIdIntType:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
                IrInstruction *arg1_value = ir_gen_node(irb, arg1_node, scope);
                if (arg1_value == irb->codegen->invalid_instruction)
                    return arg1_value;

                IrInstruction *int_type = ir_build_int_type(irb, scope, node, arg0_value, arg1_value);
                return ir_lval_wrap(irb, scope, int_type, lval);
            }
        case BuiltinFnIdVectorType:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
                IrInstruction *arg1_value = ir_gen_node(irb, arg1_node, scope);
                if (arg1_value == irb->codegen->invalid_instruction)
                    return arg1_value;

                IrInstruction *vector_type = ir_build_vector_type(irb, scope, node, arg0_value, arg1_value);
                return ir_lval_wrap(irb, scope, vector_type, lval);
            }
        case BuiltinFnIdMemcpy:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
                IrInstruction *arg1_value = ir_gen_node(irb, arg1_node, scope);
                if (arg1_value == irb->codegen->invalid_instruction)
                    return arg1_value;

                AstNode *arg2_node = node->data.fn_call_expr.params.at(2);
                IrInstruction *arg2_value = ir_gen_node(irb, arg2_node, scope);
                if (arg2_value == irb->codegen->invalid_instruction)
                    return arg2_value;

                IrInstruction *ir_memcpy = ir_build_memcpy(irb, scope, node, arg0_value, arg1_value, arg2_value);
                return ir_lval_wrap(irb, scope, ir_memcpy, lval);
            }
        case BuiltinFnIdMemset:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
                IrInstruction *arg1_value = ir_gen_node(irb, arg1_node, scope);
                if (arg1_value == irb->codegen->invalid_instruction)
                    return arg1_value;

                AstNode *arg2_node = node->data.fn_call_expr.params.at(2);
                IrInstruction *arg2_value = ir_gen_node(irb, arg2_node, scope);
                if (arg2_value == irb->codegen->invalid_instruction)
                    return arg2_value;

                IrInstruction *ir_memset = ir_build_memset(irb, scope, node, arg0_value, arg1_value, arg2_value);
                return ir_lval_wrap(irb, scope, ir_memset, lval);
            }
        case BuiltinFnIdMemberCount:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                IrInstruction *member_count = ir_build_member_count(irb, scope, node, arg0_value);
                return ir_lval_wrap(irb, scope, member_count, lval);
            }
        case BuiltinFnIdMemberType:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
                IrInstruction *arg1_value = ir_gen_node(irb, arg1_node, scope);
                if (arg1_value == irb->codegen->invalid_instruction)
                    return arg1_value;


                IrInstruction *member_type = ir_build_member_type(irb, scope, node, arg0_value, arg1_value);
                return ir_lval_wrap(irb, scope, member_type, lval);
            }
        case BuiltinFnIdMemberName:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
                IrInstruction *arg1_value = ir_gen_node(irb, arg1_node, scope);
                if (arg1_value == irb->codegen->invalid_instruction)
                    return arg1_value;


                IrInstruction *member_name = ir_build_member_name(irb, scope, node, arg0_value, arg1_value);
                return ir_lval_wrap(irb, scope, member_name, lval);
            }
        case BuiltinFnIdField:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node_extra(irb, arg0_node, scope, LValPtr);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
                IrInstruction *arg1_value = ir_gen_node(irb, arg1_node, scope);
                if (arg1_value == irb->codegen->invalid_instruction)
                    return arg1_value;

                IrInstruction *ptr_instruction = ir_build_field_ptr_instruction(irb, scope, node, arg0_value, arg1_value);

                if (lval == LValPtr)
                    return ptr_instruction;

                return ir_build_load_ptr(irb, scope, node, ptr_instruction);
            }
        case BuiltinFnIdTypeInfo:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                IrInstruction *type_info = ir_build_type_info(irb, scope, node, arg0_value);
                return ir_lval_wrap(irb, scope, type_info, lval);
            }
        case BuiltinFnIdBreakpoint:
            return ir_lval_wrap(irb, scope, ir_build_breakpoint(irb, scope, node), lval);
        case BuiltinFnIdReturnAddress:
            return ir_lval_wrap(irb, scope, ir_build_return_address(irb, scope, node), lval);
        case BuiltinFnIdFrameAddress:
            return ir_lval_wrap(irb, scope, ir_build_frame_address(irb, scope, node), lval);
        case BuiltinFnIdHandle:
            if (!irb->exec->fn_entry) {
                add_node_error(irb->codegen, node, buf_sprintf("@handle() called outside of function definition"));
                return irb->codegen->invalid_instruction;
            }
            if (!is_async) {
                add_node_error(irb->codegen, node, buf_sprintf("@handle() in non-async function"));
                return irb->codegen->invalid_instruction;
            }
            return ir_lval_wrap(irb, scope, ir_build_handle(irb, scope, node), lval);
        case BuiltinFnIdAlignOf:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                IrInstruction *align_of = ir_build_align_of(irb, scope, node, arg0_value);
                return ir_lval_wrap(irb, scope, align_of, lval);
            }
        case BuiltinFnIdAddWithOverflow:
            return ir_lval_wrap(irb, scope, ir_gen_overflow_op(irb, scope, node, IrOverflowOpAdd), lval);
        case BuiltinFnIdSubWithOverflow:
            return ir_lval_wrap(irb, scope, ir_gen_overflow_op(irb, scope, node, IrOverflowOpSub), lval);
        case BuiltinFnIdMulWithOverflow:
            return ir_lval_wrap(irb, scope, ir_gen_overflow_op(irb, scope, node, IrOverflowOpMul), lval);
        case BuiltinFnIdShlWithOverflow:
            return ir_lval_wrap(irb, scope, ir_gen_overflow_op(irb, scope, node, IrOverflowOpShl), lval);
        case BuiltinFnIdTypeName:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                IrInstruction *type_name = ir_build_type_name(irb, scope, node, arg0_value);
                return ir_lval_wrap(irb, scope, type_name, lval);
            }
        case BuiltinFnIdPanic:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                IrInstruction *panic = ir_build_panic(irb, scope, node, arg0_value);
                return ir_lval_wrap(irb, scope, panic, lval);
            }
        case BuiltinFnIdPtrCast:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
                IrInstruction *arg1_value = ir_gen_node(irb, arg1_node, scope);
                if (arg1_value == irb->codegen->invalid_instruction)
                    return arg1_value;

                IrInstruction *ptr_cast = ir_build_ptr_cast_src(irb, scope, node, arg0_value, arg1_value, true);
                return ir_lval_wrap(irb, scope, ptr_cast, lval);
            }
        case BuiltinFnIdBitCast:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
                IrInstruction *arg1_value = ir_gen_node(irb, arg1_node, scope);
                if (arg1_value == irb->codegen->invalid_instruction)
                    return arg1_value;

                IrInstruction *bit_cast = ir_build_bit_cast(irb, scope, node, arg0_value, arg1_value);
                return ir_lval_wrap(irb, scope, bit_cast, lval);
            }
        case BuiltinFnIdIntToPtr:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
                IrInstruction *arg1_value = ir_gen_node(irb, arg1_node, scope);
                if (arg1_value == irb->codegen->invalid_instruction)
                    return arg1_value;

                IrInstruction *int_to_ptr = ir_build_int_to_ptr(irb, scope, node, arg0_value, arg1_value);
                return ir_lval_wrap(irb, scope, int_to_ptr, lval);
            }
        case BuiltinFnIdPtrToInt:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                IrInstruction *ptr_to_int = ir_build_ptr_to_int(irb, scope, node, arg0_value);
                return ir_lval_wrap(irb, scope, ptr_to_int, lval);
            }
        case BuiltinFnIdTagName:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                IrInstruction *actual_tag = ir_build_union_tag(irb, scope, node, arg0_value);
                IrInstruction *tag_name = ir_build_tag_name(irb, scope, node, actual_tag);
                return ir_lval_wrap(irb, scope, tag_name, lval);
            }
        case BuiltinFnIdTagType:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                IrInstruction *tag_type = ir_build_tag_type(irb, scope, node, arg0_value);
                return ir_lval_wrap(irb, scope, tag_type, lval);
            }
        case BuiltinFnIdFieldParentPtr:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
                IrInstruction *arg1_value = ir_gen_node(irb, arg1_node, scope);
                if (arg1_value == irb->codegen->invalid_instruction)
                    return arg1_value;

                AstNode *arg2_node = node->data.fn_call_expr.params.at(2);
                IrInstruction *arg2_value = ir_gen_node(irb, arg2_node, scope);
                if (arg2_value == irb->codegen->invalid_instruction)
                    return arg2_value;

                IrInstruction *field_parent_ptr = ir_build_field_parent_ptr(irb, scope, node, arg0_value, arg1_value, arg2_value, nullptr);
                return ir_lval_wrap(irb, scope, field_parent_ptr, lval);
            }
        case BuiltinFnIdByteOffsetOf:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
                IrInstruction *arg1_value = ir_gen_node(irb, arg1_node, scope);
                if (arg1_value == irb->codegen->invalid_instruction)
                    return arg1_value;

                IrInstruction *offset_of = ir_build_byte_offset_of(irb, scope, node, arg0_value, arg1_value);
                return ir_lval_wrap(irb, scope, offset_of, lval);
            }
        case BuiltinFnIdBitOffsetOf:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
                IrInstruction *arg1_value = ir_gen_node(irb, arg1_node, scope);
                if (arg1_value == irb->codegen->invalid_instruction)
                    return arg1_value;

                IrInstruction *offset_of = ir_build_bit_offset_of(irb, scope, node, arg0_value, arg1_value);
                return ir_lval_wrap(irb, scope, offset_of, lval);
            }
        case BuiltinFnIdInlineCall:
        case BuiltinFnIdNoInlineCall:
            {
                if (node->data.fn_call_expr.params.length == 0) {
                    add_node_error(irb->codegen, node, buf_sprintf("expected at least 1 argument, found 0"));
                    return irb->codegen->invalid_instruction;
                }

                AstNode *fn_ref_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *fn_ref = ir_gen_node(irb, fn_ref_node, scope);
                if (fn_ref == irb->codegen->invalid_instruction)
                    return fn_ref;

                size_t arg_count = node->data.fn_call_expr.params.length - 1;

                IrInstruction **args = allocate<IrInstruction*>(arg_count);
                for (size_t i = 0; i < arg_count; i += 1) {
                    AstNode *arg_node = node->data.fn_call_expr.params.at(i + 1);
                    args[i] = ir_gen_node(irb, arg_node, scope);
                    if (args[i] == irb->codegen->invalid_instruction)
                        return args[i];
                }
                FnInline fn_inline = (builtin_fn->id == BuiltinFnIdInlineCall) ? FnInlineAlways : FnInlineNever;

                IrInstruction *call = ir_build_call(irb, scope, node, nullptr, fn_ref, arg_count, args, false, fn_inline, false, nullptr, nullptr);
                return ir_lval_wrap(irb, scope, call, lval);
            }
        case BuiltinFnIdNewStackCall:
            {
                if (node->data.fn_call_expr.params.length == 0) {
                    add_node_error(irb->codegen, node, buf_sprintf("expected at least 1 argument, found 0"));
                    return irb->codegen->invalid_instruction;
                }

                AstNode *new_stack_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *new_stack = ir_gen_node(irb, new_stack_node, scope);
                if (new_stack == irb->codegen->invalid_instruction)
                    return new_stack;

                AstNode *fn_ref_node = node->data.fn_call_expr.params.at(1);
                IrInstruction *fn_ref = ir_gen_node(irb, fn_ref_node, scope);
                if (fn_ref == irb->codegen->invalid_instruction)
                    return fn_ref;

                size_t arg_count = node->data.fn_call_expr.params.length - 2;

                IrInstruction **args = allocate<IrInstruction*>(arg_count);
                for (size_t i = 0; i < arg_count; i += 1) {
                    AstNode *arg_node = node->data.fn_call_expr.params.at(i + 2);
                    args[i] = ir_gen_node(irb, arg_node, scope);
                    if (args[i] == irb->codegen->invalid_instruction)
                        return args[i];
                }

                IrInstruction *call = ir_build_call(irb, scope, node, nullptr, fn_ref, arg_count, args, false, FnInlineAuto, false, nullptr, new_stack);
                return ir_lval_wrap(irb, scope, call, lval);
            }
        case BuiltinFnIdTypeId:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                IrInstruction *type_id = ir_build_type_id(irb, scope, node, arg0_value);
                return ir_lval_wrap(irb, scope, type_id, lval);
            }
        case BuiltinFnIdShlExact:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
                IrInstruction *arg1_value = ir_gen_node(irb, arg1_node, scope);
                if (arg1_value == irb->codegen->invalid_instruction)
                    return arg1_value;

                IrInstruction *bin_op = ir_build_bin_op(irb, scope, node, IrBinOpBitShiftLeftExact, arg0_value, arg1_value, true);
                return ir_lval_wrap(irb, scope, bin_op, lval);
            }
        case BuiltinFnIdShrExact:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
                IrInstruction *arg1_value = ir_gen_node(irb, arg1_node, scope);
                if (arg1_value == irb->codegen->invalid_instruction)
                    return arg1_value;

                IrInstruction *bin_op = ir_build_bin_op(irb, scope, node, IrBinOpBitShiftRightExact, arg0_value, arg1_value, true);
                return ir_lval_wrap(irb, scope, bin_op, lval);
            }
        case BuiltinFnIdSetEvalBranchQuota:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                IrInstruction *set_eval_branch_quota = ir_build_set_eval_branch_quota(irb, scope, node, arg0_value);
                return ir_lval_wrap(irb, scope, set_eval_branch_quota, lval);
            }
        case BuiltinFnIdAlignCast:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
                IrInstruction *arg1_value = ir_gen_node(irb, arg1_node, scope);
                if (arg1_value == irb->codegen->invalid_instruction)
                    return arg1_value;

                IrInstruction *align_cast = ir_build_align_cast(irb, scope, node, arg0_value, arg1_value);
                return ir_lval_wrap(irb, scope, align_cast, lval);
            }
        case BuiltinFnIdOpaqueType:
            {
                IrInstruction *opaque_type = ir_build_opaque_type(irb, scope, node);
                return ir_lval_wrap(irb, scope, opaque_type, lval);
            }
        case BuiltinFnIdThis:
            {
                IrInstruction *this_inst = ir_gen_this(irb, scope, node);
                return ir_lval_wrap(irb, scope, this_inst, lval);
            }
        case BuiltinFnIdSetAlignStack:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                IrInstruction *set_align_stack = ir_build_set_align_stack(irb, scope, node, arg0_value);
                return ir_lval_wrap(irb, scope, set_align_stack, lval);
            }
        case BuiltinFnIdArgType:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
                IrInstruction *arg1_value = ir_gen_node(irb, arg1_node, scope);
                if (arg1_value == irb->codegen->invalid_instruction)
                    return arg1_value;

                IrInstruction *arg_type = ir_build_arg_type(irb, scope, node, arg0_value, arg1_value);
                return ir_lval_wrap(irb, scope, arg_type, lval);
            }
        case BuiltinFnIdExport:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
                IrInstruction *arg1_value = ir_gen_node(irb, arg1_node, scope);
                if (arg1_value == irb->codegen->invalid_instruction)
                    return arg1_value;

                AstNode *arg2_node = node->data.fn_call_expr.params.at(2);
                IrInstruction *arg2_value = ir_gen_node(irb, arg2_node, scope);
                if (arg2_value == irb->codegen->invalid_instruction)
                    return arg2_value;

                IrInstruction *ir_export = ir_build_export(irb, scope, node, arg0_value, arg1_value, arg2_value);
                return ir_lval_wrap(irb, scope, ir_export, lval);
            }
        case BuiltinFnIdErrorReturnTrace:
            {
                IrInstruction *error_return_trace = ir_build_error_return_trace(irb, scope, node, IrInstructionErrorReturnTrace::Null);
                return ir_lval_wrap(irb, scope, error_return_trace, lval);
            }
        case BuiltinFnIdAtomicRmw:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
                IrInstruction *arg1_value = ir_gen_node(irb, arg1_node, scope);
                if (arg1_value == irb->codegen->invalid_instruction)
                    return arg1_value;

                AstNode *arg2_node = node->data.fn_call_expr.params.at(2);
                IrInstruction *arg2_value = ir_gen_node(irb, arg2_node, scope);
                if (arg2_value == irb->codegen->invalid_instruction)
                    return arg2_value;

                AstNode *arg3_node = node->data.fn_call_expr.params.at(3);
                IrInstruction *arg3_value = ir_gen_node(irb, arg3_node, scope);
                if (arg3_value == irb->codegen->invalid_instruction)
                    return arg3_value;

                AstNode *arg4_node = node->data.fn_call_expr.params.at(4);
                IrInstruction *arg4_value = ir_gen_node(irb, arg4_node, scope);
                if (arg4_value == irb->codegen->invalid_instruction)
                    return arg4_value;

                return ir_build_atomic_rmw(irb, scope, node, arg0_value, arg1_value, arg2_value, arg3_value,
                        arg4_value,
                        // these 2 values don't mean anything since we passed non-null values for other args
                        AtomicRmwOp_xchg, AtomicOrderMonotonic);
            }
        case BuiltinFnIdAtomicLoad:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
                IrInstruction *arg1_value = ir_gen_node(irb, arg1_node, scope);
                if (arg1_value == irb->codegen->invalid_instruction)
                    return arg1_value;

                AstNode *arg2_node = node->data.fn_call_expr.params.at(2);
                IrInstruction *arg2_value = ir_gen_node(irb, arg2_node, scope);
                if (arg2_value == irb->codegen->invalid_instruction)
                    return arg2_value;

                return ir_build_atomic_load(irb, scope, node, arg0_value, arg1_value, arg2_value,
                        // this value does not mean anything since we passed non-null values for other arg
                        AtomicOrderMonotonic);
            }
        case BuiltinFnIdIntToEnum:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
                IrInstruction *arg1_value = ir_gen_node(irb, arg1_node, scope);
                if (arg1_value == irb->codegen->invalid_instruction)
                    return arg1_value;

                IrInstruction *result = ir_build_int_to_enum(irb, scope, node, arg0_value, arg1_value);
                return ir_lval_wrap(irb, scope, result, lval);
            }
        case BuiltinFnIdEnumToInt:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                IrInstruction *result = ir_build_enum_to_int(irb, scope, node, arg0_value);
                return ir_lval_wrap(irb, scope, result, lval);
            }
        case BuiltinFnIdBswap:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
                IrInstruction *arg1_value = ir_gen_node(irb, arg1_node, scope);
                if (arg1_value == irb->codegen->invalid_instruction)
                    return arg1_value;

                IrInstruction *result = ir_build_bswap(irb, scope, node, arg0_value, arg1_value);
                return ir_lval_wrap(irb, scope, result, lval);
            }
        case BuiltinFnIdBitReverse:
            {
                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
                if (arg0_value == irb->codegen->invalid_instruction)
                    return arg0_value;

                AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
                IrInstruction *arg1_value = ir_gen_node(irb, arg1_node, scope);
                if (arg1_value == irb->codegen->invalid_instruction)
                    return arg1_value;

                IrInstruction *result = ir_build_bit_reverse(irb, scope, node, arg0_value, arg1_value);
                return ir_lval_wrap(irb, scope, result, lval);
            }
    }
    zig_unreachable();
}

static IrInstruction *ir_gen_fn_call(IrBuilder *irb, Scope *scope, AstNode *node, LVal lval) {
    assert(node->type == NodeTypeFnCallExpr);

    if (node->data.fn_call_expr.is_builtin)
        return ir_gen_builtin_fn_call(irb, scope, node, lval);

    AstNode *fn_ref_node = node->data.fn_call_expr.fn_ref_expr;
    IrInstruction *fn_ref = ir_gen_node(irb, fn_ref_node, scope);
    if (fn_ref == irb->codegen->invalid_instruction)
        return fn_ref;

    size_t arg_count = node->data.fn_call_expr.params.length;
    IrInstruction **args = allocate<IrInstruction*>(arg_count);
    for (size_t i = 0; i < arg_count; i += 1) {
        AstNode *arg_node = node->data.fn_call_expr.params.at(i);
        args[i] = ir_gen_node(irb, arg_node, scope);
        if (args[i] == irb->codegen->invalid_instruction)
            return args[i];
    }

    bool is_async = node->data.fn_call_expr.is_async;
    IrInstruction *async_allocator = nullptr;
    if (is_async) {
        if (node->data.fn_call_expr.async_allocator) {
            async_allocator = ir_gen_node(irb, node->data.fn_call_expr.async_allocator, scope);
            if (async_allocator == irb->codegen->invalid_instruction)
                return async_allocator;
        }
    }

    IrInstruction *fn_call = ir_build_call(irb, scope, node, nullptr, fn_ref, arg_count, args, false, FnInlineAuto,
            is_async, async_allocator, nullptr);
    return ir_lval_wrap(irb, scope, fn_call, lval);
}

static IrInstruction *ir_gen_if_bool_expr(IrBuilder *irb, Scope *scope, AstNode *node) {
    assert(node->type == NodeTypeIfBoolExpr);

    IrInstruction *condition = ir_gen_node(irb, node->data.if_bool_expr.condition, scope);
    if (condition == irb->codegen->invalid_instruction)
        return irb->codegen->invalid_instruction;

    IrInstruction *is_comptime;
    if (ir_should_inline(irb->exec, scope)) {
        is_comptime = ir_build_const_bool(irb, scope, node, true);
    } else {
        is_comptime = ir_build_test_comptime(irb, scope, node, condition);
    }

    AstNode *then_node = node->data.if_bool_expr.then_block;
    AstNode *else_node = node->data.if_bool_expr.else_node;

    IrBasicBlock *then_block = ir_create_basic_block(irb, scope, "Then");
    IrBasicBlock *else_block = ir_create_basic_block(irb, scope, "Else");
    IrBasicBlock *endif_block = ir_create_basic_block(irb, scope, "EndIf");

    ir_build_cond_br(irb, scope, condition->source_node, condition, then_block, else_block, is_comptime);

    ir_set_cursor_at_end_and_append_block(irb, then_block);

    Scope *subexpr_scope = create_runtime_scope(irb->codegen, node, scope, is_comptime);
    IrInstruction *then_expr_result = ir_gen_node(irb, then_node, subexpr_scope);
    if (then_expr_result == irb->codegen->invalid_instruction)
        return irb->codegen->invalid_instruction;
    IrBasicBlock *after_then_block = irb->current_basic_block;
    if (!instr_is_unreachable(then_expr_result))
        ir_mark_gen(ir_build_br(irb, scope, node, endif_block, is_comptime));

    ir_set_cursor_at_end_and_append_block(irb, else_block);
    IrInstruction *else_expr_result;
    if (else_node) {
        else_expr_result = ir_gen_node(irb, else_node, subexpr_scope);
        if (else_expr_result == irb->codegen->invalid_instruction)
            return irb->codegen->invalid_instruction;
    } else {
        else_expr_result = ir_build_const_void(irb, scope, node);
    }
    IrBasicBlock *after_else_block = irb->current_basic_block;
    if (!instr_is_unreachable(else_expr_result))
        ir_mark_gen(ir_build_br(irb, scope, node, endif_block, is_comptime));

    ir_set_cursor_at_end_and_append_block(irb, endif_block);
    IrInstruction **incoming_values = allocate<IrInstruction *>(2);
    incoming_values[0] = then_expr_result;
    incoming_values[1] = else_expr_result;
    IrBasicBlock **incoming_blocks = allocate<IrBasicBlock *>(2);
    incoming_blocks[0] = after_then_block;
    incoming_blocks[1] = after_else_block;

    return ir_build_phi(irb, scope, node, 2, incoming_blocks, incoming_values);
}

static IrInstruction *ir_gen_prefix_op_id_lval(IrBuilder *irb, Scope *scope, AstNode *node, IrUnOp op_id, LVal lval) {
    assert(node->type == NodeTypePrefixOpExpr);
    AstNode *expr_node = node->data.prefix_op_expr.primary_expr;

    IrInstruction *value = ir_gen_node_extra(irb, expr_node, scope, lval);
    if (value == irb->codegen->invalid_instruction)
        return value;

    return ir_build_un_op(irb, scope, node, op_id, value);
}

static IrInstruction *ir_gen_prefix_op_id(IrBuilder *irb, Scope *scope, AstNode *node, IrUnOp op_id) {
    return ir_gen_prefix_op_id_lval(irb, scope, node, op_id, LValNone);
}

static IrInstruction *ir_lval_wrap(IrBuilder *irb, Scope *scope, IrInstruction *value, LVal lval) {
    if (lval != LValPtr)
        return value;
    if (value == irb->codegen->invalid_instruction)
        return value;

    // We needed a pointer to a value, but we got a value. So we create
    // an instruction which just makes a const pointer of it.
    return ir_build_ref(irb, scope, value->source_node, value, false, false);
}

static PtrLen star_token_to_ptr_len(TokenId token_id) {
    switch (token_id) {
        case TokenIdStar:
        case TokenIdStarStar:
            return PtrLenSingle;
        case TokenIdBracketStarBracket:
            return PtrLenUnknown;
        case TokenIdBracketStarCBracket:
            return PtrLenC;
        default:
            zig_unreachable();
    }
}

static IrInstruction *ir_gen_pointer_type(IrBuilder *irb, Scope *scope, AstNode *node) {
    assert(node->type == NodeTypePointerType);
    PtrLen ptr_len = star_token_to_ptr_len(node->data.pointer_type.star_token->id);
    bool is_const = node->data.pointer_type.is_const;
    bool is_volatile = node->data.pointer_type.is_volatile;
    AstNode *expr_node = node->data.pointer_type.op_expr;
    AstNode *align_expr = node->data.pointer_type.align_expr;

    IrInstruction *align_value;
    if (align_expr != nullptr) {
        align_value = ir_gen_node(irb, align_expr, scope);
        if (align_value == irb->codegen->invalid_instruction)
            return align_value;
    } else {
        align_value = nullptr;
    }

    IrInstruction *child_type = ir_gen_node(irb, expr_node, scope);
    if (child_type == irb->codegen->invalid_instruction)
        return child_type;

    uint32_t bit_offset_start = 0;
    if (node->data.pointer_type.bit_offset_start != nullptr) {
        if (!bigint_fits_in_bits(node->data.pointer_type.bit_offset_start, 32, false)) {
            Buf *val_buf = buf_alloc();
            bigint_append_buf(val_buf, node->data.pointer_type.bit_offset_start, 10);
            exec_add_error_node(irb->codegen, irb->exec, node,
                    buf_sprintf("value %s too large for u32 bit offset", buf_ptr(val_buf)));
            return irb->codegen->invalid_instruction;
        }
        bit_offset_start = bigint_as_unsigned(node->data.pointer_type.bit_offset_start);
    }

    uint32_t host_int_bytes = 0;
    if (node->data.pointer_type.host_int_bytes != nullptr) {
        if (!bigint_fits_in_bits(node->data.pointer_type.host_int_bytes, 32, false)) {
            Buf *val_buf = buf_alloc();
            bigint_append_buf(val_buf, node->data.pointer_type.host_int_bytes, 10);
            exec_add_error_node(irb->codegen, irb->exec, node,
                    buf_sprintf("value %s too large for u32 byte count", buf_ptr(val_buf)));
            return irb->codegen->invalid_instruction;
        }
        host_int_bytes = bigint_as_unsigned(node->data.pointer_type.host_int_bytes);
    }

    if (host_int_bytes != 0 && bit_offset_start >= host_int_bytes * 8) {
        exec_add_error_node(irb->codegen, irb->exec, node,
                buf_sprintf("bit offset starts after end of host integer"));
        return irb->codegen->invalid_instruction;
    }

    return ir_build_ptr_type(irb, scope, node, child_type, is_const, is_volatile,
            ptr_len, align_value, bit_offset_start, host_int_bytes);
}

static IrInstruction *ir_gen_catch_unreachable(IrBuilder *irb, Scope *scope, AstNode *source_node, AstNode *expr_node,
        LVal lval)
{
    IrInstruction *err_union_ptr = ir_gen_node_extra(irb, expr_node, scope, LValPtr);
    if (err_union_ptr == irb->codegen->invalid_instruction)
        return irb->codegen->invalid_instruction;

    IrInstruction *payload_ptr = ir_build_unwrap_err_payload(irb, scope, source_node, err_union_ptr, true);
    if (payload_ptr == irb->codegen->invalid_instruction)
        return irb->codegen->invalid_instruction;

    if (lval == LValPtr)
        return payload_ptr;

    return ir_build_load_ptr(irb, scope, source_node, payload_ptr);
}

static IrInstruction *ir_gen_bool_not(IrBuilder *irb, Scope *scope, AstNode *node) {
    assert(node->type == NodeTypePrefixOpExpr);
    AstNode *expr_node = node->data.prefix_op_expr.primary_expr;

    IrInstruction *value = ir_gen_node(irb, expr_node, scope);
    if (value == irb->codegen->invalid_instruction)
        return irb->codegen->invalid_instruction;

    return ir_build_bool_not(irb, scope, node, value);
}

static IrInstruction *ir_gen_prefix_op_expr(IrBuilder *irb, Scope *scope, AstNode *node, LVal lval) {
    assert(node->type == NodeTypePrefixOpExpr);

    PrefixOp prefix_op = node->data.prefix_op_expr.prefix_op;

    switch (prefix_op) {
        case PrefixOpInvalid:
            zig_unreachable();
        case PrefixOpBoolNot:
            return ir_lval_wrap(irb, scope, ir_gen_bool_not(irb, scope, node), lval);
        case PrefixOpBinNot:
            return ir_lval_wrap(irb, scope, ir_gen_prefix_op_id(irb, scope, node, IrUnOpBinNot), lval);
        case PrefixOpNegation:
            return ir_lval_wrap(irb, scope, ir_gen_prefix_op_id(irb, scope, node, IrUnOpNegation), lval);
        case PrefixOpNegationWrap:
            return ir_lval_wrap(irb, scope, ir_gen_prefix_op_id(irb, scope, node, IrUnOpNegationWrap), lval);
        case PrefixOpOptional:
            return ir_lval_wrap(irb, scope, ir_gen_prefix_op_id(irb, scope, node, IrUnOpOptional), lval);
        case PrefixOpAddrOf: {
            AstNode *expr_node = node->data.prefix_op_expr.primary_expr;
            return ir_lval_wrap(irb, scope, ir_gen_node_extra(irb, expr_node, scope, LValPtr), lval);
        }
    }
    zig_unreachable();
}

static IrInstruction *ir_gen_container_init_expr(IrBuilder *irb, Scope *scope, AstNode *node) {
    assert(node->type == NodeTypeContainerInitExpr);

    AstNodeContainerInitExpr *container_init_expr = &node->data.container_init_expr;
    ContainerInitKind kind = container_init_expr->kind;

    IrInstruction *container_type = ir_gen_node(irb, container_init_expr->type, scope);
    if (container_type == irb->codegen->invalid_instruction)
        return container_type;

    if (kind == ContainerInitKindStruct) {
        size_t field_count = container_init_expr->entries.length;
        IrInstructionContainerInitFieldsField *fields = allocate<IrInstructionContainerInitFieldsField>(field_count);
        for (size_t i = 0; i < field_count; i += 1) {
            AstNode *entry_node = container_init_expr->entries.at(i);
            assert(entry_node->type == NodeTypeStructValueField);

            Buf *name = entry_node->data.struct_val_field.name;
            AstNode *expr_node = entry_node->data.struct_val_field.expr;
            IrInstruction *expr_value = ir_gen_node(irb, expr_node, scope);
            if (expr_value == irb->codegen->invalid_instruction)
                return expr_value;

            fields[i].name = name;
            fields[i].value = expr_value;
            fields[i].source_node = entry_node;
        }
        return ir_build_container_init_fields(irb, scope, node, container_type, field_count, fields);
    } else if (kind == ContainerInitKindArray) {
        size_t item_count = container_init_expr->entries.length;
        IrInstruction **values = allocate<IrInstruction *>(item_count);
        for (size_t i = 0; i < item_count; i += 1) {
            AstNode *expr_node = container_init_expr->entries.at(i);
            IrInstruction *expr_value = ir_gen_node(irb, expr_node, scope);
            if (expr_value == irb->codegen->invalid_instruction)
                return expr_value;

            values[i] = expr_value;
        }
        return ir_build_container_init_list(irb, scope, node, container_type, item_count, values);
    } else {
        zig_unreachable();
    }
}

static IrInstruction *ir_gen_var_decl(IrBuilder *irb, Scope *scope, AstNode *node) {
    assert(node->type == NodeTypeVariableDeclaration);

    AstNodeVariableDeclaration *variable_declaration = &node->data.variable_declaration;

    if (buf_eql_str(variable_declaration->symbol, "_")) {
        add_node_error(irb->codegen, node, buf_sprintf("`_` is not a declarable symbol"));
        return irb->codegen->invalid_instruction;
    }

    IrInstruction *type_instruction;
    if (variable_declaration->type != nullptr) {
        type_instruction = ir_gen_node(irb, variable_declaration->type, scope);
        if (type_instruction == irb->codegen->invalid_instruction)
            return type_instruction;
    } else {
        type_instruction = nullptr;
    }

    bool is_shadowable = false;
    bool is_const = variable_declaration->is_const;
    bool is_extern = variable_declaration->is_extern;

    IrInstruction *is_comptime = ir_build_const_bool(irb, scope, node,
        ir_should_inline(irb->exec, scope) || variable_declaration->is_comptime);
    ZigVar *var = ir_create_var(irb, node, scope, variable_declaration->symbol,
        is_const, is_const, is_shadowable, is_comptime);
    // we detect IrInstructionIdDeclVarSrc in gen_block to make sure the next node
    // is inside var->child_scope

    if (!is_extern && !variable_declaration->expr) {
        var->var_type = irb->codegen->builtin_types.entry_invalid;
        add_node_error(irb->codegen, node, buf_sprintf("variables must be initialized"));
        return irb->codegen->invalid_instruction;
    }

    IrInstruction *align_value = nullptr;
    if (variable_declaration->align_expr != nullptr) {
        align_value = ir_gen_node(irb, variable_declaration->align_expr, scope);
        if (align_value == irb->codegen->invalid_instruction)
            return align_value;
    }

    if (variable_declaration->section_expr != nullptr) {
        add_node_error(irb->codegen, variable_declaration->section_expr,
            buf_sprintf("cannot set section of local variable '%s'", buf_ptr(variable_declaration->symbol)));
    }
    if (variable_declaration->threadlocal_tok != nullptr) {
        add_token_error(irb->codegen, node->owner, variable_declaration->threadlocal_tok,
            buf_sprintf("function-local variable '%s' cannot be threadlocal", buf_ptr(variable_declaration->symbol)));
    }

    // Temporarily set the name of the IrExecutable to the VariableDeclaration
    // so that the struct or enum from the init expression inherits the name.
    Buf *old_exec_name = irb->exec->name;
    irb->exec->name = variable_declaration->symbol;
    IrInstruction *init_value = ir_gen_node(irb, variable_declaration->expr, scope);
    irb->exec->name = old_exec_name;

    if (init_value == irb->codegen->invalid_instruction)
        return init_value;

    return ir_build_var_decl_src(irb, scope, node, var, type_instruction, align_value, init_value);
}

static IrInstruction *ir_gen_while_expr(IrBuilder *irb, Scope *scope, AstNode *node) {
    assert(node->type == NodeTypeWhileExpr);

    AstNode *continue_expr_node = node->data.while_expr.continue_expr;
    AstNode *else_node = node->data.while_expr.else_node;

    IrBasicBlock *cond_block = ir_create_basic_block(irb, scope, "WhileCond");
    IrBasicBlock *body_block = ir_create_basic_block(irb, scope, "WhileBody");
    IrBasicBlock *continue_block = continue_expr_node ?
        ir_create_basic_block(irb, scope, "WhileContinue") : cond_block;
    IrBasicBlock *end_block = ir_create_basic_block(irb, scope, "WhileEnd");
    IrBasicBlock *else_block = else_node ?
        ir_create_basic_block(irb, scope, "WhileElse") : end_block;

    IrInstruction *is_comptime = ir_build_const_bool(irb, scope, node,
        ir_should_inline(irb->exec, scope) || node->data.while_expr.is_inline);
    ir_build_br(irb, scope, node, cond_block, is_comptime);

    Scope *subexpr_scope = create_runtime_scope(irb->codegen, node, scope, is_comptime);
    Buf *var_symbol = node->data.while_expr.var_symbol;
    Buf *err_symbol = node->data.while_expr.err_symbol;
    if (err_symbol != nullptr) {
        ir_set_cursor_at_end_and_append_block(irb, cond_block);

        Scope *payload_scope;
        AstNode *symbol_node = node; // TODO make more accurate
        ZigVar *payload_var;
        if (var_symbol) {
            // TODO make it an error to write to payload variable
            payload_var = ir_create_var(irb, symbol_node, subexpr_scope, var_symbol,
                    true, false, false, is_comptime);
            payload_scope = payload_var->child_scope;
        } else {
            payload_scope = subexpr_scope;
        }
        IrInstruction *err_val_ptr = ir_gen_node_extra(irb, node->data.while_expr.condition, subexpr_scope, LValPtr);
        if (err_val_ptr == irb->codegen->invalid_instruction)
            return err_val_ptr;
        IrInstruction *err_val = ir_build_load_ptr(irb, scope, node->data.while_expr.condition, err_val_ptr);
        IrInstruction *is_err = ir_build_test_err(irb, scope, node->data.while_expr.condition, err_val);
        IrBasicBlock *after_cond_block = irb->current_basic_block;
        IrInstruction *void_else_result = else_node ? nullptr : ir_mark_gen(ir_build_const_void(irb, scope, node));
        if (!instr_is_unreachable(is_err)) {
            ir_mark_gen(ir_build_cond_br(irb, scope, node->data.while_expr.condition, is_err,
                        else_block, body_block, is_comptime));
        }

        ir_set_cursor_at_end_and_append_block(irb, body_block);
        if (var_symbol) {
            IrInstruction *var_ptr_value = ir_build_unwrap_err_payload(irb, payload_scope, symbol_node,
                    err_val_ptr, false);
            IrInstruction *var_value = node->data.while_expr.var_is_ptr ?
                var_ptr_value : ir_build_load_ptr(irb, payload_scope, symbol_node, var_ptr_value);
            ir_build_var_decl_src(irb, payload_scope, symbol_node, payload_var, nullptr, nullptr, var_value);
        }

        ZigList<IrInstruction *> incoming_values = {0};
        ZigList<IrBasicBlock *> incoming_blocks = {0};

        ScopeLoop *loop_scope = create_loop_scope(irb->codegen, node, payload_scope);
        loop_scope->break_block = end_block;
        loop_scope->continue_block = continue_block;
        loop_scope->is_comptime = is_comptime;
        loop_scope->incoming_blocks = &incoming_blocks;
        loop_scope->incoming_values = &incoming_values;

        IrInstruction *body_result = ir_gen_node(irb, node->data.while_expr.body, &loop_scope->base);
        if (body_result == irb->codegen->invalid_instruction)
            return body_result;

        if (!instr_is_unreachable(body_result)) {
            ir_mark_gen(ir_build_check_statement_is_void(irb, payload_scope, node->data.while_expr.body, body_result));
            ir_mark_gen(ir_build_br(irb, payload_scope, node, continue_block, is_comptime));
        }

        if (continue_expr_node) {
            ir_set_cursor_at_end_and_append_block(irb, continue_block);
            IrInstruction *expr_result = ir_gen_node(irb, continue_expr_node, payload_scope);
            if (expr_result == irb->codegen->invalid_instruction)
                return expr_result;
            if (!instr_is_unreachable(expr_result))
                ir_mark_gen(ir_build_br(irb, payload_scope, node, cond_block, is_comptime));
        }

        ir_set_cursor_at_end_and_append_block(irb, else_block);
        assert(else_node != nullptr);

        // TODO make it an error to write to error variable
        AstNode *err_symbol_node = else_node; // TODO make more accurate
        ZigVar *err_var = ir_create_var(irb, err_symbol_node, scope, err_symbol,
                true, false, false, is_comptime);
        Scope *err_scope = err_var->child_scope;
        IrInstruction *err_var_value = ir_build_unwrap_err_code(irb, err_scope, err_symbol_node, err_val_ptr);
        ir_build_var_decl_src(irb, err_scope, symbol_node, err_var, nullptr, nullptr, err_var_value);

        IrInstruction *else_result = ir_gen_node(irb, else_node, err_scope);
        if (else_result == irb->codegen->invalid_instruction)
            return else_result;
        if (!instr_is_unreachable(else_result))
            ir_mark_gen(ir_build_br(irb, scope, node, end_block, is_comptime));
        IrBasicBlock *after_else_block = irb->current_basic_block;
        ir_set_cursor_at_end_and_append_block(irb, end_block);
        if (else_result) {
            incoming_blocks.append(after_else_block);
            incoming_values.append(else_result);
        } else {
            incoming_blocks.append(after_cond_block);
            incoming_values.append(void_else_result);
        }

        return ir_build_phi(irb, scope, node, incoming_blocks.length, incoming_blocks.items, incoming_values.items);
    } else if (var_symbol != nullptr) {
        ir_set_cursor_at_end_and_append_block(irb, cond_block);
        Scope *subexpr_scope = create_runtime_scope(irb->codegen, node, scope, is_comptime);
        // TODO make it an error to write to payload variable
        AstNode *symbol_node = node; // TODO make more accurate

        ZigVar *payload_var = ir_create_var(irb, symbol_node, subexpr_scope, var_symbol,
                true, false, false, is_comptime);
        Scope *child_scope = payload_var->child_scope;
        IrInstruction *maybe_val_ptr = ir_gen_node_extra(irb, node->data.while_expr.condition, subexpr_scope, LValPtr);
        if (maybe_val_ptr == irb->codegen->invalid_instruction)
            return maybe_val_ptr;
        IrInstruction *maybe_val = ir_build_load_ptr(irb, scope, node->data.while_expr.condition, maybe_val_ptr);
        IrInstruction *is_non_null = ir_build_test_nonnull(irb, scope, node->data.while_expr.condition, maybe_val);
        IrBasicBlock *after_cond_block = irb->current_basic_block;
        IrInstruction *void_else_result = else_node ? nullptr : ir_mark_gen(ir_build_const_void(irb, scope, node));
        if (!instr_is_unreachable(is_non_null)) {
            ir_mark_gen(ir_build_cond_br(irb, scope, node->data.while_expr.condition, is_non_null,
                        body_block, else_block, is_comptime));
        }

        ir_set_cursor_at_end_and_append_block(irb, body_block);
        IrInstruction *var_ptr_value = ir_build_optional_unwrap_ptr(irb, child_scope, symbol_node, maybe_val_ptr, false);
        IrInstruction *var_value = node->data.while_expr.var_is_ptr ?
            var_ptr_value : ir_build_load_ptr(irb, child_scope, symbol_node, var_ptr_value);
        ir_build_var_decl_src(irb, child_scope, symbol_node, payload_var, nullptr, nullptr, var_value);

        ZigList<IrInstruction *> incoming_values = {0};
        ZigList<IrBasicBlock *> incoming_blocks = {0};

        ScopeLoop *loop_scope = create_loop_scope(irb->codegen, node, child_scope);
        loop_scope->break_block = end_block;
        loop_scope->continue_block = continue_block;
        loop_scope->is_comptime = is_comptime;
        loop_scope->incoming_blocks = &incoming_blocks;
        loop_scope->incoming_values = &incoming_values;

        IrInstruction *body_result = ir_gen_node(irb, node->data.while_expr.body, &loop_scope->base);
        if (body_result == irb->codegen->invalid_instruction)
            return body_result;

        if (!instr_is_unreachable(body_result)) {
            ir_mark_gen(ir_build_check_statement_is_void(irb, child_scope, node->data.while_expr.body, body_result));
            ir_mark_gen(ir_build_br(irb, child_scope, node, continue_block, is_comptime));
        }

        if (continue_expr_node) {
            ir_set_cursor_at_end_and_append_block(irb, continue_block);
            IrInstruction *expr_result = ir_gen_node(irb, continue_expr_node, child_scope);
            if (expr_result == irb->codegen->invalid_instruction)
                return expr_result;
            if (!instr_is_unreachable(expr_result))
                ir_mark_gen(ir_build_br(irb, child_scope, node, cond_block, is_comptime));
        }

        IrInstruction *else_result = nullptr;
        if (else_node) {
            ir_set_cursor_at_end_and_append_block(irb, else_block);

            else_result = ir_gen_node(irb, else_node, scope);
            if (else_result == irb->codegen->invalid_instruction)
                return else_result;
            if (!instr_is_unreachable(else_result))
                ir_mark_gen(ir_build_br(irb, scope, node, end_block, is_comptime));
        }
        IrBasicBlock *after_else_block = irb->current_basic_block;
        ir_set_cursor_at_end_and_append_block(irb, end_block);
        if (else_result) {
            incoming_blocks.append(after_else_block);
            incoming_values.append(else_result);
        } else {
            incoming_blocks.append(after_cond_block);
            incoming_values.append(void_else_result);
        }

        return ir_build_phi(irb, scope, node, incoming_blocks.length, incoming_blocks.items, incoming_values.items);
    } else {
        ir_set_cursor_at_end_and_append_block(irb, cond_block);
        IrInstruction *cond_val = ir_gen_node(irb, node->data.while_expr.condition, scope);
        if (cond_val == irb->codegen->invalid_instruction)
            return cond_val;
        IrBasicBlock *after_cond_block = irb->current_basic_block;
        IrInstruction *void_else_result = else_node ? nullptr : ir_mark_gen(ir_build_const_void(irb, scope, node));
        if (!instr_is_unreachable(cond_val)) {
            ir_mark_gen(ir_build_cond_br(irb, scope, node->data.while_expr.condition, cond_val,
                        body_block, else_block, is_comptime));
        }

        ir_set_cursor_at_end_and_append_block(irb, body_block);

        ZigList<IrInstruction *> incoming_values = {0};
        ZigList<IrBasicBlock *> incoming_blocks = {0};

        Scope *subexpr_scope = create_runtime_scope(irb->codegen, node, scope, is_comptime);

        ScopeLoop *loop_scope = create_loop_scope(irb->codegen, node, subexpr_scope);
        loop_scope->break_block = end_block;
        loop_scope->continue_block = continue_block;
        loop_scope->is_comptime = is_comptime;
        loop_scope->incoming_blocks = &incoming_blocks;
        loop_scope->incoming_values = &incoming_values;

        IrInstruction *body_result = ir_gen_node(irb, node->data.while_expr.body, &loop_scope->base);
        if (body_result == irb->codegen->invalid_instruction)
            return body_result;

        if (!instr_is_unreachable(body_result)) {
            ir_mark_gen(ir_build_check_statement_is_void(irb, scope, node->data.while_expr.body, body_result));
            ir_mark_gen(ir_build_br(irb, scope, node, continue_block, is_comptime));
        }

        if (continue_expr_node) {
            ir_set_cursor_at_end_and_append_block(irb, continue_block);
            IrInstruction *expr_result = ir_gen_node(irb, continue_expr_node, subexpr_scope);
            if (expr_result == irb->codegen->invalid_instruction)
                return expr_result;
            if (!instr_is_unreachable(expr_result))
                ir_mark_gen(ir_build_br(irb, scope, node, cond_block, is_comptime));
        }

        IrInstruction *else_result = nullptr;
        if (else_node) {
            ir_set_cursor_at_end_and_append_block(irb, else_block);

            else_result = ir_gen_node(irb, else_node, subexpr_scope);
            if (else_result == irb->codegen->invalid_instruction)
                return else_result;
            if (!instr_is_unreachable(else_result))
                ir_mark_gen(ir_build_br(irb, scope, node, end_block, is_comptime));
        }
        IrBasicBlock *after_else_block = irb->current_basic_block;
        ir_set_cursor_at_end_and_append_block(irb, end_block);
        if (else_result) {
            incoming_blocks.append(after_else_block);
            incoming_values.append(else_result);
        } else {
            incoming_blocks.append(after_cond_block);
            incoming_values.append(void_else_result);
        }

        return ir_build_phi(irb, scope, node, incoming_blocks.length, incoming_blocks.items, incoming_values.items);
    }
}

static IrInstruction *ir_gen_for_expr(IrBuilder *irb, Scope *parent_scope, AstNode *node) {
    assert(node->type == NodeTypeForExpr);

    AstNode *array_node = node->data.for_expr.array_expr;
    AstNode *elem_node = node->data.for_expr.elem_node;
    AstNode *index_node = node->data.for_expr.index_node;
    AstNode *body_node = node->data.for_expr.body;
    AstNode *else_node = node->data.for_expr.else_node;

    if (!elem_node) {
        add_node_error(irb->codegen, node, buf_sprintf("for loop expression missing element parameter"));
        return irb->codegen->invalid_instruction;
    }
    assert(elem_node->type == NodeTypeSymbol);

    IrInstruction *array_val_ptr = ir_gen_node_extra(irb, array_node, parent_scope, LValPtr);
    if (array_val_ptr == irb->codegen->invalid_instruction)
        return array_val_ptr;

    IrInstruction *array_val = ir_build_load_ptr(irb, parent_scope, array_node, array_val_ptr);

    IrInstruction *pointer_type = ir_build_to_ptr_type(irb, parent_scope, array_node, array_val);
    IrInstruction *elem_var_type;
    if (node->data.for_expr.elem_is_ptr) {
        elem_var_type = pointer_type;
    } else {
        elem_var_type = ir_build_ptr_type_child(irb, parent_scope, elem_node, pointer_type);
    }

    IrInstruction *is_comptime = ir_build_const_bool(irb, parent_scope, node,
        ir_should_inline(irb->exec, parent_scope) || node->data.for_expr.is_inline);

    // TODO make it an error to write to element variable or i variable.
    Buf *elem_var_name = elem_node->data.symbol_expr.symbol;
    ZigVar *elem_var = ir_create_var(irb, elem_node, parent_scope, elem_var_name, true, false, false, is_comptime);
    Scope *child_scope = elem_var->child_scope;

    IrInstruction *undefined_value = ir_build_const_undefined(irb, child_scope, elem_node);
    ir_build_var_decl_src(irb, child_scope, elem_node, elem_var, elem_var_type, nullptr, undefined_value);
    IrInstruction *elem_var_ptr = ir_build_var_ptr(irb, child_scope, node, elem_var);

    AstNode *index_var_source_node;
    ZigVar *index_var;
    if (index_node) {
        index_var_source_node = index_node;
        Buf *index_var_name = index_node->data.symbol_expr.symbol;
        index_var = ir_create_var(irb, index_node, child_scope, index_var_name, true, false, false, is_comptime);
    } else {
        index_var_source_node = node;
        index_var = ir_create_var(irb, node, child_scope, nullptr, true, false, true, is_comptime);
    }
    child_scope = index_var->child_scope;

    IrInstruction *usize = ir_build_const_type(irb, child_scope, node, irb->codegen->builtin_types.entry_usize);
    IrInstruction *zero = ir_build_const_usize(irb, child_scope, node, 0);
    IrInstruction *one = ir_build_const_usize(irb, child_scope, node, 1);
    ir_build_var_decl_src(irb, child_scope, index_var_source_node, index_var, usize, nullptr, zero);
    IrInstruction *index_ptr = ir_build_var_ptr(irb, child_scope, node, index_var);


    IrBasicBlock *cond_block = ir_create_basic_block(irb, child_scope, "ForCond");
    IrBasicBlock *body_block = ir_create_basic_block(irb, child_scope, "ForBody");
    IrBasicBlock *end_block = ir_create_basic_block(irb, child_scope, "ForEnd");
    IrBasicBlock *else_block = else_node ? ir_create_basic_block(irb, child_scope, "ForElse") : end_block;
    IrBasicBlock *continue_block = ir_create_basic_block(irb, child_scope, "ForContinue");

    Buf *len_field_name = buf_create_from_str("len");
    IrInstruction *len_ref = ir_build_field_ptr(irb, child_scope, node, array_val_ptr, len_field_name);
    IrInstruction *len_val = ir_build_load_ptr(irb, child_scope, node, len_ref);
    ir_build_br(irb, child_scope, node, cond_block, is_comptime);

    ir_set_cursor_at_end_and_append_block(irb, cond_block);
    IrInstruction *index_val = ir_build_load_ptr(irb, child_scope, node, index_ptr);
    IrInstruction *cond = ir_build_bin_op(irb, child_scope, node, IrBinOpCmpLessThan, index_val, len_val, false);
    IrBasicBlock *after_cond_block = irb->current_basic_block;
    IrInstruction *void_else_value = else_node ? nullptr : ir_mark_gen(ir_build_const_void(irb, parent_scope, node));
    ir_mark_gen(ir_build_cond_br(irb, child_scope, node, cond, body_block, else_block, is_comptime));

    ir_set_cursor_at_end_and_append_block(irb, body_block);
    IrInstruction *elem_ptr = ir_build_elem_ptr(irb, child_scope, node, array_val_ptr, index_val, false, PtrLenSingle);
    IrInstruction *elem_val;
    if (node->data.for_expr.elem_is_ptr) {
        elem_val = elem_ptr;
    } else {
        elem_val = ir_build_load_ptr(irb, child_scope, node, elem_ptr);
    }
    ir_mark_gen(ir_build_store_ptr(irb, child_scope, node, elem_var_ptr, elem_val));

    ZigList<IrInstruction *> incoming_values = {0};
    ZigList<IrBasicBlock *> incoming_blocks = {0};
    ScopeLoop *loop_scope = create_loop_scope(irb->codegen, node, child_scope);
    loop_scope->break_block = end_block;
    loop_scope->continue_block = continue_block;
    loop_scope->is_comptime = is_comptime;
    loop_scope->incoming_blocks = &incoming_blocks;
    loop_scope->incoming_values = &incoming_values;

    IrInstruction *body_result = ir_gen_node(irb, body_node, &loop_scope->base);

    if (!instr_is_unreachable(body_result))
        ir_mark_gen(ir_build_br(irb, child_scope, node, continue_block, is_comptime));

    ir_set_cursor_at_end_and_append_block(irb, continue_block);
    IrInstruction *new_index_val = ir_build_bin_op(irb, child_scope, node, IrBinOpAdd, index_val, one, false);
    ir_mark_gen(ir_build_store_ptr(irb, child_scope, node, index_ptr, new_index_val));
    ir_build_br(irb, child_scope, node, cond_block, is_comptime);

    IrInstruction *else_result = nullptr;
    if (else_node) {
        ir_set_cursor_at_end_and_append_block(irb, else_block);

        else_result = ir_gen_node(irb, else_node, parent_scope);
        if (else_result == irb->codegen->invalid_instruction)
            return else_result;
        if (!instr_is_unreachable(else_result))
            ir_mark_gen(ir_build_br(irb, parent_scope, node, end_block, is_comptime));
    }
    IrBasicBlock *after_else_block = irb->current_basic_block;
    ir_set_cursor_at_end_and_append_block(irb, end_block);

    if (else_result) {
        incoming_blocks.append(after_else_block);
        incoming_values.append(else_result);
    } else {
        incoming_blocks.append(after_cond_block);
        incoming_values.append(void_else_value);
    }

    return ir_build_phi(irb, parent_scope, node, incoming_blocks.length, incoming_blocks.items, incoming_values.items);
}

static IrInstruction *ir_gen_bool_literal(IrBuilder *irb, Scope *scope, AstNode *node) {
    assert(node->type == NodeTypeBoolLiteral);
    return ir_build_const_bool(irb, scope, node, node->data.bool_literal.value);
}

static IrInstruction *ir_gen_string_literal(IrBuilder *irb, Scope *scope, AstNode *node) {
    assert(node->type == NodeTypeStringLiteral);

    if (node->data.string_literal.c) {
        return ir_build_const_c_str_lit(irb, scope, node, node->data.string_literal.buf);
    } else {
        return ir_build_const_str_lit(irb, scope, node, node->data.string_literal.buf);
    }
}

static IrInstruction *ir_gen_array_type(IrBuilder *irb, Scope *scope, AstNode *node) {
    assert(node->type == NodeTypeArrayType);

    AstNode *size_node = node->data.array_type.size;
    AstNode *child_type_node = node->data.array_type.child_type;
    bool is_const = node->data.array_type.is_const;
    bool is_volatile = node->data.array_type.is_volatile;
    AstNode *align_expr = node->data.array_type.align_expr;

    Scope *comptime_scope = create_comptime_scope(irb->codegen, node, scope);
    if (size_node) {
        if (is_const) {
            add_node_error(irb->codegen, node, buf_create_from_str("const qualifier invalid on array type"));
            return irb->codegen->invalid_instruction;
        }
        if (is_volatile) {
            add_node_error(irb->codegen, node, buf_create_from_str("volatile qualifier invalid on array type"));
            return irb->codegen->invalid_instruction;
        }
        if (align_expr != nullptr) {
            add_node_error(irb->codegen, node, buf_create_from_str("align qualifier invalid on array type"));
            return irb->codegen->invalid_instruction;
        }

        IrInstruction *size_value = ir_gen_node(irb, size_node, comptime_scope);
        if (size_value == irb->codegen->invalid_instruction)
            return size_value;

        IrInstruction *child_type = ir_gen_node(irb, child_type_node, comptime_scope);
        if (child_type == irb->codegen->invalid_instruction)
            return child_type;

        return ir_build_array_type(irb, scope, node, size_value, child_type);
    } else {
        IrInstruction *align_value;
        if (align_expr != nullptr) {
            align_value = ir_gen_node(irb, align_expr, comptime_scope);
            if (align_value == irb->codegen->invalid_instruction)
                return align_value;
        } else {
            align_value = nullptr;
        }

        IrInstruction *child_type = ir_gen_node(irb, child_type_node, comptime_scope);
        if (child_type == irb->codegen->invalid_instruction)
            return child_type;

        return ir_build_slice_type(irb, scope, node, child_type, is_const, is_volatile, align_value);
    }
}

static IrInstruction *ir_gen_promise_type(IrBuilder *irb, Scope *scope, AstNode *node) {
    assert(node->type == NodeTypePromiseType);

    AstNode *payload_type_node = node->data.promise_type.payload_type;
    IrInstruction *payload_type_value = nullptr;

    if (payload_type_node != nullptr) {
        payload_type_value = ir_gen_node(irb, payload_type_node, scope);
        if (payload_type_value == irb->codegen->invalid_instruction)
            return payload_type_value;

    }

    return ir_build_promise_type(irb, scope, node, payload_type_value);
}

static IrInstruction *ir_gen_undefined_literal(IrBuilder *irb, Scope *scope, AstNode *node) {
    assert(node->type == NodeTypeUndefinedLiteral);
    return ir_build_const_undefined(irb, scope, node);
}

static IrInstruction *ir_gen_asm_expr(IrBuilder *irb, Scope *scope, AstNode *node) {
    assert(node->type == NodeTypeAsmExpr);

    IrInstruction **input_list = allocate<IrInstruction *>(node->data.asm_expr.input_list.length);
    IrInstruction **output_types = allocate<IrInstruction *>(node->data.asm_expr.output_list.length);
    ZigVar **output_vars = allocate<ZigVar *>(node->data.asm_expr.output_list.length);
    size_t return_count = 0;
    bool is_volatile = node->data.asm_expr.is_volatile;
    if (!is_volatile && node->data.asm_expr.output_list.length == 0) {
        add_node_error(irb->codegen, node,
                buf_sprintf("assembly expression with no output must be marked volatile"));
        return irb->codegen->invalid_instruction;
    }
    for (size_t i = 0; i < node->data.asm_expr.output_list.length; i += 1) {
        AsmOutput *asm_output = node->data.asm_expr.output_list.at(i);
        if (asm_output->return_type) {
            return_count += 1;

            IrInstruction *return_type = ir_gen_node(irb, asm_output->return_type, scope);
            if (return_type == irb->codegen->invalid_instruction)
                return irb->codegen->invalid_instruction;
            if (return_count > 1) {
                add_node_error(irb->codegen, node,
                        buf_sprintf("inline assembly allows up to one output value"));
                return irb->codegen->invalid_instruction;
            }
            output_types[i] = return_type;
        } else {
            Buf *variable_name = asm_output->variable_name;
            // TODO there is some duplication here with ir_gen_symbol. I need to do a full audit of how
            // inline assembly works. https://github.com/ziglang/zig/issues/215
            ZigVar *var = find_variable(irb->codegen, scope, variable_name, nullptr);
            if (var) {
                output_vars[i] = var;
            } else {
                add_node_error(irb->codegen, node,
                        buf_sprintf("use of undeclared identifier '%s'", buf_ptr(variable_name)));
                return irb->codegen->invalid_instruction;
            }
        }

        const char modifier = *buf_ptr(asm_output->constraint);
        if (modifier != '=') {
            add_node_error(irb->codegen, node,
                buf_sprintf("invalid modifier starting output constraint for '%s': '%c', only '=' is supported."
                    " Compiler TODO: see https://github.com/ziglang/zig/issues/215",
                    buf_ptr(asm_output->asm_symbolic_name), modifier));
            return irb->codegen->invalid_instruction;
        }
    }
    for (size_t i = 0; i < node->data.asm_expr.input_list.length; i += 1) {
        AsmInput *asm_input = node->data.asm_expr.input_list.at(i);
        IrInstruction *input_value = ir_gen_node(irb, asm_input->expr, scope);
        if (input_value == irb->codegen->invalid_instruction)
            return irb->codegen->invalid_instruction;

        input_list[i] = input_value;
    }

    return ir_build_asm(irb, scope, node, input_list, output_types, output_vars, return_count, is_volatile);
}

static IrInstruction *ir_gen_if_optional_expr(IrBuilder *irb, Scope *scope, AstNode *node) {
    assert(node->type == NodeTypeIfOptional);

    Buf *var_symbol = node->data.test_expr.var_symbol;
    AstNode *expr_node = node->data.test_expr.target_node;
    AstNode *then_node = node->data.test_expr.then_node;
    AstNode *else_node = node->data.test_expr.else_node;
    bool var_is_ptr = node->data.test_expr.var_is_ptr;

    IrInstruction *maybe_val_ptr = ir_gen_node_extra(irb, expr_node, scope, LValPtr);
    if (maybe_val_ptr == irb->codegen->invalid_instruction)
        return maybe_val_ptr;

    IrInstruction *maybe_val = ir_build_load_ptr(irb, scope, node, maybe_val_ptr);
    IrInstruction *is_non_null = ir_build_test_nonnull(irb, scope, node, maybe_val);

    IrBasicBlock *then_block = ir_create_basic_block(irb, scope, "OptionalThen");
    IrBasicBlock *else_block = ir_create_basic_block(irb, scope, "OptionalElse");
    IrBasicBlock *endif_block = ir_create_basic_block(irb, scope, "OptionalEndIf");

    IrInstruction *is_comptime;
    if (ir_should_inline(irb->exec, scope)) {
        is_comptime = ir_build_const_bool(irb, scope, node, true);
    } else {
        is_comptime = ir_build_test_comptime(irb, scope, node, is_non_null);
    }
    ir_build_cond_br(irb, scope, node, is_non_null, then_block, else_block, is_comptime);

    ir_set_cursor_at_end_and_append_block(irb, then_block);

    Scope *subexpr_scope = create_runtime_scope(irb->codegen, node, scope, is_comptime);
    Scope *var_scope;
    if (var_symbol) {
        IrInstruction *var_type = nullptr;
        bool is_shadowable = false;
        bool is_const = true;
        ZigVar *var = ir_create_var(irb, node, subexpr_scope,
                var_symbol, is_const, is_const, is_shadowable, is_comptime);

        IrInstruction *var_ptr_value = ir_build_optional_unwrap_ptr(irb, subexpr_scope, node, maybe_val_ptr, false);
        IrInstruction *var_value = var_is_ptr ? var_ptr_value : ir_build_load_ptr(irb, subexpr_scope, node, var_ptr_value);
        ir_build_var_decl_src(irb, subexpr_scope, node, var, var_type, nullptr, var_value);
        var_scope = var->child_scope;
    } else {
        var_scope = subexpr_scope;
    }
    IrInstruction *then_expr_result = ir_gen_node(irb, then_node, var_scope);
    if (then_expr_result == irb->codegen->invalid_instruction)
        return then_expr_result;
    IrBasicBlock *after_then_block = irb->current_basic_block;
    if (!instr_is_unreachable(then_expr_result))
        ir_mark_gen(ir_build_br(irb, scope, node, endif_block, is_comptime));

    ir_set_cursor_at_end_and_append_block(irb, else_block);
    IrInstruction *else_expr_result;
    if (else_node) {
        else_expr_result = ir_gen_node(irb, else_node, subexpr_scope);
        if (else_expr_result == irb->codegen->invalid_instruction)
            return else_expr_result;
    } else {
        else_expr_result = ir_build_const_void(irb, scope, node);
    }
    IrBasicBlock *after_else_block = irb->current_basic_block;
    if (!instr_is_unreachable(else_expr_result))
        ir_mark_gen(ir_build_br(irb, scope, node, endif_block, is_comptime));

    ir_set_cursor_at_end_and_append_block(irb, endif_block);
    IrInstruction **incoming_values = allocate<IrInstruction *>(2);
    incoming_values[0] = then_expr_result;
    incoming_values[1] = else_expr_result;
    IrBasicBlock **incoming_blocks = allocate<IrBasicBlock *>(2);
    incoming_blocks[0] = after_then_block;
    incoming_blocks[1] = after_else_block;

    return ir_build_phi(irb, scope, node, 2, incoming_blocks, incoming_values);
}

static IrInstruction *ir_gen_if_err_expr(IrBuilder *irb, Scope *scope, AstNode *node) {
    assert(node->type == NodeTypeIfErrorExpr);

    AstNode *target_node = node->data.if_err_expr.target_node;
    AstNode *then_node = node->data.if_err_expr.then_node;
    AstNode *else_node = node->data.if_err_expr.else_node;
    bool var_is_ptr = node->data.if_err_expr.var_is_ptr;
    bool var_is_const = true;
    Buf *var_symbol = node->data.if_err_expr.var_symbol;
    Buf *err_symbol = node->data.if_err_expr.err_symbol;

    IrInstruction *err_val_ptr = ir_gen_node_extra(irb, target_node, scope, LValPtr);
    if (err_val_ptr == irb->codegen->invalid_instruction)
        return err_val_ptr;

    IrInstruction *err_val = ir_build_load_ptr(irb, scope, node, err_val_ptr);
    IrInstruction *is_err = ir_build_test_err(irb, scope, node, err_val);

    IrBasicBlock *ok_block = ir_create_basic_block(irb, scope, "TryOk");
    IrBasicBlock *else_block = ir_create_basic_block(irb, scope, "TryElse");
    IrBasicBlock *endif_block = ir_create_basic_block(irb, scope, "TryEnd");

    bool force_comptime = ir_should_inline(irb->exec, scope);
    IrInstruction *is_comptime = force_comptime ? ir_build_const_bool(irb, scope, node, true) : ir_build_test_comptime(irb, scope, node, is_err);
    ir_build_cond_br(irb, scope, node, is_err, else_block, ok_block, is_comptime);

    ir_set_cursor_at_end_and_append_block(irb, ok_block);

    Scope *subexpr_scope = create_runtime_scope(irb->codegen, node, scope, is_comptime);
    Scope *var_scope;
    if (var_symbol) {
        IrInstruction *var_type = nullptr;
        bool is_shadowable = false;
        IrInstruction *var_is_comptime = force_comptime ? ir_build_const_bool(irb, subexpr_scope, node, true) : ir_build_test_comptime(irb, subexpr_scope, node, err_val);
        ZigVar *var = ir_create_var(irb, node, subexpr_scope,
                var_symbol, var_is_const, var_is_const, is_shadowable, var_is_comptime);

        IrInstruction *var_ptr_value = ir_build_unwrap_err_payload(irb, subexpr_scope, node, err_val_ptr, false);
        IrInstruction *var_value = var_is_ptr ? var_ptr_value : ir_build_load_ptr(irb, subexpr_scope, node, var_ptr_value);
        ir_build_var_decl_src(irb, subexpr_scope, node, var, var_type, nullptr, var_value);
        var_scope = var->child_scope;
    } else {
        var_scope = subexpr_scope;
    }
    IrInstruction *then_expr_result = ir_gen_node(irb, then_node, var_scope);
    if (then_expr_result == irb->codegen->invalid_instruction)
        return then_expr_result;
    IrBasicBlock *after_then_block = irb->current_basic_block;
    if (!instr_is_unreachable(then_expr_result))
        ir_mark_gen(ir_build_br(irb, scope, node, endif_block, is_comptime));

    ir_set_cursor_at_end_and_append_block(irb, else_block);

    IrInstruction *else_expr_result;
    if (else_node) {
        Scope *err_var_scope;
        if (err_symbol) {
            IrInstruction *var_type = nullptr;
            bool is_shadowable = false;
            bool is_const = true;
            ZigVar *var = ir_create_var(irb, node, subexpr_scope,
                    err_symbol, is_const, is_const, is_shadowable, is_comptime);

            IrInstruction *var_value = ir_build_unwrap_err_code(irb, subexpr_scope, node, err_val_ptr);
            ir_build_var_decl_src(irb, subexpr_scope, node, var, var_type, nullptr, var_value);
            err_var_scope = var->child_scope;
        } else {
            err_var_scope = subexpr_scope;
        }
        else_expr_result = ir_gen_node(irb, else_node, err_var_scope);
        if (else_expr_result == irb->codegen->invalid_instruction)
            return else_expr_result;
    } else {
        else_expr_result = ir_build_const_void(irb, scope, node);
    }
    IrBasicBlock *after_else_block = irb->current_basic_block;
    if (!instr_is_unreachable(else_expr_result))
        ir_mark_gen(ir_build_br(irb, scope, node, endif_block, is_comptime));

    ir_set_cursor_at_end_and_append_block(irb, endif_block);
    IrInstruction **incoming_values = allocate<IrInstruction *>(2);
    incoming_values[0] = then_expr_result;
    incoming_values[1] = else_expr_result;
    IrBasicBlock **incoming_blocks = allocate<IrBasicBlock *>(2);
    incoming_blocks[0] = after_then_block;
    incoming_blocks[1] = after_else_block;

    return ir_build_phi(irb, scope, node, 2, incoming_blocks, incoming_values);
}

static bool ir_gen_switch_prong_expr(IrBuilder *irb, Scope *scope, AstNode *switch_node, AstNode *prong_node,
        IrBasicBlock *end_block, IrInstruction *is_comptime, IrInstruction *var_is_comptime,
        IrInstruction *target_value_ptr, IrInstruction *prong_value,
        ZigList<IrBasicBlock *> *incoming_blocks, ZigList<IrInstruction *> *incoming_values)
{
    assert(switch_node->type == NodeTypeSwitchExpr);
    assert(prong_node->type == NodeTypeSwitchProng);

    AstNode *expr_node = prong_node->data.switch_prong.expr;
    AstNode *var_symbol_node = prong_node->data.switch_prong.var_symbol;
    Scope *child_scope;
    if (var_symbol_node) {
        assert(var_symbol_node->type == NodeTypeSymbol);
        Buf *var_name = var_symbol_node->data.symbol_expr.symbol;
        bool var_is_ptr = prong_node->data.switch_prong.var_is_ptr;

        bool is_shadowable = false;
        bool is_const = true;
        ZigVar *var = ir_create_var(irb, var_symbol_node, scope,
                var_name, is_const, is_const, is_shadowable, var_is_comptime);
        child_scope = var->child_scope;
        IrInstruction *var_value;
        if (prong_value) {
            IrInstruction *var_ptr_value = ir_build_switch_var(irb, scope, var_symbol_node, target_value_ptr, prong_value);
            var_value = var_is_ptr ? var_ptr_value : ir_build_load_ptr(irb, scope, var_symbol_node, var_ptr_value);
        } else {
            var_value = var_is_ptr ? target_value_ptr : ir_build_load_ptr(irb, scope, var_symbol_node, target_value_ptr);
        }
        IrInstruction *var_type = nullptr; // infer the type
        ir_build_var_decl_src(irb, scope, var_symbol_node, var, var_type, nullptr, var_value);
    } else {
        child_scope = scope;
    }

    IrInstruction *expr_result = ir_gen_node(irb, expr_node, child_scope);
    if (expr_result == irb->codegen->invalid_instruction)
        return false;
    if (!instr_is_unreachable(expr_result))
        ir_mark_gen(ir_build_br(irb, scope, switch_node, end_block, is_comptime));
    incoming_blocks->append(irb->current_basic_block);
    incoming_values->append(expr_result);
    return true;
}

static IrInstruction *ir_gen_switch_expr(IrBuilder *irb, Scope *scope, AstNode *node) {
    assert(node->type == NodeTypeSwitchExpr);

    AstNode *target_node = node->data.switch_expr.expr;
    IrInstruction *target_value_ptr = ir_gen_node_extra(irb, target_node, scope, LValPtr);
    if (target_value_ptr == irb->codegen->invalid_instruction)
        return target_value_ptr;
    IrInstruction *target_value = ir_build_switch_target(irb, scope, node, target_value_ptr);

    IrBasicBlock *else_block = ir_create_basic_block(irb, scope, "SwitchElse");
    IrBasicBlock *end_block = ir_create_basic_block(irb, scope, "SwitchEnd");

    size_t prong_count = node->data.switch_expr.prongs.length;
    ZigList<IrInstructionSwitchBrCase> cases = {0};

    IrInstruction *is_comptime;
    IrInstruction *var_is_comptime;
    if (ir_should_inline(irb->exec, scope)) {
        is_comptime = ir_build_const_bool(irb, scope, node, true);
        var_is_comptime = is_comptime;
    } else {
        is_comptime = ir_build_test_comptime(irb, scope, node, target_value);
        var_is_comptime = ir_build_test_comptime(irb, scope, node, target_value_ptr);
    }

    ZigList<IrInstruction *> incoming_values = {0};
    ZigList<IrBasicBlock *> incoming_blocks = {0};
    ZigList<IrInstructionCheckSwitchProngsRange> check_ranges = {0};

    // First do the else and the ranges
    Scope *subexpr_scope = create_runtime_scope(irb->codegen, node, scope, is_comptime);
    Scope *comptime_scope = create_comptime_scope(irb->codegen, node, scope);
    AstNode *else_prong = nullptr;
    for (size_t prong_i = 0; prong_i < prong_count; prong_i += 1) {
        AstNode *prong_node = node->data.switch_expr.prongs.at(prong_i);
        size_t prong_item_count = prong_node->data.switch_prong.items.length;
        if (prong_item_count == 0) {
            if (else_prong) {
                ErrorMsg *msg = add_node_error(irb->codegen, prong_node,
                        buf_sprintf("multiple else prongs in switch expression"));
                add_error_note(irb->codegen, msg, else_prong,
                        buf_sprintf("previous else prong is here"));
                return irb->codegen->invalid_instruction;
            }
            else_prong = prong_node;

            IrBasicBlock *prev_block = irb->current_basic_block;
            ir_set_cursor_at_end_and_append_block(irb, else_block);
            if (!ir_gen_switch_prong_expr(irb, subexpr_scope, node, prong_node, end_block,
                is_comptime, var_is_comptime, target_value_ptr, nullptr, &incoming_blocks, &incoming_values))
            {
                return irb->codegen->invalid_instruction;
            }
            ir_set_cursor_at_end(irb, prev_block);
        } else if (prong_node->data.switch_prong.any_items_are_range) {
            IrInstruction *ok_bit = nullptr;
            AstNode *last_item_node = nullptr;
            for (size_t item_i = 0; item_i < prong_item_count; item_i += 1) {
                AstNode *item_node = prong_node->data.switch_prong.items.at(item_i);
                last_item_node = item_node;
                if (item_node->type == NodeTypeSwitchRange) {
                    AstNode *start_node = item_node->data.switch_range.start;
                    AstNode *end_node = item_node->data.switch_range.end;

                    IrInstruction *start_value = ir_gen_node(irb, start_node, comptime_scope);
                    if (start_value == irb->codegen->invalid_instruction)
                        return irb->codegen->invalid_instruction;

                    IrInstruction *end_value = ir_gen_node(irb, end_node, comptime_scope);
                    if (end_value == irb->codegen->invalid_instruction)
                        return irb->codegen->invalid_instruction;

                    IrInstructionCheckSwitchProngsRange *check_range = check_ranges.add_one();
                    check_range->start = start_value;
                    check_range->end = end_value;

                    IrInstruction *lower_range_ok = ir_build_bin_op(irb, scope, item_node, IrBinOpCmpGreaterOrEq,
                            target_value, start_value, false);
                    IrInstruction *upper_range_ok = ir_build_bin_op(irb, scope, item_node, IrBinOpCmpLessOrEq,
                            target_value, end_value, false);
                    IrInstruction *both_ok = ir_build_bin_op(irb, scope, item_node, IrBinOpBoolAnd,
                            lower_range_ok, upper_range_ok, false);
                    if (ok_bit) {
                        ok_bit = ir_build_bin_op(irb, scope, item_node, IrBinOpBoolOr, both_ok, ok_bit, false);
                    } else {
                        ok_bit = both_ok;
                    }
                } else {
                    IrInstruction *item_value = ir_gen_node(irb, item_node, comptime_scope);
                    if (item_value == irb->codegen->invalid_instruction)
                        return irb->codegen->invalid_instruction;

                    IrInstructionCheckSwitchProngsRange *check_range = check_ranges.add_one();
                    check_range->start = item_value;
                    check_range->end = item_value;

                    IrInstruction *cmp_ok = ir_build_bin_op(irb, scope, item_node, IrBinOpCmpEq,
                            item_value, target_value, false);
                    if (ok_bit) {
                        ok_bit = ir_build_bin_op(irb, scope, item_node, IrBinOpBoolOr, cmp_ok, ok_bit, false);
                    } else {
                        ok_bit = cmp_ok;
                    }
                }
            }

            IrBasicBlock *range_block_yes = ir_create_basic_block(irb, scope, "SwitchRangeYes");
            IrBasicBlock *range_block_no = ir_create_basic_block(irb, scope, "SwitchRangeNo");

            assert(ok_bit);
            assert(last_item_node);
            ir_mark_gen(ir_build_cond_br(irb, scope, last_item_node, ok_bit, range_block_yes,
                        range_block_no, is_comptime));

            ir_set_cursor_at_end_and_append_block(irb, range_block_yes);
            if (!ir_gen_switch_prong_expr(irb, subexpr_scope, node, prong_node, end_block,
                is_comptime, var_is_comptime, target_value_ptr, nullptr, &incoming_blocks, &incoming_values))
            {
                return irb->codegen->invalid_instruction;
            }

            ir_set_cursor_at_end_and_append_block(irb, range_block_no);
        }
    }

    // next do the non-else non-ranges
    for (size_t prong_i = 0; prong_i < prong_count; prong_i += 1) {
        AstNode *prong_node = node->data.switch_expr.prongs.at(prong_i);
        size_t prong_item_count = prong_node->data.switch_prong.items.length;
        if (prong_item_count == 0)
            continue;
        if (prong_node->data.switch_prong.any_items_are_range)
            continue;

        IrBasicBlock *prong_block = ir_create_basic_block(irb, scope, "SwitchProng");
        IrInstruction *last_item_value = nullptr;

        for (size_t item_i = 0; item_i < prong_item_count; item_i += 1) {
            AstNode *item_node = prong_node->data.switch_prong.items.at(item_i);
            assert(item_node->type != NodeTypeSwitchRange);

            IrInstruction *item_value = ir_gen_node(irb, item_node, comptime_scope);
            if (item_value == irb->codegen->invalid_instruction)
                return irb->codegen->invalid_instruction;

            IrInstructionCheckSwitchProngsRange *check_range = check_ranges.add_one();
            check_range->start = item_value;
            check_range->end = item_value;

            IrInstructionSwitchBrCase *this_case = cases.add_one();
            this_case->value = item_value;
            this_case->block = prong_block;

            last_item_value = item_value;
        }
        IrInstruction *only_item_value = (prong_item_count == 1) ? last_item_value : nullptr;

        IrBasicBlock *prev_block = irb->current_basic_block;
        ir_set_cursor_at_end_and_append_block(irb, prong_block);
        if (!ir_gen_switch_prong_expr(irb, subexpr_scope, node, prong_node, end_block,
            is_comptime, var_is_comptime, target_value_ptr, only_item_value, &incoming_blocks, &incoming_values))
        {
            return irb->codegen->invalid_instruction;
        }

        ir_set_cursor_at_end(irb, prev_block);

    }

    IrInstruction *switch_prongs_void = ir_build_check_switch_prongs(irb, scope, node, target_value, check_ranges.items, check_ranges.length,
            else_prong != nullptr);

    if (cases.length == 0) {
        ir_build_br(irb, scope, node, else_block, is_comptime);
    } else {
        ir_build_switch_br(irb, scope, node, target_value, else_block, cases.length, cases.items, is_comptime, switch_prongs_void);
    }

    if (!else_prong) {
        ir_set_cursor_at_end_and_append_block(irb, else_block);
        ir_build_unreachable(irb, scope, node);
    }

    ir_set_cursor_at_end_and_append_block(irb, end_block);
    assert(incoming_blocks.length == incoming_values.length);
    if (incoming_blocks.length == 0) {
        return ir_build_const_void(irb, scope, node);
    } else {
        return ir_build_phi(irb, scope, node, incoming_blocks.length, incoming_blocks.items, incoming_values.items);
    }
}

static IrInstruction *ir_gen_comptime(IrBuilder *irb, Scope *parent_scope, AstNode *node, LVal lval) {
    assert(node->type == NodeTypeCompTime);

    Scope *child_scope = create_comptime_scope(irb->codegen, node, parent_scope);
    return ir_gen_node_extra(irb, node->data.comptime_expr.expr, child_scope, lval);
}

static IrInstruction *ir_gen_return_from_block(IrBuilder *irb, Scope *break_scope, AstNode *node, ScopeBlock *block_scope) {
    IrInstruction *is_comptime;
    if (ir_should_inline(irb->exec, break_scope)) {
        is_comptime = ir_build_const_bool(irb, break_scope, node, true);
    } else {
        is_comptime = block_scope->is_comptime;
    }

    IrInstruction *result_value;
    if (node->data.break_expr.expr) {
        result_value = ir_gen_node(irb, node->data.break_expr.expr, break_scope);
        if (result_value == irb->codegen->invalid_instruction)
            return irb->codegen->invalid_instruction;
    } else {
        result_value = ir_build_const_void(irb, break_scope, node);
    }

    IrBasicBlock *dest_block = block_scope->end_block;
    ir_gen_defers_for_block(irb, break_scope, dest_block->scope, false);

    block_scope->incoming_blocks->append(irb->current_basic_block);
    block_scope->incoming_values->append(result_value);
    return ir_build_br(irb, break_scope, node, dest_block, is_comptime);
}

static IrInstruction *ir_gen_break(IrBuilder *irb, Scope *break_scope, AstNode *node) {
    assert(node->type == NodeTypeBreak);

    // Search up the scope. We'll find one of these things first:
    // * function definition scope or global scope => error, break outside loop
    // * defer expression scope => error, cannot break out of defer expression
    // * loop scope => OK
    // * (if it's a labeled break) labeled block => OK

    Scope *search_scope = break_scope;
    ScopeLoop *loop_scope;
    for (;;) {
        if (search_scope == nullptr || search_scope->id == ScopeIdFnDef) {
            if (node->data.break_expr.name != nullptr) {
                add_node_error(irb->codegen, node, buf_sprintf("label not found: '%s'", buf_ptr(node->data.break_expr.name)));
                return irb->codegen->invalid_instruction;
            } else {
                add_node_error(irb->codegen, node, buf_sprintf("break expression outside loop"));
                return irb->codegen->invalid_instruction;
            }
        } else if (search_scope->id == ScopeIdDeferExpr) {
            add_node_error(irb->codegen, node, buf_sprintf("cannot break out of defer expression"));
            return irb->codegen->invalid_instruction;
        } else if (search_scope->id == ScopeIdLoop) {
            ScopeLoop *this_loop_scope = (ScopeLoop *)search_scope;
            if (node->data.break_expr.name == nullptr ||
                (this_loop_scope->name != nullptr && buf_eql_buf(node->data.break_expr.name, this_loop_scope->name)))
            {
                loop_scope = this_loop_scope;
                break;
            }
        } else if (search_scope->id == ScopeIdBlock) {
            ScopeBlock *this_block_scope = (ScopeBlock *)search_scope;
            if (node->data.break_expr.name != nullptr &&
                (this_block_scope->name != nullptr && buf_eql_buf(node->data.break_expr.name, this_block_scope->name)))
            {
                assert(this_block_scope->end_block != nullptr);
                return ir_gen_return_from_block(irb, break_scope, node, this_block_scope);
            }
        } else if (search_scope->id == ScopeIdSuspend) {
            add_node_error(irb->codegen, node, buf_sprintf("cannot break out of suspend block"));
            return irb->codegen->invalid_instruction;
        }
        search_scope = search_scope->parent;
    }

    IrInstruction *is_comptime;
    if (ir_should_inline(irb->exec, break_scope)) {
        is_comptime = ir_build_const_bool(irb, break_scope, node, true);
    } else {
        is_comptime = loop_scope->is_comptime;
    }

    IrInstruction *result_value;
    if (node->data.break_expr.expr) {
        result_value = ir_gen_node(irb, node->data.break_expr.expr, break_scope);
        if (result_value == irb->codegen->invalid_instruction)
            return irb->codegen->invalid_instruction;
    } else {
        result_value = ir_build_const_void(irb, break_scope, node);
    }

    IrBasicBlock *dest_block = loop_scope->break_block;
    ir_gen_defers_for_block(irb, break_scope, dest_block->scope, false);

    loop_scope->incoming_blocks->append(irb->current_basic_block);
    loop_scope->incoming_values->append(result_value);
    return ir_build_br(irb, break_scope, node, dest_block, is_comptime);
}

static IrInstruction *ir_gen_continue(IrBuilder *irb, Scope *continue_scope, AstNode *node) {
    assert(node->type == NodeTypeContinue);

    // Search up the scope. We'll find one of these things first:
    // * function definition scope or global scope => error, break outside loop
    // * defer expression scope => error, cannot break out of defer expression
    // * loop scope => OK

    ZigList<ScopeRuntime *> runtime_scopes = {};

    Scope *search_scope = continue_scope;
    ScopeLoop *loop_scope;
    for (;;) {
        if (search_scope == nullptr || search_scope->id == ScopeIdFnDef) {
            if (node->data.continue_expr.name != nullptr) {
                add_node_error(irb->codegen, node, buf_sprintf("labeled loop not found: '%s'", buf_ptr(node->data.continue_expr.name)));
                return irb->codegen->invalid_instruction;
            } else {
                add_node_error(irb->codegen, node, buf_sprintf("continue expression outside loop"));
                return irb->codegen->invalid_instruction;
            }
        } else if (search_scope->id == ScopeIdDeferExpr) {
            add_node_error(irb->codegen, node, buf_sprintf("cannot continue out of defer expression"));
            return irb->codegen->invalid_instruction;
        } else if (search_scope->id == ScopeIdLoop) {
            ScopeLoop *this_loop_scope = (ScopeLoop *)search_scope;
            if (node->data.continue_expr.name == nullptr ||
                (this_loop_scope->name != nullptr && buf_eql_buf(node->data.continue_expr.name, this_loop_scope->name)))
            {
                loop_scope = this_loop_scope;
                break;
            }
        } else if (search_scope->id == ScopeIdRuntime) {
            ScopeRuntime *scope_runtime = (ScopeRuntime *)search_scope;
            runtime_scopes.append(scope_runtime);
        }
        search_scope = search_scope->parent;
    }

    IrInstruction *is_comptime;
    if (ir_should_inline(irb->exec, continue_scope)) {
        is_comptime = ir_build_const_bool(irb, continue_scope, node, true);
    } else {
        is_comptime = loop_scope->is_comptime;
    }

    for (size_t i = 0; i < runtime_scopes.length; i += 1) {
        ScopeRuntime *scope_runtime = runtime_scopes.at(i);
        ir_mark_gen(ir_build_check_runtime_scope(irb, continue_scope, node, scope_runtime->is_comptime, is_comptime));
    }

    IrBasicBlock *dest_block = loop_scope->continue_block;
    ir_gen_defers_for_block(irb, continue_scope, dest_block->scope, false);
    return ir_mark_gen(ir_build_br(irb, continue_scope, node, dest_block, is_comptime));
}

static IrInstruction *ir_gen_error_type(IrBuilder *irb, Scope *scope, AstNode *node) {
    assert(node->type == NodeTypeErrorType);
    return ir_build_const_type(irb, scope, node, irb->codegen->builtin_types.entry_global_error_set);
}

static IrInstruction *ir_gen_defer(IrBuilder *irb, Scope *parent_scope, AstNode *node) {
    assert(node->type == NodeTypeDefer);

    ScopeDefer *defer_child_scope = create_defer_scope(irb->codegen, node, parent_scope);
    node->data.defer.child_scope = &defer_child_scope->base;

    ScopeDeferExpr *defer_expr_scope = create_defer_expr_scope(irb->codegen, node, parent_scope);
    node->data.defer.expr_scope = &defer_expr_scope->base;

    return ir_build_const_void(irb, parent_scope, node);
}

static IrInstruction *ir_gen_slice(IrBuilder *irb, Scope *scope, AstNode *node) {
    assert(node->type == NodeTypeSliceExpr);

    AstNodeSliceExpr *slice_expr = &node->data.slice_expr;
    AstNode *array_node = slice_expr->array_ref_expr;
    AstNode *start_node = slice_expr->start;
    AstNode *end_node = slice_expr->end;

    IrInstruction *ptr_value = ir_gen_node_extra(irb, array_node, scope, LValPtr);
    if (ptr_value == irb->codegen->invalid_instruction)
        return irb->codegen->invalid_instruction;

    IrInstruction *start_value = ir_gen_node(irb, start_node, scope);
    if (start_value == irb->codegen->invalid_instruction)
        return irb->codegen->invalid_instruction;

    IrInstruction *end_value;
    if (end_node) {
        end_value = ir_gen_node(irb, end_node, scope);
        if (end_value == irb->codegen->invalid_instruction)
            return irb->codegen->invalid_instruction;
    } else {
        end_value = nullptr;
    }

    return ir_build_slice(irb, scope, node, ptr_value, start_value, end_value, true);
}

static IrInstruction *ir_gen_catch(IrBuilder *irb, Scope *parent_scope, AstNode *node) {
    assert(node->type == NodeTypeUnwrapErrorExpr);

    AstNode *op1_node = node->data.unwrap_err_expr.op1;
    AstNode *op2_node = node->data.unwrap_err_expr.op2;
    AstNode *var_node = node->data.unwrap_err_expr.symbol;

    if (op2_node->type == NodeTypeUnreachable) {
        if (var_node != nullptr) {
            assert(var_node->type == NodeTypeSymbol);
            Buf *var_name = var_node->data.symbol_expr.symbol;
            add_node_error(irb->codegen, var_node, buf_sprintf("unused variable: '%s'", buf_ptr(var_name)));
            return irb->codegen->invalid_instruction;
        }
        return ir_gen_catch_unreachable(irb, parent_scope, node, op1_node, LValNone);
    }


    IrInstruction *err_union_ptr = ir_gen_node_extra(irb, op1_node, parent_scope, LValPtr);
    if (err_union_ptr == irb->codegen->invalid_instruction)
        return irb->codegen->invalid_instruction;

    IrInstruction *err_union_val = ir_build_load_ptr(irb, parent_scope, node, err_union_ptr);
    IrInstruction *is_err = ir_build_test_err(irb, parent_scope, node, err_union_val);

    IrInstruction *is_comptime;
    if (ir_should_inline(irb->exec, parent_scope)) {
        is_comptime = ir_build_const_bool(irb, parent_scope, node, true);
    } else {
        is_comptime = ir_build_test_comptime(irb, parent_scope, node, is_err);
    }

    IrBasicBlock *ok_block = ir_create_basic_block(irb, parent_scope, "UnwrapErrOk");
    IrBasicBlock *err_block = ir_create_basic_block(irb, parent_scope, "UnwrapErrError");
    IrBasicBlock *end_block = ir_create_basic_block(irb, parent_scope, "UnwrapErrEnd");
    ir_build_cond_br(irb, parent_scope, node, is_err, err_block, ok_block, is_comptime);

    ir_set_cursor_at_end_and_append_block(irb, err_block);
    Scope *err_scope;
    if (var_node) {
        assert(var_node->type == NodeTypeSymbol);
        Buf *var_name = var_node->data.symbol_expr.symbol;
        bool is_const = true;
        bool is_shadowable = false;
        ZigVar *var = ir_create_var(irb, node, parent_scope, var_name,
            is_const, is_const, is_shadowable, is_comptime);
        err_scope = var->child_scope;
        IrInstruction *err_val = ir_build_unwrap_err_code(irb, err_scope, node, err_union_ptr);
        ir_build_var_decl_src(irb, err_scope, var_node, var, nullptr, nullptr, err_val);
    } else {
        err_scope = parent_scope;
    }
    IrInstruction *err_result = ir_gen_node(irb, op2_node, err_scope);
    if (err_result == irb->codegen->invalid_instruction)
        return irb->codegen->invalid_instruction;
    IrBasicBlock *after_err_block = irb->current_basic_block;
    if (!instr_is_unreachable(err_result))
        ir_mark_gen(ir_build_br(irb, err_scope, node, end_block, is_comptime));

    ir_set_cursor_at_end_and_append_block(irb, ok_block);
    IrInstruction *unwrapped_ptr = ir_build_unwrap_err_payload(irb, parent_scope, node, err_union_ptr, false);
    IrInstruction *unwrapped_payload = ir_build_load_ptr(irb, parent_scope, node, unwrapped_ptr);
    IrBasicBlock *after_ok_block = irb->current_basic_block;
    ir_build_br(irb, parent_scope, node, end_block, is_comptime);

    ir_set_cursor_at_end_and_append_block(irb, end_block);
    IrInstruction **incoming_values = allocate<IrInstruction *>(2);
    incoming_values[0] = err_result;
    incoming_values[1] = unwrapped_payload;
    IrBasicBlock **incoming_blocks = allocate<IrBasicBlock *>(2);
    incoming_blocks[0] = after_err_block;
    incoming_blocks[1] = after_ok_block;
    return ir_build_phi(irb, parent_scope, node, 2, incoming_blocks, incoming_values);
}

static bool render_instance_name_recursive(CodeGen *codegen, Buf *name, Scope *outer_scope, Scope *inner_scope) {
    if (inner_scope == nullptr || inner_scope == outer_scope) return false;
    bool need_comma = render_instance_name_recursive(codegen, name, outer_scope, inner_scope->parent);
    if (inner_scope->id != ScopeIdVarDecl)
        return need_comma;

    ScopeVarDecl *var_scope = (ScopeVarDecl *)inner_scope;
    if (need_comma)
        buf_append_char(name, ',');
    // TODO: const ptr reinterpret here to make the var type agree with the value?
    render_const_value(codegen, name, var_scope->var->const_value);
    return true;
}

static Buf *get_anon_type_name(CodeGen *codegen, IrExecutable *exec, const char *kind_name, AstNode *source_node) {
    if (exec->name) {
        return exec->name;
    } else if (exec->name_fn != nullptr) {
        Buf *name = buf_alloc();
        buf_append_buf(name, &exec->name_fn->symbol_name);
        buf_appendf(name, "(");
        render_instance_name_recursive(codegen, name, &exec->name_fn->fndef_scope->base, exec->begin_scope);
        buf_appendf(name, ")");
        return name;
    } else {
        //Note: C-imports do not have valid location information
        return buf_sprintf("(anonymous %s at %s:%" ZIG_PRI_usize ":%" ZIG_PRI_usize ")", kind_name,
            (source_node->owner->path != nullptr) ? buf_ptr(source_node->owner->path) : "(null)", source_node->line + 1, source_node->column + 1);
    }
}

static IrInstruction *ir_gen_container_decl(IrBuilder *irb, Scope *parent_scope, AstNode *node) {
    assert(node->type == NodeTypeContainerDecl);

    ContainerKind kind = node->data.container_decl.kind;
    Buf *name = get_anon_type_name(irb->codegen, irb->exec, container_string(kind), node);

    VisibMod visib_mod = VisibModPub;
    TldContainer *tld_container = allocate<TldContainer>(1);
    init_tld(&tld_container->base, TldIdContainer, name, visib_mod, node, parent_scope);

    ContainerLayout layout = node->data.container_decl.layout;
    ZigType *container_type = get_partial_container_type(irb->codegen, parent_scope,
            kind, node, buf_ptr(name), layout);
    ScopeDecls *child_scope = get_container_scope(container_type);

    tld_container->type_entry = container_type;
    tld_container->decls_scope = child_scope;

    for (size_t i = 0; i < node->data.container_decl.decls.length; i += 1) {
        AstNode *child_node = node->data.container_decl.decls.at(i);
        scan_decls(irb->codegen, child_scope, child_node);
    }
    irb->codegen->resolve_queue.append(&tld_container->base);

    // Add this to the list to mark as invalid if analyzing this exec fails.
    irb->exec->tld_list.append(&tld_container->base);

    return ir_build_const_type(irb, parent_scope, node, container_type);
}

// errors should be populated with set1's values
static ZigType *get_error_set_union(CodeGen *g, ErrorTableEntry **errors, ZigType *set1, ZigType *set2) {
    assert(set1->id == ZigTypeIdErrorSet);
    assert(set2->id == ZigTypeIdErrorSet);

    ZigType *err_set_type = new_type_table_entry(ZigTypeIdErrorSet);
    buf_resize(&err_set_type->name, 0);
    buf_appendf(&err_set_type->name, "error{");

    for (uint32_t i = 0, count = set1->data.error_set.err_count; i < count; i += 1) {
        assert(errors[set1->data.error_set.errors[i]->value] == set1->data.error_set.errors[i]);
    }

    uint32_t count = set1->data.error_set.err_count;
    for (uint32_t i = 0; i < set2->data.error_set.err_count; i += 1) {
        ErrorTableEntry *error_entry = set2->data.error_set.errors[i];
        if (errors[error_entry->value] == nullptr) {
            count += 1;
        }
    }

    err_set_type->type_ref = g->builtin_types.entry_global_error_set->type_ref;
    err_set_type->di_type = g->builtin_types.entry_global_error_set->di_type;
    err_set_type->data.error_set.err_count = count;
    err_set_type->data.error_set.errors = allocate<ErrorTableEntry *>(count);

    for (uint32_t i = 0; i < set1->data.error_set.err_count; i += 1) {
        ErrorTableEntry *error_entry = set1->data.error_set.errors[i];
        buf_appendf(&err_set_type->name, "%s,", buf_ptr(&error_entry->name));
        err_set_type->data.error_set.errors[i] = error_entry;
    }

    uint32_t index = set1->data.error_set.err_count;
    for (uint32_t i = 0; i < set2->data.error_set.err_count; i += 1) {
        ErrorTableEntry *error_entry = set2->data.error_set.errors[i];
        if (errors[error_entry->value] == nullptr) {
            errors[error_entry->value] = error_entry;
            buf_appendf(&err_set_type->name, "%s,", buf_ptr(&error_entry->name));
            err_set_type->data.error_set.errors[index] = error_entry;
            index += 1;
        }
    }
    assert(index == count);
    assert(count != 0);

    buf_appendf(&err_set_type->name, "}");

    g->error_di_types.append(&err_set_type->di_type);

    return err_set_type;

}

static ZigType *make_err_set_with_one_item(CodeGen *g, Scope *parent_scope, AstNode *node,
        ErrorTableEntry *err_entry)
{
    ZigType *err_set_type = new_type_table_entry(ZigTypeIdErrorSet);
    buf_resize(&err_set_type->name, 0);
    buf_appendf(&err_set_type->name, "error{%s}", buf_ptr(&err_entry->name));
    err_set_type->type_ref = g->builtin_types.entry_global_error_set->type_ref;
    err_set_type->di_type = g->builtin_types.entry_global_error_set->di_type;
    err_set_type->data.error_set.err_count = 1;
    err_set_type->data.error_set.errors = allocate<ErrorTableEntry *>(1);

    g->error_di_types.append(&err_set_type->di_type);

    err_set_type->data.error_set.errors[0] = err_entry;

    return err_set_type;
}

static IrInstruction *ir_gen_err_set_decl(IrBuilder *irb, Scope *parent_scope, AstNode *node) {
    assert(node->type == NodeTypeErrorSetDecl);

    uint32_t err_count = node->data.err_set_decl.decls.length;

    Buf *type_name = get_anon_type_name(irb->codegen, irb->exec, "error set", node);
    ZigType *err_set_type = new_type_table_entry(ZigTypeIdErrorSet);
    buf_init_from_buf(&err_set_type->name, type_name);
    err_set_type->data.error_set.err_count = err_count;
    err_set_type->type_ref = irb->codegen->builtin_types.entry_global_error_set->type_ref;
    err_set_type->di_type = irb->codegen->builtin_types.entry_global_error_set->di_type;
    irb->codegen->error_di_types.append(&err_set_type->di_type);
    err_set_type->data.error_set.errors = allocate<ErrorTableEntry *>(err_count);

    ErrorTableEntry **errors = allocate<ErrorTableEntry *>(irb->codegen->errors_by_index.length + err_count);

    for (uint32_t i = 0; i < err_count; i += 1) {
        AstNode *symbol_node = node->data.err_set_decl.decls.at(i);
        assert(symbol_node->type == NodeTypeSymbol);
        Buf *err_name = symbol_node->data.symbol_expr.symbol;
        ErrorTableEntry *err = allocate<ErrorTableEntry>(1);
        err->decl_node = symbol_node;
        buf_init_from_buf(&err->name, err_name);

        auto existing_entry = irb->codegen->error_table.put_unique(err_name, err);
        if (existing_entry) {
            err->value = existing_entry->value->value;
        } else {
            size_t error_value_count = irb->codegen->errors_by_index.length;
            assert((uint32_t)error_value_count < (((uint32_t)1) << (uint32_t)irb->codegen->err_tag_type->data.integral.bit_count));
            err->value = error_value_count;
            irb->codegen->errors_by_index.append(err);
            irb->codegen->err_enumerators.append(ZigLLVMCreateDebugEnumerator(irb->codegen->dbuilder,
                buf_ptr(err_name), error_value_count));
        }
        err_set_type->data.error_set.errors[i] = err;

        ErrorTableEntry *prev_err = errors[err->value];
        if (prev_err != nullptr) {
            ErrorMsg *msg = add_node_error(irb->codegen, err->decl_node, buf_sprintf("duplicate error: '%s'", buf_ptr(&err->name)));
            add_error_note(irb->codegen, msg, prev_err->decl_node, buf_sprintf("other error here"));
            return irb->codegen->invalid_instruction;
        }
        errors[err->value] = err;
    }
    free(errors);
    return ir_build_const_type(irb, parent_scope, node, err_set_type);
}

static IrInstruction *ir_gen_fn_proto(IrBuilder *irb, Scope *parent_scope, AstNode *node) {
    assert(node->type == NodeTypeFnProto);

    size_t param_count = node->data.fn_proto.params.length;
    IrInstruction **param_types = allocate<IrInstruction*>(param_count);

    bool is_var_args = false;
    for (size_t i = 0; i < param_count; i += 1) {
        AstNode *param_node = node->data.fn_proto.params.at(i);
        if (param_node->data.param_decl.is_var_args) {
            is_var_args = true;
            break;
        }
        if (param_node->data.param_decl.var_token == nullptr) {
            AstNode *type_node = param_node->data.param_decl.type;
            IrInstruction *type_value = ir_gen_node(irb, type_node, parent_scope);
            if (type_value == irb->codegen->invalid_instruction)
                return irb->codegen->invalid_instruction;
            param_types[i] = type_value;
        } else {
            param_types[i] = nullptr;
        }
    }

    IrInstruction *align_value = nullptr;
    if (node->data.fn_proto.align_expr != nullptr) {
        align_value = ir_gen_node(irb, node->data.fn_proto.align_expr, parent_scope);
        if (align_value == irb->codegen->invalid_instruction)
            return irb->codegen->invalid_instruction;
    }

    IrInstruction *return_type;
    if (node->data.fn_proto.return_var_token == nullptr) {
        if (node->data.fn_proto.return_type == nullptr) {
            return_type = ir_build_const_type(irb, parent_scope, node, irb->codegen->builtin_types.entry_void);
        } else {
            return_type = ir_gen_node(irb, node->data.fn_proto.return_type, parent_scope);
            if (return_type == irb->codegen->invalid_instruction)
                return irb->codegen->invalid_instruction;
        }
    } else {
        add_node_error(irb->codegen, node,
            buf_sprintf("TODO implement inferred return types https://github.com/ziglang/zig/issues/447"));
        return irb->codegen->invalid_instruction;
        //return_type = nullptr;
    }

    IrInstruction *async_allocator_type_value = nullptr;
    if (node->data.fn_proto.async_allocator_type != nullptr) {
        async_allocator_type_value = ir_gen_node(irb, node->data.fn_proto.async_allocator_type, parent_scope);
        if (async_allocator_type_value == irb->codegen->invalid_instruction)
            return irb->codegen->invalid_instruction;
    }

    return ir_build_fn_proto(irb, parent_scope, node, param_types, align_value, return_type,
            async_allocator_type_value, is_var_args);
}

static IrInstruction *ir_gen_cancel_target(IrBuilder *irb, Scope *scope, AstNode *node,
        IrInstruction *target_inst, bool cancel_non_suspended, bool cancel_awaited)
{
    IrBasicBlock *done_block = ir_create_basic_block(irb, scope, "CancelDone");
    IrBasicBlock *not_canceled_block = ir_create_basic_block(irb, scope, "NotCanceled");
    IrBasicBlock *pre_return_block = ir_create_basic_block(irb, scope, "PreReturn");
    IrBasicBlock *post_return_block = ir_create_basic_block(irb, scope, "PostReturn");
    IrBasicBlock *do_cancel_block = ir_create_basic_block(irb, scope, "DoCancel");

    IrInstruction *zero = ir_build_const_usize(irb, scope, node, 0);
    IrInstruction *usize_type_val = ir_build_const_type(irb, scope, node, irb->codegen->builtin_types.entry_usize);
    IrInstruction *is_comptime = ir_build_const_bool(irb, scope, node, false);
    IrInstruction *is_canceled_mask = ir_build_const_usize(irb, scope, node, 0x1); // 0b001
    IrInstruction *promise_T_type_val = ir_build_const_type(irb, scope, node,
            get_promise_type(irb->codegen, irb->codegen->builtin_types.entry_void));
    IrInstruction *inverted_ptr_mask = ir_build_const_usize(irb, scope, node, 0x7); // 0b111
    IrInstruction *ptr_mask = ir_build_un_op(irb, scope, node, IrUnOpBinNot, inverted_ptr_mask); // 0b111...000
    IrInstruction *await_mask = ir_build_const_usize(irb, scope, node, 0x4); // 0b100
    IrInstruction *is_suspended_mask = ir_build_const_usize(irb, scope, node, 0x2); // 0b010

    // TODO relies on Zig not re-ordering fields
    IrInstruction *casted_target_inst = ir_build_ptr_cast_src(irb, scope, node, promise_T_type_val, target_inst,
            false);
    IrInstruction *coro_promise_ptr = ir_build_coro_promise(irb, scope, node, casted_target_inst);
    Buf *atomic_state_field_name = buf_create_from_str(ATOMIC_STATE_FIELD_NAME);
    IrInstruction *atomic_state_ptr = ir_build_field_ptr(irb, scope, node, coro_promise_ptr,
            atomic_state_field_name);

    // set the is_canceled bit
    IrInstruction *prev_atomic_value = ir_build_atomic_rmw(irb, scope, node,
            usize_type_val, atomic_state_ptr, nullptr, is_canceled_mask, nullptr,
            AtomicRmwOp_or, AtomicOrderSeqCst);

    IrInstruction *is_canceled_value = ir_build_bin_op(irb, scope, node, IrBinOpBinAnd, prev_atomic_value, is_canceled_mask, false);
    IrInstruction *is_canceled_bool = ir_build_bin_op(irb, scope, node, IrBinOpCmpNotEq, is_canceled_value, zero, false);
    ir_build_cond_br(irb, scope, node, is_canceled_bool, done_block, not_canceled_block, is_comptime);

    ir_set_cursor_at_end_and_append_block(irb, not_canceled_block);
    IrInstruction *awaiter_addr = ir_build_bin_op(irb, scope, node, IrBinOpBinAnd, prev_atomic_value, ptr_mask, false);
    IrInstruction *is_returned_bool = ir_build_bin_op(irb, scope, node, IrBinOpCmpEq, awaiter_addr, ptr_mask, false);
    ir_build_cond_br(irb, scope, node, is_returned_bool, post_return_block, pre_return_block, is_comptime);

    ir_set_cursor_at_end_and_append_block(irb, post_return_block);
    if (cancel_awaited) {
        ir_build_br(irb, scope, node, do_cancel_block, is_comptime);
    } else {
        IrInstruction *is_awaited_value = ir_build_bin_op(irb, scope, node, IrBinOpBinAnd, prev_atomic_value, await_mask, false);
        IrInstruction *is_awaited_bool = ir_build_bin_op(irb, scope, node, IrBinOpCmpNotEq, is_awaited_value, zero, false);
        ir_build_cond_br(irb, scope, node, is_awaited_bool, done_block, do_cancel_block, is_comptime);
    }

    ir_set_cursor_at_end_and_append_block(irb, pre_return_block);
    if (cancel_awaited) {
        if (cancel_non_suspended) {
            ir_build_br(irb, scope, node, do_cancel_block, is_comptime);
        } else {
            IrInstruction *is_suspended_value = ir_build_bin_op(irb, scope, node, IrBinOpBinAnd, prev_atomic_value, is_suspended_mask, false);
            IrInstruction *is_suspended_bool = ir_build_bin_op(irb, scope, node, IrBinOpCmpNotEq, is_suspended_value, zero, false);
            ir_build_cond_br(irb, scope, node, is_suspended_bool, do_cancel_block, done_block, is_comptime);
        }
    } else {
        ir_build_br(irb, scope, node, done_block, is_comptime);
    }

    ir_set_cursor_at_end_and_append_block(irb, do_cancel_block);
    ir_build_cancel(irb, scope, node, target_inst);
    ir_build_br(irb, scope, node, done_block, is_comptime);

    ir_set_cursor_at_end_and_append_block(irb, done_block);
    return ir_build_const_void(irb, scope, node);
}

static IrInstruction *ir_gen_cancel(IrBuilder *irb, Scope *scope, AstNode *node) {
    assert(node->type == NodeTypeCancel);

    IrInstruction *target_inst = ir_gen_node(irb, node->data.cancel_expr.expr, scope);
    if (target_inst == irb->codegen->invalid_instruction)
        return irb->codegen->invalid_instruction;

    return ir_gen_cancel_target(irb, scope, node, target_inst, false, true);
}

static IrInstruction *ir_gen_resume_target(IrBuilder *irb, Scope *scope, AstNode *node,
        IrInstruction *target_inst)
{
    IrBasicBlock *done_block = ir_create_basic_block(irb, scope, "ResumeDone");
    IrBasicBlock *not_canceled_block = ir_create_basic_block(irb, scope, "NotCanceled");
    IrBasicBlock *suspended_block = ir_create_basic_block(irb, scope, "IsSuspended");
    IrBasicBlock *not_suspended_block = ir_create_basic_block(irb, scope, "IsNotSuspended");

    IrInstruction *zero = ir_build_const_usize(irb, scope, node, 0);
    IrInstruction *is_canceled_mask = ir_build_const_usize(irb, scope, node, 0x1); // 0b001
    IrInstruction *is_suspended_mask = ir_build_const_usize(irb, scope, node, 0x2); // 0b010
    IrInstruction *and_mask = ir_build_un_op(irb, scope, node, IrUnOpBinNot, is_suspended_mask);
    IrInstruction *is_comptime = ir_build_const_bool(irb, scope, node, false);
    IrInstruction *usize_type_val = ir_build_const_type(irb, scope, node, irb->codegen->builtin_types.entry_usize);
    IrInstruction *promise_T_type_val = ir_build_const_type(irb, scope, node,
            get_promise_type(irb->codegen, irb->codegen->builtin_types.entry_void));

    // TODO relies on Zig not re-ordering fields
    IrInstruction *casted_target_inst = ir_build_ptr_cast_src(irb, scope, node, promise_T_type_val, target_inst,
            false);
    IrInstruction *coro_promise_ptr = ir_build_coro_promise(irb, scope, node, casted_target_inst);
    Buf *atomic_state_field_name = buf_create_from_str(ATOMIC_STATE_FIELD_NAME);
    IrInstruction *atomic_state_ptr = ir_build_field_ptr(irb, scope, node, coro_promise_ptr,
            atomic_state_field_name);

    // clear the is_suspended bit
    IrInstruction *prev_atomic_value = ir_build_atomic_rmw(irb, scope, node,
            usize_type_val, atomic_state_ptr, nullptr, and_mask, nullptr,
            AtomicRmwOp_and, AtomicOrderSeqCst);

    IrInstruction *is_canceled_value = ir_build_bin_op(irb, scope, node, IrBinOpBinAnd, prev_atomic_value, is_canceled_mask, false);
    IrInstruction *is_canceled_bool = ir_build_bin_op(irb, scope, node, IrBinOpCmpNotEq, is_canceled_value, zero, false);
    ir_build_cond_br(irb, scope, node, is_canceled_bool, done_block, not_canceled_block, is_comptime);

    ir_set_cursor_at_end_and_append_block(irb, not_canceled_block);
    IrInstruction *is_suspended_value = ir_build_bin_op(irb, scope, node, IrBinOpBinAnd, prev_atomic_value, is_suspended_mask, false);
    IrInstruction *is_suspended_bool = ir_build_bin_op(irb, scope, node, IrBinOpCmpNotEq, is_suspended_value, zero, false);
    ir_build_cond_br(irb, scope, node, is_suspended_bool, suspended_block, not_suspended_block, is_comptime);

    ir_set_cursor_at_end_and_append_block(irb, not_suspended_block);
    ir_build_unreachable(irb, scope, node);

    ir_set_cursor_at_end_and_append_block(irb, suspended_block);
    ir_build_coro_resume(irb, scope, node, target_inst);
    ir_build_br(irb, scope, node, done_block, is_comptime);

    ir_set_cursor_at_end_and_append_block(irb, done_block);
    return ir_build_const_void(irb, scope, node);
}

static IrInstruction *ir_gen_resume(IrBuilder *irb, Scope *scope, AstNode *node) {
    assert(node->type == NodeTypeResume);

    IrInstruction *target_inst = ir_gen_node(irb, node->data.resume_expr.expr, scope);
    if (target_inst == irb->codegen->invalid_instruction)
        return irb->codegen->invalid_instruction;

    return ir_gen_resume_target(irb, scope, node, target_inst);
}

static IrInstruction *ir_gen_await_expr(IrBuilder *irb, Scope *scope, AstNode *node) {
    assert(node->type == NodeTypeAwaitExpr);

    IrInstruction *target_inst = ir_gen_node(irb, node->data.await_expr.expr, scope);
    if (target_inst == irb->codegen->invalid_instruction)
        return irb->codegen->invalid_instruction;

    ZigFn *fn_entry = exec_fn_entry(irb->exec);
    if (!fn_entry) {
        add_node_error(irb->codegen, node, buf_sprintf("await outside function definition"));
        return irb->codegen->invalid_instruction;
    }
    if (fn_entry->type_entry->data.fn.fn_type_id.cc != CallingConventionAsync) {
        add_node_error(irb->codegen, node, buf_sprintf("await in non-async function"));
        return irb->codegen->invalid_instruction;
    }

    ScopeDeferExpr *scope_defer_expr = get_scope_defer_expr(scope);
    if (scope_defer_expr) {
        if (!scope_defer_expr->reported_err) {
            add_node_error(irb->codegen, node, buf_sprintf("cannot await inside defer expression"));
            scope_defer_expr->reported_err = true;
        }
        return irb->codegen->invalid_instruction;
    }

    Scope *outer_scope = irb->exec->begin_scope;

    IrInstruction *coro_promise_ptr = ir_build_coro_promise(irb, scope, node, target_inst);
    Buf *result_ptr_field_name = buf_create_from_str(RESULT_PTR_FIELD_NAME);
    IrInstruction *result_ptr_field_ptr = ir_build_field_ptr(irb, scope, node, coro_promise_ptr, result_ptr_field_name);

    if (irb->codegen->have_err_ret_tracing) {
        IrInstruction *err_ret_trace_ptr = ir_build_error_return_trace(irb, scope, node, IrInstructionErrorReturnTrace::NonNull);
        Buf *err_ret_trace_ptr_field_name = buf_create_from_str(ERR_RET_TRACE_PTR_FIELD_NAME);
        IrInstruction *err_ret_trace_ptr_field_ptr = ir_build_field_ptr(irb, scope, node, coro_promise_ptr, err_ret_trace_ptr_field_name);
        ir_build_store_ptr(irb, scope, node, err_ret_trace_ptr_field_ptr, err_ret_trace_ptr);
    }

    IrBasicBlock *already_awaited_block = ir_create_basic_block(irb, scope, "AlreadyAwaited");
    IrBasicBlock *not_awaited_block = ir_create_basic_block(irb, scope, "NotAwaited");
    IrBasicBlock *not_canceled_block = ir_create_basic_block(irb, scope, "NotCanceled");
    IrBasicBlock *yes_suspend_block = ir_create_basic_block(irb, scope, "YesSuspend");
    IrBasicBlock *no_suspend_block = ir_create_basic_block(irb, scope, "NoSuspend");
    IrBasicBlock *merge_block = ir_create_basic_block(irb, scope, "MergeSuspend");
    IrBasicBlock *cleanup_block = ir_create_basic_block(irb, scope, "SuspendCleanup");
    IrBasicBlock *resume_block = ir_create_basic_block(irb, scope, "SuspendResume");
    IrBasicBlock *cancel_target_block = ir_create_basic_block(irb, scope, "CancelTarget");
    IrBasicBlock *do_cancel_block = ir_create_basic_block(irb, scope, "DoCancel");
    IrBasicBlock *do_defers_block = ir_create_basic_block(irb, scope, "DoDefers");
    IrBasicBlock *destroy_block = ir_create_basic_block(irb, scope, "DestroyBlock");
    IrBasicBlock *my_suspended_block = ir_create_basic_block(irb, scope, "AlreadySuspended");
    IrBasicBlock *my_not_suspended_block = ir_create_basic_block(irb, scope, "NotAlreadySuspended");
    IrBasicBlock *do_suspend_block = ir_create_basic_block(irb, scope, "DoSuspend");

    Buf *atomic_state_field_name = buf_create_from_str(ATOMIC_STATE_FIELD_NAME);
    IrInstruction *atomic_state_ptr = ir_build_field_ptr(irb, scope, node, coro_promise_ptr,
            atomic_state_field_name);

    IrInstruction *promise_type_val = ir_build_const_type(irb, scope, node, irb->codegen->builtin_types.entry_promise);
    IrInstruction *const_bool_false = ir_build_const_bool(irb, scope, node, false);
    IrInstruction *undefined_value = ir_build_const_undefined(irb, scope, node);
    IrInstruction *usize_type_val = ir_build_const_type(irb, scope, node, irb->codegen->builtin_types.entry_usize);
    IrInstruction *zero = ir_build_const_usize(irb, scope, node, 0);
    IrInstruction *inverted_ptr_mask = ir_build_const_usize(irb, scope, node, 0x7); // 0b111
    IrInstruction *ptr_mask = ir_build_un_op(irb, scope, node, IrUnOpBinNot, inverted_ptr_mask); // 0b111...000
    IrInstruction *await_mask = ir_build_const_usize(irb, scope, node, 0x4); // 0b100
    IrInstruction *is_canceled_mask = ir_build_const_usize(irb, scope, node, 0x1); // 0b001
    IrInstruction *is_suspended_mask = ir_build_const_usize(irb, scope, node, 0x2); // 0b010

    ZigVar *result_var = ir_create_var(irb, node, scope, nullptr,
            false, false, true, const_bool_false);
    IrInstruction *target_promise_type = ir_build_typeof(irb, scope, node, target_inst);
    IrInstruction *promise_result_type = ir_build_promise_result_type(irb, scope, node, target_promise_type);
    ir_build_await_bookkeeping(irb, scope, node, promise_result_type);
    ir_build_var_decl_src(irb, scope, node, result_var, promise_result_type, nullptr, undefined_value);
    IrInstruction *my_result_var_ptr = ir_build_var_ptr(irb, scope, node, result_var);
    ir_build_store_ptr(irb, scope, node, result_ptr_field_ptr, my_result_var_ptr);
    IrInstruction *save_token = ir_build_coro_save(irb, scope, node, irb->exec->coro_handle);

    IrInstruction *coro_handle_addr = ir_build_ptr_to_int(irb, scope, node, irb->exec->coro_handle);
    IrInstruction *mask_bits = ir_build_bin_op(irb, scope, node, IrBinOpBinOr, coro_handle_addr, await_mask, false);
    IrInstruction *prev_atomic_value = ir_build_atomic_rmw(irb, scope, node,
            usize_type_val, atomic_state_ptr, nullptr, mask_bits, nullptr,
            AtomicRmwOp_or, AtomicOrderSeqCst);

    IrInstruction *is_awaited_value = ir_build_bin_op(irb, scope, node, IrBinOpBinAnd, prev_atomic_value, await_mask, false);
    IrInstruction *is_awaited_bool = ir_build_bin_op(irb, scope, node, IrBinOpCmpNotEq, is_awaited_value, zero, false);
    ir_build_cond_br(irb, scope, node, is_awaited_bool, already_awaited_block, not_awaited_block, const_bool_false);

    ir_set_cursor_at_end_and_append_block(irb, already_awaited_block);
    ir_build_unreachable(irb, scope, node);

    ir_set_cursor_at_end_and_append_block(irb, not_awaited_block);
    IrInstruction *await_handle_addr = ir_build_bin_op(irb, scope, node, IrBinOpBinAnd, prev_atomic_value, ptr_mask, false);
    IrInstruction *is_non_null = ir_build_bin_op(irb, scope, node, IrBinOpCmpNotEq, await_handle_addr, zero, false);
    IrInstruction *is_canceled_value = ir_build_bin_op(irb, scope, node, IrBinOpBinAnd, prev_atomic_value, is_canceled_mask, false);
    IrInstruction *is_canceled_bool = ir_build_bin_op(irb, scope, node, IrBinOpCmpNotEq, is_canceled_value, zero, false);
    ir_build_cond_br(irb, scope, node, is_canceled_bool, cancel_target_block, not_canceled_block, const_bool_false);

    ir_set_cursor_at_end_and_append_block(irb, not_canceled_block);
    ir_build_cond_br(irb, scope, node, is_non_null, no_suspend_block, yes_suspend_block, const_bool_false);

    ir_set_cursor_at_end_and_append_block(irb, cancel_target_block);
    ir_build_cancel(irb, scope, node, target_inst);
    ir_mark_gen(ir_build_br(irb, scope, node, cleanup_block, const_bool_false));

    ir_set_cursor_at_end_and_append_block(irb, no_suspend_block);
    if (irb->codegen->have_err_ret_tracing) {
        Buf *err_ret_trace_field_name = buf_create_from_str(ERR_RET_TRACE_FIELD_NAME);
        IrInstruction *src_err_ret_trace_ptr = ir_build_field_ptr(irb, scope, node, coro_promise_ptr, err_ret_trace_field_name);
        IrInstruction *dest_err_ret_trace_ptr = ir_build_error_return_trace(irb, scope, node, IrInstructionErrorReturnTrace::NonNull);
        ir_build_merge_err_ret_traces(irb, scope, node, coro_promise_ptr, src_err_ret_trace_ptr, dest_err_ret_trace_ptr);
    }
    Buf *result_field_name = buf_create_from_str(RESULT_FIELD_NAME);
    IrInstruction *promise_result_ptr = ir_build_field_ptr(irb, scope, node, coro_promise_ptr, result_field_name);
    // If the type of the result handle_is_ptr then this does not actually perform a load. But we need it to,
    // because we're about to destroy the memory. So we store it into our result variable.
    IrInstruction *no_suspend_result = ir_build_load_ptr(irb, scope, node, promise_result_ptr);
    ir_build_store_ptr(irb, scope, node, my_result_var_ptr, no_suspend_result);
    ir_build_cancel(irb, scope, node, target_inst);
    ir_build_br(irb, scope, node, merge_block, const_bool_false);


    ir_set_cursor_at_end_and_append_block(irb, yes_suspend_block);
    IrInstruction *my_prev_atomic_value = ir_build_atomic_rmw(irb, scope, node,
            usize_type_val, irb->exec->atomic_state_field_ptr, nullptr, is_suspended_mask, nullptr,
            AtomicRmwOp_or, AtomicOrderSeqCst);
    IrInstruction *my_is_suspended_value = ir_build_bin_op(irb, scope, node, IrBinOpBinAnd, my_prev_atomic_value, is_suspended_mask, false);
    IrInstruction *my_is_suspended_bool = ir_build_bin_op(irb, scope, node, IrBinOpCmpNotEq, my_is_suspended_value, zero, false);
    ir_build_cond_br(irb, scope, node, my_is_suspended_bool, my_suspended_block, my_not_suspended_block, const_bool_false);

    ir_set_cursor_at_end_and_append_block(irb, my_suspended_block);
    ir_build_unreachable(irb, scope, node);

    ir_set_cursor_at_end_and_append_block(irb, my_not_suspended_block);
    IrInstruction *my_is_canceled_value = ir_build_bin_op(irb, scope, node, IrBinOpBinAnd, my_prev_atomic_value, is_canceled_mask, false);
    IrInstruction *my_is_canceled_bool = ir_build_bin_op(irb, scope, node, IrBinOpCmpNotEq, my_is_canceled_value, zero, false);
    ir_build_cond_br(irb, scope, node, my_is_canceled_bool, cleanup_block, do_suspend_block, const_bool_false);

    ir_set_cursor_at_end_and_append_block(irb, do_suspend_block);
    IrInstruction *suspend_code = ir_build_coro_suspend(irb, scope, node, save_token, const_bool_false);

    IrInstructionSwitchBrCase *cases = allocate<IrInstructionSwitchBrCase>(2);
    cases[0].value = ir_build_const_u8(irb, scope, node, 0);
    cases[0].block = resume_block;
    cases[1].value = ir_build_const_u8(irb, scope, node, 1);
    cases[1].block = destroy_block;
    ir_build_switch_br(irb, scope, node, suspend_code, irb->exec->coro_suspend_block,
            2, cases, const_bool_false, nullptr);

    ir_set_cursor_at_end_and_append_block(irb, destroy_block);
    ir_gen_cancel_target(irb, scope, node, target_inst, false, true);
    ir_mark_gen(ir_build_br(irb, scope, node, cleanup_block, const_bool_false));

    ir_set_cursor_at_end_and_append_block(irb, cleanup_block);
    IrInstruction *my_mask_bits = ir_build_bin_op(irb, scope, node, IrBinOpBinOr, ptr_mask, is_canceled_mask, false);
    IrInstruction *b_my_prev_atomic_value = ir_build_atomic_rmw(irb, scope, node,
            usize_type_val, irb->exec->atomic_state_field_ptr, nullptr, my_mask_bits, nullptr,
            AtomicRmwOp_or, AtomicOrderSeqCst);
    IrInstruction *my_await_handle_addr = ir_build_bin_op(irb, scope, node, IrBinOpBinAnd, b_my_prev_atomic_value, ptr_mask, false);
    IrInstruction *dont_have_my_await_handle = ir_build_bin_op(irb, scope, node, IrBinOpCmpEq, my_await_handle_addr, zero, false);
    IrInstruction *dont_destroy_ourselves = ir_build_bin_op(irb, scope, node, IrBinOpBoolAnd, dont_have_my_await_handle, is_canceled_bool, false);
    ir_build_cond_br(irb, scope, node, dont_have_my_await_handle, do_defers_block, do_cancel_block, const_bool_false);

    ir_set_cursor_at_end_and_append_block(irb, do_cancel_block);
    IrInstruction *my_await_handle = ir_build_int_to_ptr(irb, scope, node, promise_type_val, my_await_handle_addr);
    ir_gen_cancel_target(irb, scope, node, my_await_handle, true, false);
    ir_mark_gen(ir_build_br(irb, scope, node, do_defers_block, const_bool_false));

    ir_set_cursor_at_end_and_append_block(irb, do_defers_block);
    ir_gen_defers_for_block(irb, scope, outer_scope, true);
    ir_mark_gen(ir_build_cond_br(irb, scope, node, dont_destroy_ourselves, irb->exec->coro_early_final, irb->exec->coro_final_cleanup_block, const_bool_false));

    ir_set_cursor_at_end_and_append_block(irb, resume_block);
    ir_build_br(irb, scope, node, merge_block, const_bool_false);

    ir_set_cursor_at_end_and_append_block(irb, merge_block);
    return ir_build_load_ptr(irb, scope, node, my_result_var_ptr);
}

static IrInstruction *ir_gen_suspend(IrBuilder *irb, Scope *parent_scope, AstNode *node) {
    assert(node->type == NodeTypeSuspend);

    ZigFn *fn_entry = exec_fn_entry(irb->exec);
    if (!fn_entry) {
        add_node_error(irb->codegen, node, buf_sprintf("suspend outside function definition"));
        return irb->codegen->invalid_instruction;
    }
    if (fn_entry->type_entry->data.fn.fn_type_id.cc != CallingConventionAsync) {
        add_node_error(irb->codegen, node, buf_sprintf("suspend in non-async function"));
        return irb->codegen->invalid_instruction;
    }

    ScopeDeferExpr *scope_defer_expr = get_scope_defer_expr(parent_scope);
    if (scope_defer_expr) {
        if (!scope_defer_expr->reported_err) {
            ErrorMsg *msg = add_node_error(irb->codegen, node, buf_sprintf("cannot suspend inside defer expression"));
            add_error_note(irb->codegen, msg, scope_defer_expr->base.source_node, buf_sprintf("defer here"));
            scope_defer_expr->reported_err = true;
        }
        return irb->codegen->invalid_instruction;
    }
    ScopeSuspend *existing_suspend_scope = get_scope_suspend(parent_scope);
    if (existing_suspend_scope) {
        if (!existing_suspend_scope->reported_err) {
            ErrorMsg *msg = add_node_error(irb->codegen, node, buf_sprintf("cannot suspend inside suspend block"));
            add_error_note(irb->codegen, msg, existing_suspend_scope->base.source_node, buf_sprintf("other suspend block here"));
            existing_suspend_scope->reported_err = true;
        }
        return irb->codegen->invalid_instruction;
    }

    Scope *outer_scope = irb->exec->begin_scope;

    IrBasicBlock *cleanup_block = ir_create_basic_block(irb, parent_scope, "SuspendCleanup");
    IrBasicBlock *resume_block = ir_create_basic_block(irb, parent_scope, "SuspendResume");
    IrBasicBlock *suspended_block = ir_create_basic_block(irb, parent_scope, "AlreadySuspended");
    IrBasicBlock *canceled_block = ir_create_basic_block(irb, parent_scope, "IsCanceled");
    IrBasicBlock *not_canceled_block = ir_create_basic_block(irb, parent_scope, "NotCanceled");
    IrBasicBlock *not_suspended_block = ir_create_basic_block(irb, parent_scope, "NotAlreadySuspended");
    IrBasicBlock *cancel_awaiter_block = ir_create_basic_block(irb, parent_scope, "CancelAwaiter");

    IrInstruction *promise_type_val = ir_build_const_type(irb, parent_scope, node, irb->codegen->builtin_types.entry_promise);
    IrInstruction *const_bool_true = ir_build_const_bool(irb, parent_scope, node, true);
    IrInstruction *const_bool_false = ir_build_const_bool(irb, parent_scope, node, false);
    IrInstruction *usize_type_val = ir_build_const_type(irb, parent_scope, node, irb->codegen->builtin_types.entry_usize);
    IrInstruction *is_canceled_mask = ir_build_const_usize(irb, parent_scope, node, 0x1); // 0b001
    IrInstruction *is_suspended_mask = ir_build_const_usize(irb, parent_scope, node, 0x2); // 0b010
    IrInstruction *zero = ir_build_const_usize(irb, parent_scope, node, 0);
    IrInstruction *inverted_ptr_mask = ir_build_const_usize(irb, parent_scope, node, 0x7); // 0b111
    IrInstruction *ptr_mask = ir_build_un_op(irb, parent_scope, node, IrUnOpBinNot, inverted_ptr_mask); // 0b111...000

    IrInstruction *prev_atomic_value = ir_build_atomic_rmw(irb, parent_scope, node,
            usize_type_val, irb->exec->atomic_state_field_ptr, nullptr, is_suspended_mask, nullptr,
            AtomicRmwOp_or, AtomicOrderSeqCst);

    IrInstruction *is_canceled_value = ir_build_bin_op(irb, parent_scope, node, IrBinOpBinAnd, prev_atomic_value, is_canceled_mask, false);
    IrInstruction *is_canceled_bool = ir_build_bin_op(irb, parent_scope, node, IrBinOpCmpNotEq, is_canceled_value, zero, false);
    ir_build_cond_br(irb, parent_scope, node, is_canceled_bool, canceled_block, not_canceled_block, const_bool_false);

    ir_set_cursor_at_end_and_append_block(irb, canceled_block);
    IrInstruction *await_handle_addr = ir_build_bin_op(irb, parent_scope, node, IrBinOpBinAnd, prev_atomic_value, ptr_mask, false);
    IrInstruction *have_await_handle = ir_build_bin_op(irb, parent_scope, node, IrBinOpCmpNotEq, await_handle_addr, zero, false);
    IrBasicBlock *post_canceled_block = irb->current_basic_block;
    ir_build_cond_br(irb, parent_scope, node, have_await_handle, cancel_awaiter_block, cleanup_block, const_bool_false);

    ir_set_cursor_at_end_and_append_block(irb, cancel_awaiter_block);
    IrInstruction *await_handle = ir_build_int_to_ptr(irb, parent_scope, node, promise_type_val, await_handle_addr);
    ir_gen_cancel_target(irb, parent_scope, node, await_handle, true, false);
    IrBasicBlock *post_cancel_awaiter_block = irb->current_basic_block;
    ir_build_br(irb, parent_scope, node, cleanup_block, const_bool_false);

    ir_set_cursor_at_end_and_append_block(irb, not_canceled_block);
    IrInstruction *is_suspended_value = ir_build_bin_op(irb, parent_scope, node, IrBinOpBinAnd, prev_atomic_value, is_suspended_mask, false);
    IrInstruction *is_suspended_bool = ir_build_bin_op(irb, parent_scope, node, IrBinOpCmpNotEq, is_suspended_value, zero, false);
    ir_build_cond_br(irb, parent_scope, node, is_suspended_bool, suspended_block, not_suspended_block, const_bool_false);

    ir_set_cursor_at_end_and_append_block(irb, suspended_block);
    ir_build_unreachable(irb, parent_scope, node);

    ir_set_cursor_at_end_and_append_block(irb, not_suspended_block);
    IrInstruction *suspend_code;
    if (node->data.suspend.block == nullptr) {
        suspend_code = ir_build_coro_suspend(irb, parent_scope, node, nullptr, const_bool_false);
    } else {
        Scope *child_scope;
        ScopeSuspend *suspend_scope = create_suspend_scope(irb->codegen, node, parent_scope);
        suspend_scope->resume_block = resume_block;
        child_scope = &suspend_scope->base;
        IrInstruction *save_token = ir_build_coro_save(irb, child_scope, node, irb->exec->coro_handle);
        ir_gen_node(irb, node->data.suspend.block, child_scope);
        suspend_code = ir_mark_gen(ir_build_coro_suspend(irb, parent_scope, node, save_token, const_bool_false));
    }

    IrInstructionSwitchBrCase *cases = allocate<IrInstructionSwitchBrCase>(2);
    cases[0].value = ir_mark_gen(ir_build_const_u8(irb, parent_scope, node, 0));
    cases[0].block = resume_block;
    cases[1].value = ir_mark_gen(ir_build_const_u8(irb, parent_scope, node, 1));
    cases[1].block = canceled_block;
    ir_mark_gen(ir_build_switch_br(irb, parent_scope, node, suspend_code, irb->exec->coro_suspend_block,
            2, cases, const_bool_false, nullptr));

    ir_set_cursor_at_end_and_append_block(irb, cleanup_block);
    IrBasicBlock **incoming_blocks = allocate<IrBasicBlock *>(2);
    IrInstruction **incoming_values = allocate<IrInstruction *>(2);
    incoming_blocks[0] = post_canceled_block;
    incoming_values[0] = const_bool_true;
    incoming_blocks[1] = post_cancel_awaiter_block;
    incoming_values[1] = const_bool_false;
    IrInstruction *destroy_ourselves = ir_build_phi(irb, parent_scope, node, 2, incoming_blocks, incoming_values);
    ir_gen_defers_for_block(irb, parent_scope, outer_scope, true);
    ir_mark_gen(ir_build_cond_br(irb, parent_scope, node, destroy_ourselves, irb->exec->coro_final_cleanup_block, irb->exec->coro_early_final, const_bool_false));

    ir_set_cursor_at_end_and_append_block(irb, resume_block);
    return ir_mark_gen(ir_build_const_void(irb, parent_scope, node));
}

static IrInstruction *ir_gen_node_raw(IrBuilder *irb, AstNode *node, Scope *scope,
        LVal lval)
{
    assert(scope);
    switch (node->type) {
        case NodeTypeStructValueField:
        case NodeTypeParamDecl:
        case NodeTypeUse:
        case NodeTypeSwitchProng:
        case NodeTypeSwitchRange:
        case NodeTypeStructField:
        case NodeTypeFnDef:
        case NodeTypeTestDecl:
            zig_unreachable();
        case NodeTypeBlock:
            return ir_lval_wrap(irb, scope, ir_gen_block(irb, scope, node), lval);
        case NodeTypeGroupedExpr:
            return ir_gen_node_raw(irb, node->data.grouped_expr, scope, lval);
        case NodeTypeBinOpExpr:
            return ir_lval_wrap(irb, scope, ir_gen_bin_op(irb, scope, node), lval);
        case NodeTypeIntLiteral:
            return ir_lval_wrap(irb, scope, ir_gen_int_lit(irb, scope, node), lval);
        case NodeTypeFloatLiteral:
            return ir_lval_wrap(irb, scope, ir_gen_float_lit(irb, scope, node), lval);
        case NodeTypeCharLiteral:
            return ir_lval_wrap(irb, scope, ir_gen_char_lit(irb, scope, node), lval);
        case NodeTypeSymbol:
            return ir_gen_symbol(irb, scope, node, lval);
        case NodeTypeFnCallExpr:
            return ir_gen_fn_call(irb, scope, node, lval);
        case NodeTypeIfBoolExpr:
            return ir_lval_wrap(irb, scope, ir_gen_if_bool_expr(irb, scope, node), lval);
        case NodeTypePrefixOpExpr:
            return ir_gen_prefix_op_expr(irb, scope, node, lval);
        case NodeTypeContainerInitExpr:
            return ir_lval_wrap(irb, scope, ir_gen_container_init_expr(irb, scope, node), lval);
        case NodeTypeVariableDeclaration:
            return ir_lval_wrap(irb, scope, ir_gen_var_decl(irb, scope, node), lval);
        case NodeTypeWhileExpr:
            return ir_lval_wrap(irb, scope, ir_gen_while_expr(irb, scope, node), lval);
        case NodeTypeForExpr:
            return ir_lval_wrap(irb, scope, ir_gen_for_expr(irb, scope, node), lval);
        case NodeTypeArrayAccessExpr:
            return ir_gen_array_access(irb, scope, node, lval);
        case NodeTypeReturnExpr:
            return ir_gen_return(irb, scope, node, lval);
        case NodeTypeFieldAccessExpr:
            {
                IrInstruction *ptr_instruction = ir_gen_field_access(irb, scope, node);
                if (ptr_instruction == irb->codegen->invalid_instruction)
                    return ptr_instruction;
                if (lval == LValPtr)
                    return ptr_instruction;

                return ir_build_load_ptr(irb, scope, node, ptr_instruction);
            }
        case NodeTypePtrDeref: {
            AstNode *expr_node = node->data.ptr_deref_expr.target;
            IrInstruction *value = ir_gen_node_extra(irb, expr_node, scope, lval);
            if (value == irb->codegen->invalid_instruction)
                return value;

            // We essentially just converted any lvalue from &(x.*) to (&x).*;
            // this inhibits checking that x is a pointer later, so we directly
            // record whether the pointer check is needed
            return ir_build_un_op_lval(irb, scope, node, IrUnOpDereference, value, lval);
        }
        case NodeTypeUnwrapOptional: {
            AstNode *expr_node = node->data.unwrap_optional.expr;

            IrInstruction *maybe_ptr = ir_gen_node_extra(irb, expr_node, scope, LValPtr);
            if (maybe_ptr == irb->codegen->invalid_instruction)
                return irb->codegen->invalid_instruction;

            IrInstruction *unwrapped_ptr = ir_build_optional_unwrap_ptr(irb, scope, node, maybe_ptr, true);
            if (lval == LValPtr)
                return unwrapped_ptr;

            return ir_build_load_ptr(irb, scope, node, unwrapped_ptr);
        }
        case NodeTypeBoolLiteral:
            return ir_lval_wrap(irb, scope, ir_gen_bool_literal(irb, scope, node), lval);
        case NodeTypeArrayType:
            return ir_lval_wrap(irb, scope, ir_gen_array_type(irb, scope, node), lval);
        case NodeTypePointerType:
            return ir_lval_wrap(irb, scope, ir_gen_pointer_type(irb, scope, node), lval);
        case NodeTypePromiseType:
            return ir_lval_wrap(irb, scope, ir_gen_promise_type(irb, scope, node), lval);
        case NodeTypeStringLiteral:
            return ir_lval_wrap(irb, scope, ir_gen_string_literal(irb, scope, node), lval);
        case NodeTypeUndefinedLiteral:
            return ir_lval_wrap(irb, scope, ir_gen_undefined_literal(irb, scope, node), lval);
        case NodeTypeAsmExpr:
            return ir_lval_wrap(irb, scope, ir_gen_asm_expr(irb, scope, node), lval);
        case NodeTypeNullLiteral:
            return ir_lval_wrap(irb, scope, ir_gen_null_literal(irb, scope, node), lval);
        case NodeTypeIfErrorExpr:
            return ir_lval_wrap(irb, scope, ir_gen_if_err_expr(irb, scope, node), lval);
        case NodeTypeIfOptional:
            return ir_lval_wrap(irb, scope, ir_gen_if_optional_expr(irb, scope, node), lval);
        case NodeTypeSwitchExpr:
            return ir_lval_wrap(irb, scope, ir_gen_switch_expr(irb, scope, node), lval);
        case NodeTypeCompTime:
            return ir_gen_comptime(irb, scope, node, lval);
        case NodeTypeErrorType:
            return ir_lval_wrap(irb, scope, ir_gen_error_type(irb, scope, node), lval);
        case NodeTypeBreak:
            return ir_lval_wrap(irb, scope, ir_gen_break(irb, scope, node), lval);
        case NodeTypeContinue:
            return ir_lval_wrap(irb, scope, ir_gen_continue(irb, scope, node), lval);
        case NodeTypeUnreachable:
            return ir_lval_wrap(irb, scope, ir_build_unreachable(irb, scope, node), lval);
        case NodeTypeDefer:
            return ir_lval_wrap(irb, scope, ir_gen_defer(irb, scope, node), lval);
        case NodeTypeSliceExpr:
            return ir_lval_wrap(irb, scope, ir_gen_slice(irb, scope, node), lval);
        case NodeTypeUnwrapErrorExpr:
            return ir_lval_wrap(irb, scope, ir_gen_catch(irb, scope, node), lval);
        case NodeTypeContainerDecl:
            return ir_lval_wrap(irb, scope, ir_gen_container_decl(irb, scope, node), lval);
        case NodeTypeFnProto:
            return ir_lval_wrap(irb, scope, ir_gen_fn_proto(irb, scope, node), lval);
        case NodeTypeErrorSetDecl:
            return ir_lval_wrap(irb, scope, ir_gen_err_set_decl(irb, scope, node), lval);
        case NodeTypeCancel:
            return ir_lval_wrap(irb, scope, ir_gen_cancel(irb, scope, node), lval);
        case NodeTypeResume:
            return ir_lval_wrap(irb, scope, ir_gen_resume(irb, scope, node), lval);
        case NodeTypeAwaitExpr:
            return ir_lval_wrap(irb, scope, ir_gen_await_expr(irb, scope, node), lval);
        case NodeTypeSuspend:
            return ir_lval_wrap(irb, scope, ir_gen_suspend(irb, scope, node), lval);
    }
    zig_unreachable();
}

static IrInstruction *ir_gen_node_extra(IrBuilder *irb, AstNode *node, Scope *scope, LVal lval) {
    IrInstruction *result = ir_gen_node_raw(irb, node, scope, lval);
    irb->exec->invalid = irb->exec->invalid || (result == irb->codegen->invalid_instruction);
    return result;
}

static IrInstruction *ir_gen_node(IrBuilder *irb, AstNode *node, Scope *scope) {
    return ir_gen_node_extra(irb, node, scope, LValNone);
}

static void invalidate_exec(IrExecutable *exec) {
    if (exec->invalid)
        return;

    exec->invalid = true;

    for (size_t i = 0; i < exec->tld_list.length; i += 1) {
        exec->tld_list.items[i]->resolution = TldResolutionInvalid;
    }

    if (exec->source_exec != nullptr)
        invalidate_exec(exec->source_exec);
}


bool ir_gen(CodeGen *codegen, AstNode *node, Scope *scope, IrExecutable *ir_executable) {
    assert(node->owner);

    IrBuilder ir_builder = {0};
    IrBuilder *irb = &ir_builder;

    irb->codegen = codegen;
    irb->exec = ir_executable;

    IrBasicBlock *entry_block = ir_create_basic_block(irb, scope, "Entry");
    ir_set_cursor_at_end_and_append_block(irb, entry_block);
    // Entry block gets a reference because we enter it to begin.
    ir_ref_bb(irb->current_basic_block);

    ZigFn *fn_entry = exec_fn_entry(irb->exec);

    bool is_async = fn_entry != nullptr && fn_entry->type_entry->data.fn.fn_type_id.cc == CallingConventionAsync;
    IrInstruction *coro_id;
    IrInstruction *u8_ptr_type;
    IrInstruction *const_bool_false;
    IrInstruction *coro_promise_ptr;
    IrInstruction *err_ret_trace_ptr;
    ZigType *return_type;
    Buf *result_ptr_field_name;
    ZigVar *coro_size_var;
    if (is_async) {
        // create the coro promise
        Scope *coro_scope = create_coro_prelude_scope(irb->codegen, node, scope);
        const_bool_false = ir_build_const_bool(irb, coro_scope, node, false);
        ZigVar *promise_var = ir_create_var(irb, node, coro_scope, nullptr, false, false, true, const_bool_false);

        return_type = fn_entry->type_entry->data.fn.fn_type_id.return_type;
        IrInstruction *undef = ir_build_const_undefined(irb, coro_scope, node);
        ZigType *coro_frame_type = get_promise_frame_type(irb->codegen, return_type);
        IrInstruction *coro_frame_type_value = ir_build_const_type(irb, coro_scope, node, coro_frame_type);
        // TODO mark this var decl as "no safety" e.g. disable initializing the undef value to 0xaa
        ir_build_var_decl_src(irb, coro_scope, node, promise_var, coro_frame_type_value, nullptr, undef);
        coro_promise_ptr = ir_build_var_ptr(irb, coro_scope, node, promise_var);

        ZigVar *await_handle_var = ir_create_var(irb, node, coro_scope, nullptr, false, false, true, const_bool_false);
        IrInstruction *null_value = ir_build_const_null(irb, coro_scope, node);
        IrInstruction *await_handle_type_val = ir_build_const_type(irb, coro_scope, node,
                get_optional_type(irb->codegen, irb->codegen->builtin_types.entry_promise));
        ir_build_var_decl_src(irb, coro_scope, node, await_handle_var, await_handle_type_val, nullptr, null_value);
        irb->exec->await_handle_var_ptr = ir_build_var_ptr(irb, coro_scope, node, await_handle_var);

        u8_ptr_type = ir_build_const_type(irb, coro_scope, node,
                get_pointer_to_type(irb->codegen, irb->codegen->builtin_types.entry_u8, false));
        IrInstruction *promise_as_u8_ptr = ir_build_ptr_cast_src(irb, coro_scope, node, u8_ptr_type,
                coro_promise_ptr, false);
        coro_id = ir_build_coro_id(irb, coro_scope, node, promise_as_u8_ptr);
        coro_size_var = ir_create_var(irb, node, coro_scope, nullptr, false, false, true, const_bool_false);
        IrInstruction *coro_size = ir_build_coro_size(irb, coro_scope, node);
        ir_build_var_decl_src(irb, coro_scope, node, coro_size_var, nullptr, nullptr, coro_size);
        IrInstruction *implicit_allocator_ptr = ir_build_get_implicit_allocator(irb, coro_scope, node,
                ImplicitAllocatorIdArg);
        irb->exec->coro_allocator_var = ir_create_var(irb, node, coro_scope, nullptr, true, true, true, const_bool_false);
        ir_build_var_decl_src(irb, coro_scope, node, irb->exec->coro_allocator_var, nullptr, nullptr, implicit_allocator_ptr);
        Buf *alloc_field_name = buf_create_from_str(ASYNC_ALLOC_FIELD_NAME);
        IrInstruction *alloc_fn_ptr = ir_build_field_ptr(irb, coro_scope, node, implicit_allocator_ptr, alloc_field_name);
        IrInstruction *alloc_fn = ir_build_load_ptr(irb, coro_scope, node, alloc_fn_ptr);
        IrInstruction *maybe_coro_mem_ptr = ir_build_coro_alloc_helper(irb, coro_scope, node, alloc_fn, coro_size);
        IrInstruction *alloc_result_is_ok = ir_build_test_nonnull(irb, coro_scope, node, maybe_coro_mem_ptr);
        IrBasicBlock *alloc_err_block = ir_create_basic_block(irb, coro_scope, "AllocError");
        IrBasicBlock *alloc_ok_block = ir_create_basic_block(irb, coro_scope, "AllocOk");
        ir_build_cond_br(irb, coro_scope, node, alloc_result_is_ok, alloc_ok_block, alloc_err_block, const_bool_false);

        ir_set_cursor_at_end_and_append_block(irb, alloc_err_block);
        // we can return undefined here, because the caller passes a pointer to the error struct field
        // in the error union result, and we populate it in case of allocation failure.
        ir_build_return(irb, coro_scope, node, undef);

        ir_set_cursor_at_end_and_append_block(irb, alloc_ok_block);
        IrInstruction *coro_mem_ptr = ir_build_ptr_cast_src(irb, coro_scope, node, u8_ptr_type, maybe_coro_mem_ptr,
                false);
        irb->exec->coro_handle = ir_build_coro_begin(irb, coro_scope, node, coro_id, coro_mem_ptr);

        Buf *atomic_state_field_name = buf_create_from_str(ATOMIC_STATE_FIELD_NAME);
        irb->exec->atomic_state_field_ptr = ir_build_field_ptr(irb, scope, node, coro_promise_ptr,
                atomic_state_field_name);
        IrInstruction *zero = ir_build_const_usize(irb, scope, node, 0);
        ir_build_store_ptr(irb, scope, node, irb->exec->atomic_state_field_ptr, zero);
        Buf *result_field_name = buf_create_from_str(RESULT_FIELD_NAME);
        irb->exec->coro_result_field_ptr = ir_build_field_ptr(irb, scope, node, coro_promise_ptr, result_field_name);
        result_ptr_field_name = buf_create_from_str(RESULT_PTR_FIELD_NAME);
        irb->exec->coro_result_ptr_field_ptr = ir_build_field_ptr(irb, scope, node, coro_promise_ptr, result_ptr_field_name);
        ir_build_store_ptr(irb, scope, node, irb->exec->coro_result_ptr_field_ptr, irb->exec->coro_result_field_ptr);
        if (irb->codegen->have_err_ret_tracing) {
            // initialize the error return trace
            Buf *return_addresses_field_name = buf_create_from_str(RETURN_ADDRESSES_FIELD_NAME);
            IrInstruction *return_addresses_ptr = ir_build_field_ptr(irb, scope, node, coro_promise_ptr, return_addresses_field_name);

            Buf *err_ret_trace_field_name = buf_create_from_str(ERR_RET_TRACE_FIELD_NAME);
            err_ret_trace_ptr = ir_build_field_ptr(irb, scope, node, coro_promise_ptr, err_ret_trace_field_name);
            ir_build_mark_err_ret_trace_ptr(irb, scope, node, err_ret_trace_ptr);

            // coordinate with builtin.zig
            Buf *index_name = buf_create_from_str("index");
            IrInstruction *index_ptr = ir_build_field_ptr(irb, scope, node, err_ret_trace_ptr, index_name);
            ir_build_store_ptr(irb, scope, node, index_ptr, zero);

            Buf *instruction_addresses_name = buf_create_from_str("instruction_addresses");
            IrInstruction *addrs_slice_ptr = ir_build_field_ptr(irb, scope, node, err_ret_trace_ptr, instruction_addresses_name);

            IrInstruction *slice_value = ir_build_slice(irb, scope, node, return_addresses_ptr, zero, nullptr, false);
            ir_build_store_ptr(irb, scope, node, addrs_slice_ptr, slice_value);
        }


        irb->exec->coro_early_final = ir_create_basic_block(irb, scope, "CoroEarlyFinal");
        irb->exec->coro_normal_final = ir_create_basic_block(irb, scope, "CoroNormalFinal");
        irb->exec->coro_suspend_block = ir_create_basic_block(irb, scope, "Suspend");
        irb->exec->coro_final_cleanup_block = ir_create_basic_block(irb, scope, "FinalCleanup");
    }

    IrInstruction *result = ir_gen_node_extra(irb, node, scope, LValNone);
    assert(result);
    if (irb->exec->invalid)
        return false;

    if (!instr_is_unreachable(result)) {
        // no need for save_err_ret_addr because this cannot return error
        ir_gen_async_return(irb, scope, result->source_node, result, true);
    }

    if (is_async) {
        IrBasicBlock *invalid_resume_block = ir_create_basic_block(irb, scope, "InvalidResume");
        IrBasicBlock *check_free_block = ir_create_basic_block(irb, scope, "CheckFree");

        ir_set_cursor_at_end_and_append_block(irb, irb->exec->coro_early_final);
        IrInstruction *const_bool_true = ir_build_const_bool(irb, scope, node, true);
        IrInstruction *suspend_code = ir_build_coro_suspend(irb, scope, node, nullptr, const_bool_true);
        IrInstructionSwitchBrCase *cases = allocate<IrInstructionSwitchBrCase>(2);
        cases[0].value = ir_build_const_u8(irb, scope, node, 0);
        cases[0].block = invalid_resume_block;
        cases[1].value = ir_build_const_u8(irb, scope, node, 1);
        cases[1].block = irb->exec->coro_final_cleanup_block;
        ir_build_switch_br(irb, scope, node, suspend_code, irb->exec->coro_suspend_block, 2, cases, const_bool_false, nullptr);

        ir_set_cursor_at_end_and_append_block(irb, irb->exec->coro_suspend_block);
        ir_build_coro_end(irb, scope, node);
        ir_build_return(irb, scope, node, irb->exec->coro_handle);

        ir_set_cursor_at_end_and_append_block(irb, invalid_resume_block);
        ir_build_unreachable(irb, scope, node);

        ir_set_cursor_at_end_and_append_block(irb, irb->exec->coro_normal_final);
        if (type_has_bits(return_type)) {
            IrInstruction *u8_ptr_type_unknown_len = ir_build_const_type(irb, scope, node,
                    get_pointer_to_type_extra(irb->codegen, irb->codegen->builtin_types.entry_u8,
                        false, false, PtrLenUnknown, 0, 0, 0));
            IrInstruction *result_ptr = ir_build_load_ptr(irb, scope, node, irb->exec->coro_result_ptr_field_ptr);
            IrInstruction *result_ptr_as_u8_ptr = ir_build_ptr_cast_src(irb, scope, node, u8_ptr_type_unknown_len,
                    result_ptr, false);
            IrInstruction *return_value_ptr_as_u8_ptr = ir_build_ptr_cast_src(irb, scope, node,
                    u8_ptr_type_unknown_len, irb->exec->coro_result_field_ptr, false);
            IrInstruction *return_type_inst = ir_build_const_type(irb, scope, node,
                    fn_entry->type_entry->data.fn.fn_type_id.return_type);
            IrInstruction *size_of_ret_val = ir_build_size_of(irb, scope, node, return_type_inst);
            ir_build_memcpy(irb, scope, node, result_ptr_as_u8_ptr, return_value_ptr_as_u8_ptr, size_of_ret_val);
        }
        if (irb->codegen->have_err_ret_tracing) {
            Buf *err_ret_trace_ptr_field_name = buf_create_from_str(ERR_RET_TRACE_PTR_FIELD_NAME);
            IrInstruction *err_ret_trace_ptr_field_ptr = ir_build_field_ptr(irb, scope, node, coro_promise_ptr, err_ret_trace_ptr_field_name);
            IrInstruction *dest_err_ret_trace_ptr = ir_build_load_ptr(irb, scope, node, err_ret_trace_ptr_field_ptr);
            ir_build_merge_err_ret_traces(irb, scope, node, coro_promise_ptr, err_ret_trace_ptr, dest_err_ret_trace_ptr);
        }
        // Before we destroy the coroutine frame, we need to load the target promise into
        // a register or local variable which does not get spilled into the frame,
        // otherwise llvm tries to access memory inside the destroyed frame.
        IrInstruction *unwrapped_await_handle_ptr = ir_build_optional_unwrap_ptr(irb, scope, node,
                irb->exec->await_handle_var_ptr, false);
        IrInstruction *await_handle_in_block = ir_build_load_ptr(irb, scope, node, unwrapped_await_handle_ptr);
        ir_build_br(irb, scope, node, check_free_block, const_bool_false);

        ir_set_cursor_at_end_and_append_block(irb, irb->exec->coro_final_cleanup_block);
        ir_build_br(irb, scope, node, check_free_block, const_bool_false);

        ir_set_cursor_at_end_and_append_block(irb, check_free_block);
        IrBasicBlock **incoming_blocks = allocate<IrBasicBlock *>(2);
        IrInstruction **incoming_values = allocate<IrInstruction *>(2);
        incoming_blocks[0] = irb->exec->coro_final_cleanup_block;
        incoming_values[0] = const_bool_false;
        incoming_blocks[1] = irb->exec->coro_normal_final;
        incoming_values[1] = const_bool_true;
        IrInstruction *resume_awaiter = ir_build_phi(irb, scope, node, 2, incoming_blocks, incoming_values);

        IrBasicBlock **merge_incoming_blocks = allocate<IrBasicBlock *>(2);
        IrInstruction **merge_incoming_values = allocate<IrInstruction *>(2);
        merge_incoming_blocks[0] = irb->exec->coro_final_cleanup_block;
        merge_incoming_values[0] = ir_build_const_undefined(irb, scope, node);
        merge_incoming_blocks[1] = irb->exec->coro_normal_final;
        merge_incoming_values[1] = await_handle_in_block;
        IrInstruction *awaiter_handle = ir_build_phi(irb, scope, node, 2, merge_incoming_blocks, merge_incoming_values);

        Buf *free_field_name = buf_create_from_str(ASYNC_FREE_FIELD_NAME);
        IrInstruction *implicit_allocator_ptr = ir_build_get_implicit_allocator(irb, scope, node,
                ImplicitAllocatorIdLocalVar);
        IrInstruction *free_fn_ptr = ir_build_field_ptr(irb, scope, node, implicit_allocator_ptr, free_field_name);
        IrInstruction *free_fn = ir_build_load_ptr(irb, scope, node, free_fn_ptr);
        IrInstruction *zero = ir_build_const_usize(irb, scope, node, 0);
        IrInstruction *coro_mem_ptr_maybe = ir_build_coro_free(irb, scope, node, coro_id, irb->exec->coro_handle);
        IrInstruction *u8_ptr_type_unknown_len = ir_build_const_type(irb, scope, node,
                get_pointer_to_type_extra(irb->codegen, irb->codegen->builtin_types.entry_u8,
                    false, false, PtrLenUnknown, 0, 0, 0));
        IrInstruction *coro_mem_ptr = ir_build_ptr_cast_src(irb, scope, node, u8_ptr_type_unknown_len,
                coro_mem_ptr_maybe, false);
        IrInstruction *coro_mem_ptr_ref = ir_build_ref(irb, scope, node, coro_mem_ptr, true, false);
        IrInstruction *coro_size_ptr = ir_build_var_ptr(irb, scope, node, coro_size_var);
        IrInstruction *coro_size = ir_build_load_ptr(irb, scope, node, coro_size_ptr);
        IrInstruction *mem_slice = ir_build_slice(irb, scope, node, coro_mem_ptr_ref, zero, coro_size, false);
        size_t arg_count = 2;
        IrInstruction **args = allocate<IrInstruction *>(arg_count);
        args[0] = implicit_allocator_ptr; // self
        args[1] = mem_slice; // old_mem
        ir_build_call(irb, scope, node, nullptr, free_fn, arg_count, args, false, FnInlineAuto, false, nullptr, nullptr);

        IrBasicBlock *resume_block = ir_create_basic_block(irb, scope, "Resume");
        ir_build_cond_br(irb, scope, node, resume_awaiter, resume_block, irb->exec->coro_suspend_block, const_bool_false);

        ir_set_cursor_at_end_and_append_block(irb, resume_block);
        ir_gen_resume_target(irb, scope, node, awaiter_handle);
        ir_build_br(irb, scope, node, irb->exec->coro_suspend_block, const_bool_false);
    }

    return true;
}

bool ir_gen_fn(CodeGen *codegen, ZigFn *fn_entry) {
    assert(fn_entry);

    IrExecutable *ir_executable = &fn_entry->ir_executable;
    AstNode *body_node = fn_entry->body_node;

    assert(fn_entry->child_scope);

    return ir_gen(codegen, body_node, fn_entry->child_scope, ir_executable);
}

static void add_call_stack_errors(CodeGen *codegen, IrExecutable *exec, ErrorMsg *err_msg, int limit) {
    if (!exec || !exec->source_node || limit < 0) return;
    add_error_note(codegen, err_msg, exec->source_node, buf_sprintf("called from here"));

    add_call_stack_errors(codegen, exec->parent_exec, err_msg, limit - 1);
}

static ErrorMsg *exec_add_error_node(CodeGen *codegen, IrExecutable *exec, AstNode *source_node, Buf *msg) {
    invalidate_exec(exec);
    ErrorMsg *err_msg = add_node_error(codegen, source_node, msg);
    if (exec->parent_exec) {
        add_call_stack_errors(codegen, exec, err_msg, 10);
    }
    return err_msg;
}

static ErrorMsg *ir_add_error_node(IrAnalyze *ira, AstNode *source_node, Buf *msg) {
    return exec_add_error_node(ira->codegen, ira->new_irb.exec, source_node, msg);
}

static ErrorMsg *opt_ir_add_error_node(IrAnalyze *ira, CodeGen *codegen, AstNode *source_node, Buf *msg) {
    if (ira != nullptr)
        return exec_add_error_node(codegen, ira->new_irb.exec, source_node, msg);
    else
        return add_node_error(codegen, source_node, msg);
}

static ErrorMsg *ir_add_error(IrAnalyze *ira, IrInstruction *source_instruction, Buf *msg) {
    return ir_add_error_node(ira, source_instruction->source_node, msg);
}

// This function takes a comptime ptr and makes the child const value conform to the type
// described by the pointer.
static Error eval_comptime_ptr_reinterpret(IrAnalyze *ira, CodeGen *codegen, AstNode *source_node,
        ConstExprValue *ptr_val)
{
    Error err;
    assert(ptr_val->type->id == ZigTypeIdPointer);
    ConstExprValue tmp = {};
    tmp.special = ConstValSpecialStatic;
    tmp.type = ptr_val->type->data.pointer.child_type;
    if ((err = ir_read_const_ptr(ira, codegen, source_node, &tmp, ptr_val)))
        return err;
    ConstExprValue *child_val = const_ptr_pointee_unchecked(codegen, ptr_val);
    copy_const_val(child_val, &tmp, false);
    return ErrorNone;
}

ConstExprValue *const_ptr_pointee(IrAnalyze *ira, CodeGen *codegen, ConstExprValue *const_val,
        AstNode *source_node)
{
    Error err;
    ConstExprValue *val = const_ptr_pointee_unchecked(codegen, const_val);
    assert(val != nullptr);
    assert(const_val->type->id == ZigTypeIdPointer);
    ZigType *expected_type = const_val->type->data.pointer.child_type;
    if (!types_have_same_zig_comptime_repr(val->type, expected_type)) {
        if ((err = eval_comptime_ptr_reinterpret(ira, codegen, source_node, const_val)))
            return nullptr;
        return const_ptr_pointee_unchecked(codegen, const_val);
    }
    return val;
}

static ConstExprValue *ir_exec_const_result(CodeGen *codegen, IrExecutable *exec) {
    IrBasicBlock *bb = exec->basic_block_list.at(0);
    for (size_t i = 0; i < bb->instruction_list.length; i += 1) {
        IrInstruction *instruction = bb->instruction_list.at(i);
        if (instruction->id == IrInstructionIdReturn) {
            IrInstructionReturn *ret_inst = (IrInstructionReturn *)instruction;
            IrInstruction *value = ret_inst->value;
            if (value->value.special == ConstValSpecialRuntime) {
                exec_add_error_node(codegen, exec, value->source_node,
                        buf_sprintf("unable to evaluate constant expression"));
                return &codegen->invalid_instruction->value;
            }
            return &value->value;
        } else if (ir_has_side_effects(instruction)) {
            exec_add_error_node(codegen, exec, instruction->source_node,
                    buf_sprintf("unable to evaluate constant expression"));
            return &codegen->invalid_instruction->value;
        }
    }
    return &codegen->invalid_instruction->value;
}

static bool ir_emit_global_runtime_side_effect(IrAnalyze *ira, IrInstruction *source_instruction) {
    if (ir_should_inline(ira->new_irb.exec, source_instruction->scope)) {
        ir_add_error(ira, source_instruction, buf_sprintf("unable to evaluate constant expression"));
        return false;
    }
    return true;
}

static bool const_val_fits_in_num_lit(ConstExprValue *const_val, ZigType *num_lit_type) {
    return ((num_lit_type->id == ZigTypeIdComptimeFloat &&
        (const_val->type->id == ZigTypeIdFloat || const_val->type->id == ZigTypeIdComptimeFloat)) ||
               (num_lit_type->id == ZigTypeIdComptimeInt &&
        (const_val->type->id == ZigTypeIdInt || const_val->type->id == ZigTypeIdComptimeInt)));
}

static bool float_has_fraction(ConstExprValue *const_val) {
    if (const_val->type->id == ZigTypeIdComptimeFloat) {
        return bigfloat_has_fraction(&const_val->data.x_bigfloat);
    } else if (const_val->type->id == ZigTypeIdFloat) {
        switch (const_val->type->data.floating.bit_count) {
            case 16:
                {
                    float16_t floored = f16_roundToInt(const_val->data.x_f16, softfloat_round_minMag, false);
                    return !f16_eq(floored, const_val->data.x_f16);
                }
            case 32:
                return floorf(const_val->data.x_f32) != const_val->data.x_f32;
            case 64:
                return floor(const_val->data.x_f64) != const_val->data.x_f64;
            case 128:
                {
                    float128_t floored;
                    f128M_roundToInt(&const_val->data.x_f128, softfloat_round_minMag, false, &floored);
                    return !f128M_eq(&floored, &const_val->data.x_f128);
                }
            default:
                zig_unreachable();
        }
    } else {
        zig_unreachable();
    }
}

static void float_append_buf(Buf *buf, ConstExprValue *const_val) {
    if (const_val->type->id == ZigTypeIdComptimeFloat) {
        bigfloat_append_buf(buf, &const_val->data.x_bigfloat);
    } else if (const_val->type->id == ZigTypeIdFloat) {
        switch (const_val->type->data.floating.bit_count) {
            case 16:
                buf_appendf(buf, "%f", zig_f16_to_double(const_val->data.x_f16));
                break;
            case 32:
                buf_appendf(buf, "%f", const_val->data.x_f32);
                break;
            case 64:
                buf_appendf(buf, "%f", const_val->data.x_f64);
                break;
            case 128:
                {
                    // TODO actual implementation
                    const size_t extra_len = 100;
                    size_t old_len = buf_len(buf);
                    buf_resize(buf, old_len + extra_len);

                    float64_t f64_value = f128M_to_f64(&const_val->data.x_f128);
                    double double_value;
                    memcpy(&double_value, &f64_value, sizeof(double));

                    int len = snprintf(buf_ptr(buf) + old_len, extra_len, "%f", double_value);
                    assert(len > 0);
                    buf_resize(buf, old_len + len);
                    break;
                }
            default:
                zig_unreachable();
        }
    } else {
        zig_unreachable();
    }
}

static void float_init_bigint(BigInt *bigint, ConstExprValue *const_val) {
    if (const_val->type->id == ZigTypeIdComptimeFloat) {
        bigint_init_bigfloat(bigint, &const_val->data.x_bigfloat);
    } else if (const_val->type->id == ZigTypeIdFloat) {
        switch (const_val->type->data.floating.bit_count) {
            case 16:
                {
                    double x = zig_f16_to_double(const_val->data.x_f16);
                    if (x >= 0) {
                        bigint_init_unsigned(bigint, (uint64_t)x);
                    } else {
                        bigint_init_unsigned(bigint, (uint64_t)-x);
                        bigint->is_negative = true;
                    }
                    break;
                }
            case 32:
                if (const_val->data.x_f32 >= 0) {
                    bigint_init_unsigned(bigint, (uint64_t)(const_val->data.x_f32));
                } else {
                    bigint_init_unsigned(bigint, (uint64_t)(-const_val->data.x_f32));
                    bigint->is_negative = true;
                }
                break;
            case 64:
                if (const_val->data.x_f64 >= 0) {
                    bigint_init_unsigned(bigint, (uint64_t)(const_val->data.x_f64));
                } else {
                    bigint_init_unsigned(bigint, (uint64_t)(-const_val->data.x_f64));
                    bigint->is_negative = true;
                }
                break;
            case 128:
                {
                    BigFloat tmp_float;
                    bigfloat_init_128(&tmp_float, const_val->data.x_f128);
                    bigint_init_bigfloat(bigint, &tmp_float);
                }
                break;
            default:
                zig_unreachable();
        }
    } else {
        zig_unreachable();
    }
}

static void float_init_bigfloat(ConstExprValue *dest_val, BigFloat *bigfloat) {
    if (dest_val->type->id == ZigTypeIdComptimeFloat) {
        bigfloat_init_bigfloat(&dest_val->data.x_bigfloat, bigfloat);
    } else if (dest_val->type->id == ZigTypeIdFloat) {
        switch (dest_val->type->data.floating.bit_count) {
            case 16:
                dest_val->data.x_f16 = bigfloat_to_f16(bigfloat);
                break;
            case 32:
                dest_val->data.x_f32 = bigfloat_to_f32(bigfloat);
                break;
            case 64:
                dest_val->data.x_f64 = bigfloat_to_f64(bigfloat);
                break;
            case 128:
                dest_val->data.x_f128 = bigfloat_to_f128(bigfloat);
                break;
            default:
                zig_unreachable();
        }
    } else {
        zig_unreachable();
    }
}

static void float_init_f16(ConstExprValue *dest_val, float16_t x) {
    if (dest_val->type->id == ZigTypeIdComptimeFloat) {
        bigfloat_init_16(&dest_val->data.x_bigfloat, x);
    } else if (dest_val->type->id == ZigTypeIdFloat) {
        switch (dest_val->type->data.floating.bit_count) {
            case 16:
                dest_val->data.x_f16 = x;
                break;
            case 32:
                dest_val->data.x_f32 = zig_f16_to_double(x);
                break;
            case 64:
                dest_val->data.x_f64 = zig_f16_to_double(x);
                break;
            case 128:
                f16_to_f128M(x, &dest_val->data.x_f128);
                break;
            default:
                zig_unreachable();
        }
    } else {
        zig_unreachable();
    }
}

static void float_init_f32(ConstExprValue *dest_val, float x) {
    if (dest_val->type->id == ZigTypeIdComptimeFloat) {
        bigfloat_init_32(&dest_val->data.x_bigfloat, x);
    } else if (dest_val->type->id == ZigTypeIdFloat) {
        switch (dest_val->type->data.floating.bit_count) {
            case 16:
                dest_val->data.x_f16 = zig_double_to_f16(x);
                break;
            case 32:
                dest_val->data.x_f32 = x;
                break;
            case 64:
                dest_val->data.x_f64 = x;
                break;
            case 128:
                {
                    float32_t x_f32;
                    memcpy(&x_f32, &x, sizeof(float));
                    f32_to_f128M(x_f32, &dest_val->data.x_f128);
                    break;
                }
            default:
                zig_unreachable();
        }
    } else {
        zig_unreachable();
    }
}

static void float_init_f64(ConstExprValue *dest_val, double x) {
    if (dest_val->type->id == ZigTypeIdComptimeFloat) {
        bigfloat_init_64(&dest_val->data.x_bigfloat, x);
    } else if (dest_val->type->id == ZigTypeIdFloat) {
        switch (dest_val->type->data.floating.bit_count) {
            case 16:
                dest_val->data.x_f16 = zig_double_to_f16(x);
                break;
            case 32:
                dest_val->data.x_f32 = x;
                break;
            case 64:
                dest_val->data.x_f64 = x;
                break;
            case 128:
                {
                    float64_t x_f64;
                    memcpy(&x_f64, &x, sizeof(double));
                    f64_to_f128M(x_f64, &dest_val->data.x_f128);
                    break;
                }
            default:
                zig_unreachable();
        }
    } else {
        zig_unreachable();
    }
}

static void float_init_f128(ConstExprValue *dest_val, float128_t x) {
    if (dest_val->type->id == ZigTypeIdComptimeFloat) {
        bigfloat_init_128(&dest_val->data.x_bigfloat, x);
    } else if (dest_val->type->id == ZigTypeIdFloat) {
        switch (dest_val->type->data.floating.bit_count) {
            case 16:
                dest_val->data.x_f16 = f128M_to_f16(&x);
                break;
            case 32:
                {
                    float32_t f32_val = f128M_to_f32(&x);
                    memcpy(&dest_val->data.x_f32, &f32_val, sizeof(float));
                    break;
                }
            case 64:
                {
                    float64_t f64_val = f128M_to_f64(&x);
                    memcpy(&dest_val->data.x_f64, &f64_val, sizeof(double));
                    break;
                }
            case 128:
                {
                    memcpy(&dest_val->data.x_f128, &x, sizeof(float128_t));
                    break;
                }
            default:
                zig_unreachable();
        }
    } else {
        zig_unreachable();
    }
}

static void float_init_float(ConstExprValue *dest_val, ConstExprValue *src_val) {
    if (src_val->type->id == ZigTypeIdComptimeFloat) {
        float_init_bigfloat(dest_val, &src_val->data.x_bigfloat);
    } else if (src_val->type->id == ZigTypeIdFloat) {
        switch (src_val->type->data.floating.bit_count) {
            case 16:
                float_init_f16(dest_val, src_val->data.x_f16);
                break;
            case 32:
                float_init_f32(dest_val, src_val->data.x_f32);
                break;
            case 64:
                float_init_f64(dest_val, src_val->data.x_f64);
                break;
            case 128:
                float_init_f128(dest_val, src_val->data.x_f128);
                break;
            default:
                zig_unreachable();
        }
    } else {
        zig_unreachable();
    }
}

static Cmp float_cmp(ConstExprValue *op1, ConstExprValue *op2) {
    assert(op1->type == op2->type);
    if (op1->type->id == ZigTypeIdComptimeFloat) {
        return bigfloat_cmp(&op1->data.x_bigfloat, &op2->data.x_bigfloat);
    } else if (op1->type->id == ZigTypeIdFloat) {
        switch (op1->type->data.floating.bit_count) {
            case 16:
                if (f16_lt(op1->data.x_f16, op2->data.x_f16)) {
                    return CmpLT;
                } else if (f16_lt(op2->data.x_f16, op1->data.x_f16)) {
                    return CmpGT;
                } else {
                    return CmpEQ;
                }
            case 32:
                if (op1->data.x_f32 > op2->data.x_f32) {
                    return CmpGT;
                } else if (op1->data.x_f32 < op2->data.x_f32) {
                    return CmpLT;
                } else {
                    return CmpEQ;
                }
            case 64:
                if (op1->data.x_f64 > op2->data.x_f64) {
                    return CmpGT;
                } else if (op1->data.x_f64 < op2->data.x_f64) {
                    return CmpLT;
                } else {
                    return CmpEQ;
                }
            case 128:
                if (f128M_lt(&op1->data.x_f128, &op2->data.x_f128)) {
                    return CmpLT;
                } else if (f128M_eq(&op1->data.x_f128, &op2->data.x_f128)) {
                    return CmpEQ;
                } else {
                    return CmpGT;
                }
            default:
                zig_unreachable();
        }
    } else {
        zig_unreachable();
    }
}

static Cmp float_cmp_zero(ConstExprValue *op) {
    if (op->type->id == ZigTypeIdComptimeFloat) {
        return bigfloat_cmp_zero(&op->data.x_bigfloat);
    } else if (op->type->id == ZigTypeIdFloat) {
        switch (op->type->data.floating.bit_count) {
            case 16:
                {
                    const float16_t zero = zig_double_to_f16(0);
                    if (f16_lt(op->data.x_f16, zero)) {
                        return CmpLT;
                    } else if (f16_lt(zero, op->data.x_f16)) {
                        return CmpGT;
                    } else {
                        return CmpEQ;
                    }
                }
            case 32:
                if (op->data.x_f32 < 0.0) {
                    return CmpLT;
                } else if (op->data.x_f32 > 0.0) {
                    return CmpGT;
                } else {
                    return CmpEQ;
                }
            case 64:
                if (op->data.x_f64 < 0.0) {
                    return CmpLT;
                } else if (op->data.x_f64 > 0.0) {
                    return CmpGT;
                } else {
                    return CmpEQ;
                }
            case 128:
                float128_t zero_float;
                ui32_to_f128M(0, &zero_float);
                if (f128M_lt(&op->data.x_f128, &zero_float)) {
                    return CmpLT;
                } else if (f128M_eq(&op->data.x_f128, &zero_float)) {
                    return CmpEQ;
                } else {
                    return CmpGT;
                }
            default:
                zig_unreachable();
        }
    } else {
        zig_unreachable();
    }
}

static void float_add(ConstExprValue *out_val, ConstExprValue *op1, ConstExprValue *op2) {
    assert(op1->type == op2->type);
    out_val->type = op1->type;
    if (op1->type->id == ZigTypeIdComptimeFloat) {
        bigfloat_add(&out_val->data.x_bigfloat, &op1->data.x_bigfloat, &op2->data.x_bigfloat);
    } else if (op1->type->id == ZigTypeIdFloat) {
        switch (op1->type->data.floating.bit_count) {
            case 16:
                out_val->data.x_f16 = f16_add(op1->data.x_f16, op2->data.x_f16);
                return;
            case 32:
                out_val->data.x_f32 =  op1->data.x_f32 + op2->data.x_f32;
                return;
            case 64:
                out_val->data.x_f64 =  op1->data.x_f64 + op2->data.x_f64;
                return;
            case 128:
                f128M_add(&op1->data.x_f128, &op2->data.x_f128, &out_val->data.x_f128);
                return;
            default:
                zig_unreachable();
        }
    } else {
        zig_unreachable();
    }
}

static void float_sub(ConstExprValue *out_val, ConstExprValue *op1, ConstExprValue *op2) {
    assert(op1->type == op2->type);
    out_val->type = op1->type;
    if (op1->type->id == ZigTypeIdComptimeFloat) {
        bigfloat_sub(&out_val->data.x_bigfloat, &op1->data.x_bigfloat, &op2->data.x_bigfloat);
    } else if (op1->type->id == ZigTypeIdFloat) {
        switch (op1->type->data.floating.bit_count) {
            case 16:
                out_val->data.x_f16 = f16_sub(op1->data.x_f16, op2->data.x_f16);
                return;
            case 32:
                out_val->data.x_f32 = op1->data.x_f32 - op2->data.x_f32;
                return;
            case 64:
                out_val->data.x_f64 = op1->data.x_f64 - op2->data.x_f64;
                return;
            case 128:
                f128M_sub(&op1->data.x_f128, &op2->data.x_f128, &out_val->data.x_f128);
                return;
            default:
                zig_unreachable();
        }
    } else {
        zig_unreachable();
    }
}

static void float_mul(ConstExprValue *out_val, ConstExprValue *op1, ConstExprValue *op2) {
    assert(op1->type == op2->type);
    out_val->type = op1->type;
    if (op1->type->id == ZigTypeIdComptimeFloat) {
        bigfloat_mul(&out_val->data.x_bigfloat, &op1->data.x_bigfloat, &op2->data.x_bigfloat);
    } else if (op1->type->id == ZigTypeIdFloat) {
        switch (op1->type->data.floating.bit_count) {
            case 16:
                out_val->data.x_f16 = f16_mul(op1->data.x_f16, op2->data.x_f16);
                return;
            case 32:
                out_val->data.x_f32 = op1->data.x_f32 * op2->data.x_f32;
                return;
            case 64:
                out_val->data.x_f64 = op1->data.x_f64 * op2->data.x_f64;
                return;
            case 128:
                f128M_mul(&op1->data.x_f128, &op2->data.x_f128, &out_val->data.x_f128);
                return;
            default:
                zig_unreachable();
        }
    } else {
        zig_unreachable();
    }
}

static void float_div(ConstExprValue *out_val, ConstExprValue *op1, ConstExprValue *op2) {
    assert(op1->type == op2->type);
    out_val->type = op1->type;
    if (op1->type->id == ZigTypeIdComptimeFloat) {
        bigfloat_div(&out_val->data.x_bigfloat, &op1->data.x_bigfloat, &op2->data.x_bigfloat);
    } else if (op1->type->id == ZigTypeIdFloat) {
        switch (op1->type->data.floating.bit_count) {
            case 16:
                out_val->data.x_f16 = f16_div(op1->data.x_f16, op2->data.x_f16);
                return;
            case 32:
                out_val->data.x_f32 = op1->data.x_f32 / op2->data.x_f32;
                return;
            case 64:
                out_val->data.x_f64 = op1->data.x_f64 / op2->data.x_f64;
                return;
            case 128:
                f128M_div(&op1->data.x_f128, &op2->data.x_f128, &out_val->data.x_f128);
                return;
            default:
                zig_unreachable();
        }
    } else {
        zig_unreachable();
    }
}

static void float_div_trunc(ConstExprValue *out_val, ConstExprValue *op1, ConstExprValue *op2) {
    assert(op1->type == op2->type);
    out_val->type = op1->type;
    if (op1->type->id == ZigTypeIdComptimeFloat) {
        bigfloat_div_trunc(&out_val->data.x_bigfloat, &op1->data.x_bigfloat, &op2->data.x_bigfloat);
    } else if (op1->type->id == ZigTypeIdFloat) {
        switch (op1->type->data.floating.bit_count) {
            case 16:
                out_val->data.x_f16 = f16_div(op1->data.x_f16, op2->data.x_f16);
                out_val->data.x_f16 = f16_roundToInt(out_val->data.x_f16, softfloat_round_minMag, false);
                return;
            case 32:
                out_val->data.x_f32 = truncf(op1->data.x_f32 / op2->data.x_f32);
                return;
            case 64:
                out_val->data.x_f64 = trunc(op1->data.x_f64 / op2->data.x_f64);
                return;
            case 128:
                f128M_div(&op1->data.x_f128, &op2->data.x_f128, &out_val->data.x_f128);
                f128M_roundToInt(&out_val->data.x_f128, softfloat_round_minMag, false, &out_val->data.x_f128);
                return;
            default:
                zig_unreachable();
        }
    } else {
        zig_unreachable();
    }
}

static void float_div_floor(ConstExprValue *out_val, ConstExprValue *op1, ConstExprValue *op2) {
    assert(op1->type == op2->type);
    out_val->type = op1->type;
    if (op1->type->id == ZigTypeIdComptimeFloat) {
        bigfloat_div_floor(&out_val->data.x_bigfloat, &op1->data.x_bigfloat, &op2->data.x_bigfloat);
    } else if (op1->type->id == ZigTypeIdFloat) {
        switch (op1->type->data.floating.bit_count) {
            case 16:
                out_val->data.x_f16 = f16_div(op1->data.x_f16, op2->data.x_f16);
                out_val->data.x_f16 = f16_roundToInt(out_val->data.x_f16, softfloat_round_min, false);
                return;
            case 32:
                out_val->data.x_f32 = floorf(op1->data.x_f32 / op2->data.x_f32);
                return;
            case 64:
                out_val->data.x_f64 = floor(op1->data.x_f64 / op2->data.x_f64);
                return;
            case 128:
                f128M_div(&op1->data.x_f128, &op2->data.x_f128, &out_val->data.x_f128);
                f128M_roundToInt(&out_val->data.x_f128, softfloat_round_min, false, &out_val->data.x_f128);
                return;
            default:
                zig_unreachable();
        }
    } else {
        zig_unreachable();
    }
}

static void float_rem(ConstExprValue *out_val, ConstExprValue *op1, ConstExprValue *op2) {
    assert(op1->type == op2->type);
    out_val->type = op1->type;
    if (op1->type->id == ZigTypeIdComptimeFloat) {
        bigfloat_rem(&out_val->data.x_bigfloat, &op1->data.x_bigfloat, &op2->data.x_bigfloat);
    } else if (op1->type->id == ZigTypeIdFloat) {
        switch (op1->type->data.floating.bit_count) {
            case 16:
                out_val->data.x_f16 = f16_rem(op1->data.x_f16, op2->data.x_f16);
                return;
            case 32:
                out_val->data.x_f32 = fmodf(op1->data.x_f32, op2->data.x_f32);
                return;
            case 64:
                out_val->data.x_f64 = fmod(op1->data.x_f64, op2->data.x_f64);
                return;
            case 128:
                f128M_rem(&op1->data.x_f128, &op2->data.x_f128, &out_val->data.x_f128);
                return;
            default:
                zig_unreachable();
        }
    } else {
        zig_unreachable();
    }
}

// c = a - b * trunc(a / b)
static float16_t zig_f16_mod(float16_t a, float16_t b) {
    float16_t c;
    c = f16_div(a, b);
    c = f16_roundToInt(c, softfloat_round_min, true);
    c = f16_mul(b, c);
    c = f16_sub(a, c);
    return c;
}

// c = a - b * trunc(a / b)
static void zig_f128M_mod(const float128_t* a, const float128_t* b, float128_t* c) {
    f128M_div(a, b, c);
    f128M_roundToInt(c, softfloat_round_min, true, c);
    f128M_mul(b, c, c);
    f128M_sub(a, c, c);
}

static void float_mod(ConstExprValue *out_val, ConstExprValue *op1, ConstExprValue *op2) {
    assert(op1->type == op2->type);
    out_val->type = op1->type;
    if (op1->type->id == ZigTypeIdComptimeFloat) {
        bigfloat_mod(&out_val->data.x_bigfloat, &op1->data.x_bigfloat, &op2->data.x_bigfloat);
    } else if (op1->type->id == ZigTypeIdFloat) {
        switch (op1->type->data.floating.bit_count) {
            case 16:
                out_val->data.x_f16 = zig_f16_mod(op1->data.x_f16, op2->data.x_f16);
                return;
            case 32:
                out_val->data.x_f32 = fmodf(fmodf(op1->data.x_f32, op2->data.x_f32) + op2->data.x_f32, op2->data.x_f32);
                return;
            case 64:
                out_val->data.x_f64 = fmod(fmod(op1->data.x_f64, op2->data.x_f64) + op2->data.x_f64, op2->data.x_f64);
                return;
            case 128:
                zig_f128M_mod(&op1->data.x_f128, &op2->data.x_f128, &out_val->data.x_f128);
                return;
            default:
                zig_unreachable();
        }
    } else {
        zig_unreachable();
    }
}

static void float_negate(ConstExprValue *out_val, ConstExprValue *op) {
    out_val->type = op->type;
    if (op->type->id == ZigTypeIdComptimeFloat) {
        bigfloat_negate(&out_val->data.x_bigfloat, &op->data.x_bigfloat);
    } else if (op->type->id == ZigTypeIdFloat) {
        switch (op->type->data.floating.bit_count) {
            case 16:
                {
                    const float16_t zero = zig_double_to_f16(0);
                    out_val->data.x_f16 = f16_sub(zero, op->data.x_f16);
                    return;
                }
            case 32:
                out_val->data.x_f32 = -op->data.x_f32;
                return;
            case 64:
                out_val->data.x_f64 = -op->data.x_f64;
                return;
            case 128:
                float128_t zero_f128;
                ui32_to_f128M(0, &zero_f128);
                f128M_sub(&zero_f128, &op->data.x_f128, &out_val->data.x_f128);
                return;
            default:
                zig_unreachable();
        }
    } else {
        zig_unreachable();
    }
}

void float_write_ieee597(ConstExprValue *op, uint8_t *buf, bool is_big_endian) {
    if (op->type->id == ZigTypeIdFloat) {
        switch (op->type->data.floating.bit_count) {
            case 16:
                memcpy(buf, &op->data.x_f16, 2); // TODO wrong when compiler is big endian
                return;
            case 32:
                memcpy(buf, &op->data.x_f32, 4); // TODO wrong when compiler is big endian
                return;
            case 64:
                memcpy(buf, &op->data.x_f64, 8); // TODO wrong when compiler is big endian
                return;
            case 128:
                memcpy(buf, &op->data.x_f128, 16); // TODO wrong when compiler is big endian
                return;
            default:
                zig_unreachable();
        }
    } else {
        zig_unreachable();
    }
}

void float_read_ieee597(ConstExprValue *val, uint8_t *buf, bool is_big_endian) {
    if (val->type->id == ZigTypeIdFloat) {
        switch (val->type->data.floating.bit_count) {
            case 16:
                memcpy(&val->data.x_f16, buf, 2); // TODO wrong when compiler is big endian
                return;
            case 32:
                memcpy(&val->data.x_f32, buf, 4); // TODO wrong when compiler is big endian
                return;
            case 64:
                memcpy(&val->data.x_f64, buf, 8); // TODO wrong when compiler is big endian
                return;
            case 128:
                memcpy(&val->data.x_f128, buf, 16); // TODO wrong when compiler is big endian
                return;
            default:
                zig_unreachable();
        }
    } else {
        zig_unreachable();
    }
}

static bool ir_num_lit_fits_in_other_type(IrAnalyze *ira, IrInstruction *instruction, ZigType *other_type,
        bool explicit_cast)
{
    if (type_is_invalid(other_type)) {
        return false;
    }

    ConstExprValue *const_val = ir_resolve_const(ira, instruction, UndefBad);
    assert(const_val != nullptr);

    bool const_val_is_int = (const_val->type->id == ZigTypeIdInt || const_val->type->id == ZigTypeIdComptimeInt);
    bool const_val_is_float = (const_val->type->id == ZigTypeIdFloat || const_val->type->id == ZigTypeIdComptimeFloat);
    assert(const_val_is_int || const_val_is_float);

    if (other_type->id == ZigTypeIdFloat) {
        if (const_val->type->id == ZigTypeIdComptimeInt || const_val->type->id == ZigTypeIdComptimeFloat) {
            return true;
        }
        if (const_val->type->id == ZigTypeIdInt) {
            BigFloat tmp_bf;
            bigfloat_init_bigint(&tmp_bf, &const_val->data.x_bigint);
            BigFloat orig_bf;
            switch (other_type->data.floating.bit_count) {
                case 16: {
                    float16_t tmp = bigfloat_to_f16(&tmp_bf);
                    bigfloat_init_16(&orig_bf, tmp);
                    break;
                }
                case 32: {
                    float tmp = bigfloat_to_f32(&tmp_bf);
                    bigfloat_init_32(&orig_bf, tmp);
                    break;
                }
                case 64: {
                    double tmp = bigfloat_to_f64(&tmp_bf);
                    bigfloat_init_64(&orig_bf, tmp);
                    break;
                }
                case 80:
                    zig_panic("TODO");
                case 128: {
                    float128_t tmp = bigfloat_to_f128(&tmp_bf);
                    bigfloat_init_128(&orig_bf, tmp);
                    break;
                }
                default:
                    zig_unreachable();
            }
            BigInt orig_bi;
            bigint_init_bigfloat(&orig_bi, &orig_bf);
            if (bigint_cmp(&orig_bi, &const_val->data.x_bigint) == CmpEQ) {
                return true;
            }
            Buf *val_buf = buf_alloc();
            bigint_append_buf(val_buf, &const_val->data.x_bigint, 10);
            ir_add_error(ira, instruction,
                buf_sprintf("integer value %s has no representation in type '%s'",
                    buf_ptr(val_buf),
                    buf_ptr(&other_type->name)));
            return false;
        }
        if (other_type->data.floating.bit_count >= const_val->type->data.floating.bit_count) {
            return true;
        }
        switch (other_type->data.floating.bit_count) {
            case 16:
                switch (const_val->type->data.floating.bit_count) {
                    case 32: {
                        float16_t tmp = zig_double_to_f16(const_val->data.x_f32);
                        float orig = zig_f16_to_double(tmp);
                        if (const_val->data.x_f32 == orig) {
                            return true;
                        }
                        break;
                    }
                    case 64: {
                        float16_t tmp = zig_double_to_f16(const_val->data.x_f64);
                        double orig = zig_f16_to_double(tmp);
                        if (const_val->data.x_f64 == orig) {
                            return true;
                        }
                        break;
                    }
                    case 80:
                        zig_panic("TODO");
                    case 128: {
                        float16_t tmp = f128M_to_f16(&const_val->data.x_f128);
                        float128_t orig;
                        f16_to_f128M(tmp, &orig);
                        if (f128M_eq(&orig, &const_val->data.x_f128)) {
                            return true;
                        }
                        break;
                    }
                    default:
                        zig_unreachable();
                }
                break;
            case 32:
                switch (const_val->type->data.floating.bit_count) {
                    case 64: {
                        float tmp = const_val->data.x_f64;
                        double orig = tmp;
                        if (const_val->data.x_f64 == orig) {
                            return true;
                        }
                        break;
                    }
                    case 80:
                        zig_panic("TODO");
                    case 128: {
                        float32_t tmp = f128M_to_f32(&const_val->data.x_f128);
                        float128_t orig;
                        f32_to_f128M(tmp, &orig);
                        if (f128M_eq(&orig, &const_val->data.x_f128)) {
                            return true;
                        }
                        break;
                    }
                    default:
                        zig_unreachable();
                }
                break;
            case 64:
                switch (const_val->type->data.floating.bit_count) {
                    case 80:
                        zig_panic("TODO");
                    case 128: {
                        float64_t tmp = f128M_to_f64(&const_val->data.x_f128);
                        float128_t orig;
                        f64_to_f128M(tmp, &orig);
                        if (f128M_eq(&orig, &const_val->data.x_f128)) {
                            return true;
                        }
                        break;
                    }
                    default:
                        zig_unreachable();
                }
                break;
            case 80:
                assert(const_val->type->data.floating.bit_count == 128);
                zig_panic("TODO");
            case 128:
                return true;
            default:
                zig_unreachable();
        }
        Buf *val_buf = buf_alloc();
        float_append_buf(val_buf, const_val);
        ir_add_error(ira, instruction,
            buf_sprintf("cast of value %s to type '%s' loses information",
                buf_ptr(val_buf),
                buf_ptr(&other_type->name)));
        return false;
    } else if (other_type->id == ZigTypeIdInt && const_val_is_int) {
        if (!other_type->data.integral.is_signed && const_val->data.x_bigint.is_negative) {
            Buf *val_buf = buf_alloc();
            bigint_append_buf(val_buf, &const_val->data.x_bigint, 10);
            ir_add_error(ira, instruction,
                buf_sprintf("cannot cast negative value %s to unsigned integer type '%s'",
                    buf_ptr(val_buf),
                    buf_ptr(&other_type->name)));
            return false;
        }
        if (bigint_fits_in_bits(&const_val->data.x_bigint, other_type->data.integral.bit_count,
                    other_type->data.integral.is_signed))
        {
            return true;
        }
    } else if (const_val_fits_in_num_lit(const_val, other_type)) {
        return true;
    } else if (other_type->id == ZigTypeIdOptional) {
        ZigType *child_type = other_type->data.maybe.child_type;
        if (const_val_fits_in_num_lit(const_val, child_type)) {
            return true;
        } else if (child_type->id == ZigTypeIdInt && const_val_is_int) {
            if (!child_type->data.integral.is_signed && const_val->data.x_bigint.is_negative) {
                Buf *val_buf = buf_alloc();
                bigint_append_buf(val_buf, &const_val->data.x_bigint, 10);
                ir_add_error(ira, instruction,
                    buf_sprintf("cannot cast negative value %s to unsigned integer type '%s'",
                        buf_ptr(val_buf),
                        buf_ptr(&child_type->name)));
                return false;
            }
            if (bigint_fits_in_bits(&const_val->data.x_bigint,
                        child_type->data.integral.bit_count,
                        child_type->data.integral.is_signed))
            {
                return true;
            }
        } else if (child_type->id == ZigTypeIdFloat && const_val_is_float) {
            return true;
        }
    }
    if (explicit_cast && (other_type->id == ZigTypeIdInt || other_type->id == ZigTypeIdComptimeInt) &&
        const_val_is_float)
    {
        if (float_has_fraction(const_val)) {
            Buf *val_buf = buf_alloc();
            float_append_buf(val_buf, const_val);

            ir_add_error(ira, instruction,
                buf_sprintf("fractional component prevents float value %s from being casted to type '%s'",
                    buf_ptr(val_buf),
                    buf_ptr(&other_type->name)));
            return false;
        } else {
            if (other_type->id == ZigTypeIdComptimeInt) {
                return true;
            } else {
                BigInt bigint;
                float_init_bigint(&bigint, const_val);
                if (bigint_fits_in_bits(&bigint, other_type->data.integral.bit_count,
                    other_type->data.integral.is_signed))
                {
                    return true;
                }
            }
        }
    }

    const char *num_lit_str;
    Buf *val_buf = buf_alloc();
    if (const_val_is_float) {
        num_lit_str = "float";
        float_append_buf(val_buf, const_val);
    } else {
        num_lit_str = "integer";
        bigint_append_buf(val_buf, &const_val->data.x_bigint, 10);
    }

    ir_add_error(ira, instruction,
        buf_sprintf("%s value %s cannot be implicitly casted to type '%s'",
            num_lit_str,
            buf_ptr(val_buf),
            buf_ptr(&other_type->name)));
    return false;
}

static bool is_slice(ZigType *type) {
    return type->id == ZigTypeIdStruct && type->data.structure.is_slice;
}

static bool slice_is_const(ZigType *type) {
    assert(is_slice(type));
    return type->data.structure.fields[slice_ptr_index].type_entry->data.pointer.is_const;
}

static ZigType *get_error_set_intersection(IrAnalyze *ira, ZigType *set1, ZigType *set2,
        AstNode *source_node)
{
    assert(set1->id == ZigTypeIdErrorSet);
    assert(set2->id == ZigTypeIdErrorSet);

    if (!resolve_inferred_error_set(ira->codegen, set1, source_node)) {
        return ira->codegen->builtin_types.entry_invalid;
    }
    if (!resolve_inferred_error_set(ira->codegen, set2, source_node)) {
        return ira->codegen->builtin_types.entry_invalid;
    }
    if (type_is_global_error_set(set1)) {
        return set2;
    }
    if (type_is_global_error_set(set2)) {
        return set1;
    }
    ErrorTableEntry **errors = allocate<ErrorTableEntry *>(ira->codegen->errors_by_index.length);
    for (uint32_t i = 0; i < set1->data.error_set.err_count; i += 1) {
        ErrorTableEntry *error_entry = set1->data.error_set.errors[i];
        assert(errors[error_entry->value] == nullptr);
        errors[error_entry->value] = error_entry;
    }
    ZigList<ErrorTableEntry *> intersection_list = {};

    ZigType *err_set_type = new_type_table_entry(ZigTypeIdErrorSet);
    buf_resize(&err_set_type->name, 0);
    buf_appendf(&err_set_type->name, "error{");

    for (uint32_t i = 0; i < set2->data.error_set.err_count; i += 1) {
        ErrorTableEntry *error_entry = set2->data.error_set.errors[i];
        ErrorTableEntry *existing_entry = errors[error_entry->value];
        if (existing_entry != nullptr) {
            intersection_list.append(existing_entry);
            buf_appendf(&err_set_type->name, "%s,", buf_ptr(&existing_entry->name));
        }
    }
    free(errors);

    err_set_type->type_ref = ira->codegen->builtin_types.entry_global_error_set->type_ref;
    err_set_type->di_type = ira->codegen->builtin_types.entry_global_error_set->di_type;
    err_set_type->data.error_set.err_count = intersection_list.length;
    err_set_type->data.error_set.errors = intersection_list.items;
    err_set_type->zero_bits = intersection_list.length == 0;

    buf_appendf(&err_set_type->name, "}");

    ira->codegen->error_di_types.append(&err_set_type->di_type);

    return err_set_type;
}

static ConstCastOnly types_match_const_cast_only(IrAnalyze *ira, ZigType *wanted_type,
        ZigType *actual_type, AstNode *source_node, bool wanted_is_mutable)
{
    CodeGen *g = ira->codegen;
    ConstCastOnly result = {};
    result.id = ConstCastResultIdOk;

    Error err;

    if (wanted_type == actual_type)
        return result;

    // If pointers have the same representation in memory, they can be "const-casted".
    // `const` attribute can be gained
    // `volatile` attribute can be gained
    // `allowzero` attribute can be gained (whether from explicit attribute, C pointer, or optional pointer)
    //   but only if !wanted_is_mutable
    // alignment can be decreased
    // bit offset attributes must match exactly
    // PtrLenSingle/PtrLenUnknown must match exactly, but PtrLenC matches either one
    ZigType *wanted_ptr_type = get_src_ptr_type(wanted_type);
    ZigType *actual_ptr_type = get_src_ptr_type(actual_type);
    bool wanted_allows_zero = ptr_allows_addr_zero(wanted_type);
    bool actual_allows_zero = ptr_allows_addr_zero(actual_type);
    bool wanted_is_c_ptr = wanted_type->id == ZigTypeIdPointer && wanted_type->data.pointer.ptr_len == PtrLenC;
    bool actual_is_c_ptr = actual_type->id == ZigTypeIdPointer && actual_type->data.pointer.ptr_len == PtrLenC;
    bool wanted_opt_or_ptr = wanted_ptr_type != nullptr &&
        (wanted_type->id == ZigTypeIdPointer || wanted_type->id == ZigTypeIdOptional);
    bool actual_opt_or_ptr = actual_ptr_type != nullptr &&
        (actual_type->id == ZigTypeIdPointer || actual_type->id == ZigTypeIdOptional);
    if (wanted_opt_or_ptr && actual_opt_or_ptr) {
        ConstCastOnly child = types_match_const_cast_only(ira, wanted_ptr_type->data.pointer.child_type,
                actual_ptr_type->data.pointer.child_type, source_node, !wanted_ptr_type->data.pointer.is_const);
        if (child.id == ConstCastResultIdInvalid)
            return child;
        if (child.id != ConstCastResultIdOk) {
            result.id = ConstCastResultIdPointerChild;
            result.data.pointer_mismatch = allocate_nonzero<ConstCastPointerMismatch>(1);
            result.data.pointer_mismatch->child = child;
            result.data.pointer_mismatch->wanted_child = wanted_ptr_type->data.pointer.child_type;
            result.data.pointer_mismatch->actual_child = actual_ptr_type->data.pointer.child_type;
            return result;
        }
        bool ok_allows_zero = (wanted_allows_zero &&
                (actual_allows_zero || !wanted_is_mutable)) ||
            (!wanted_allows_zero && !actual_allows_zero);
        if (!ok_allows_zero) {
            result.id = ConstCastResultIdBadAllowsZero;
            result.data.bad_allows_zero = allocate_nonzero<ConstCastBadAllowsZero>(1);
            result.data.bad_allows_zero->wanted_type = wanted_type;
            result.data.bad_allows_zero->actual_type = actual_type;
            return result;
        }
        if ((err = type_resolve(g, actual_ptr_type->data.pointer.child_type, ResolveStatusAlignmentKnown))) {
            result.id = ConstCastResultIdInvalid;
            return result;
        }
        if ((err = type_resolve(g, wanted_ptr_type->data.pointer.child_type, ResolveStatusAlignmentKnown))) {
            result.id = ConstCastResultIdInvalid;
            return result;
        }
        bool ptr_lens_equal = actual_ptr_type->data.pointer.ptr_len == wanted_ptr_type->data.pointer.ptr_len;
        if ((ptr_lens_equal || wanted_is_c_ptr || actual_is_c_ptr) &&
            type_has_bits(wanted_type) == type_has_bits(actual_type) &&
            (!actual_ptr_type->data.pointer.is_const || wanted_ptr_type->data.pointer.is_const) &&
            (!actual_ptr_type->data.pointer.is_volatile || wanted_ptr_type->data.pointer.is_volatile) &&
            actual_ptr_type->data.pointer.bit_offset_in_host == wanted_ptr_type->data.pointer.bit_offset_in_host &&
            actual_ptr_type->data.pointer.host_int_bytes == wanted_ptr_type->data.pointer.host_int_bytes &&
            get_ptr_align(ira->codegen, actual_ptr_type) >= get_ptr_align(ira->codegen, wanted_ptr_type))
        {
            return result;
        }
    }

    // slice const
    if (is_slice(wanted_type) && is_slice(actual_type)) {
        ZigType *actual_ptr_type = actual_type->data.structure.fields[slice_ptr_index].type_entry;
        ZigType *wanted_ptr_type = wanted_type->data.structure.fields[slice_ptr_index].type_entry;
        if ((err = type_resolve(g, actual_ptr_type->data.pointer.child_type, ResolveStatusAlignmentKnown))) {
            result.id = ConstCastResultIdInvalid;
            return result;
        }
        if ((err = type_resolve(g, wanted_ptr_type->data.pointer.child_type, ResolveStatusAlignmentKnown))) {
            result.id = ConstCastResultIdInvalid;
            return result;
        }
        if ((!actual_ptr_type->data.pointer.is_const || wanted_ptr_type->data.pointer.is_const) &&
            (!actual_ptr_type->data.pointer.is_volatile || wanted_ptr_type->data.pointer.is_volatile) &&
            actual_ptr_type->data.pointer.bit_offset_in_host == wanted_ptr_type->data.pointer.bit_offset_in_host &&
            actual_ptr_type->data.pointer.host_int_bytes == wanted_ptr_type->data.pointer.host_int_bytes &&
            get_ptr_align(g, actual_ptr_type) >= get_ptr_align(g, wanted_ptr_type))
        {
            ConstCastOnly child = types_match_const_cast_only(ira, wanted_ptr_type->data.pointer.child_type,
                    actual_ptr_type->data.pointer.child_type, source_node, !wanted_ptr_type->data.pointer.is_const);
            if (child.id == ConstCastResultIdInvalid)
                return child;
            if (child.id != ConstCastResultIdOk) {
                result.id = ConstCastResultIdSliceChild;
                result.data.slice_mismatch = allocate_nonzero<ConstCastSliceMismatch>(1);
                result.data.slice_mismatch->child = child;
                result.data.slice_mismatch->actual_child = actual_ptr_type->data.pointer.child_type;
                result.data.slice_mismatch->wanted_child = wanted_ptr_type->data.pointer.child_type;
            }
            return result;
        }
    }

    // maybe
    if (wanted_type->id == ZigTypeIdOptional && actual_type->id == ZigTypeIdOptional) {
        ConstCastOnly child = types_match_const_cast_only(ira, wanted_type->data.maybe.child_type,
                actual_type->data.maybe.child_type, source_node, wanted_is_mutable);
        if (child.id == ConstCastResultIdInvalid)
            return child;
        if (child.id != ConstCastResultIdOk) {
            result.id = ConstCastResultIdOptionalChild;
            result.data.optional = allocate_nonzero<ConstCastOptionalMismatch>(1);
            result.data.optional->child = child;
            result.data.optional->wanted_child = wanted_type->data.maybe.child_type;
            result.data.optional->actual_child = actual_type->data.maybe.child_type;
        }
        return result;
    }

    // error union
    if (wanted_type->id == ZigTypeIdErrorUnion && actual_type->id == ZigTypeIdErrorUnion) {
        ConstCastOnly payload_child = types_match_const_cast_only(ira, wanted_type->data.error_union.payload_type,
                actual_type->data.error_union.payload_type, source_node, wanted_is_mutable);
        if (payload_child.id == ConstCastResultIdInvalid)
            return payload_child;
        if (payload_child.id != ConstCastResultIdOk) {
            result.id = ConstCastResultIdErrorUnionPayload;
            result.data.error_union_payload = allocate_nonzero<ConstCastErrUnionPayloadMismatch>(1);
            result.data.error_union_payload->child = payload_child;
            result.data.error_union_payload->wanted_payload = wanted_type->data.error_union.payload_type;
            result.data.error_union_payload->actual_payload = actual_type->data.error_union.payload_type;
            return result;
        }
        ConstCastOnly error_set_child = types_match_const_cast_only(ira, wanted_type->data.error_union.err_set_type,
                actual_type->data.error_union.err_set_type, source_node, wanted_is_mutable);
        if (error_set_child.id == ConstCastResultIdInvalid)
            return error_set_child;
        if (error_set_child.id != ConstCastResultIdOk) {
            result.id = ConstCastResultIdErrorUnionErrorSet;
            result.data.error_union_error_set = allocate_nonzero<ConstCastErrUnionErrSetMismatch>(1);
            result.data.error_union_error_set->child = error_set_child;
            result.data.error_union_error_set->wanted_err_set = wanted_type->data.error_union.err_set_type;
            result.data.error_union_error_set->actual_err_set = actual_type->data.error_union.err_set_type;
            return result;
        }
        return result;
    }

    // error set
    if (wanted_type->id == ZigTypeIdErrorSet && actual_type->id == ZigTypeIdErrorSet) {
        ZigType *contained_set = actual_type;
        ZigType *container_set = wanted_type;

        // if the container set is inferred, then this will always work.
        if (container_set->data.error_set.infer_fn != nullptr) {
            return result;
        }
        // if the container set is the global one, it will always work.
        if (type_is_global_error_set(container_set)) {
            return result;
        }

        if (!resolve_inferred_error_set(ira->codegen, contained_set, source_node)) {
            result.id = ConstCastResultIdUnresolvedInferredErrSet;
            return result;
        }

        if (type_is_global_error_set(contained_set)) {
            result.id = ConstCastResultIdErrSetGlobal;
            return result;
        }

        ErrorTableEntry **errors = allocate<ErrorTableEntry *>(g->errors_by_index.length);
        for (uint32_t i = 0; i < container_set->data.error_set.err_count; i += 1) {
            ErrorTableEntry *error_entry = container_set->data.error_set.errors[i];
            assert(errors[error_entry->value] == nullptr);
            errors[error_entry->value] = error_entry;
        }
        for (uint32_t i = 0; i < contained_set->data.error_set.err_count; i += 1) {
            ErrorTableEntry *contained_error_entry = contained_set->data.error_set.errors[i];
            ErrorTableEntry *error_entry = errors[contained_error_entry->value];
            if (error_entry == nullptr) {
                if (result.id == ConstCastResultIdOk) {
                    result.id = ConstCastResultIdErrSet;
                    result.data.error_set_mismatch = allocate<ConstCastErrSetMismatch>(1);
                }
                result.data.error_set_mismatch->missing_errors.append(contained_error_entry);
            }
        }
        free(errors);
        return result;
    }

    if (wanted_type == ira->codegen->builtin_types.entry_promise &&
        actual_type->id == ZigTypeIdPromise)
    {
        return result;
    }

    // fn
    if (wanted_type->id == ZigTypeIdFn &&
        actual_type->id == ZigTypeIdFn)
    {
        if (wanted_type->data.fn.fn_type_id.alignment > actual_type->data.fn.fn_type_id.alignment) {
            result.id = ConstCastResultIdFnAlign;
            return result;
        }
        if (wanted_type->data.fn.fn_type_id.cc != actual_type->data.fn.fn_type_id.cc) {
            result.id = ConstCastResultIdFnCC;
            return result;
        }
        if (wanted_type->data.fn.fn_type_id.is_var_args != actual_type->data.fn.fn_type_id.is_var_args) {
            result.id = ConstCastResultIdFnVarArgs;
            return result;
        }
        if (wanted_type->data.fn.is_generic != actual_type->data.fn.is_generic) {
            result.id = ConstCastResultIdFnIsGeneric;
            return result;
        }
        if (!wanted_type->data.fn.is_generic &&
            actual_type->data.fn.fn_type_id.return_type->id != ZigTypeIdUnreachable)
        {
            ConstCastOnly child = types_match_const_cast_only(ira, wanted_type->data.fn.fn_type_id.return_type,
                    actual_type->data.fn.fn_type_id.return_type, source_node, false);
            if (child.id == ConstCastResultIdInvalid)
                return child;
            if (child.id != ConstCastResultIdOk) {
                result.id = ConstCastResultIdFnReturnType;
                result.data.return_type = allocate_nonzero<ConstCastOnly>(1);
                *result.data.return_type = child;
                return result;
            }
        }
        if (!wanted_type->data.fn.is_generic && wanted_type->data.fn.fn_type_id.cc == CallingConventionAsync) {
            ConstCastOnly child = types_match_const_cast_only(ira,
                    actual_type->data.fn.fn_type_id.async_allocator_type,
                    wanted_type->data.fn.fn_type_id.async_allocator_type,
                    source_node, false);
            if (child.id == ConstCastResultIdInvalid)
                return child;
            if (child.id != ConstCastResultIdOk) {
                result.id = ConstCastResultIdAsyncAllocatorType;
                result.data.async_allocator_type = allocate_nonzero<ConstCastOnly>(1);
                *result.data.async_allocator_type = child;
                return result;
            }
        }
        if (wanted_type->data.fn.fn_type_id.param_count != actual_type->data.fn.fn_type_id.param_count) {
            result.id = ConstCastResultIdFnArgCount;
            return result;
        }
        if (wanted_type->data.fn.fn_type_id.next_param_index != actual_type->data.fn.fn_type_id.next_param_index) {
            result.id = ConstCastResultIdFnGenericArgCount;
            return result;
        }
        assert(wanted_type->data.fn.is_generic ||
                wanted_type->data.fn.fn_type_id.next_param_index  == wanted_type->data.fn.fn_type_id.param_count);
        for (size_t i = 0; i < wanted_type->data.fn.fn_type_id.next_param_index; i += 1) {
            // note it's reversed for parameters
            FnTypeParamInfo *actual_param_info = &actual_type->data.fn.fn_type_id.param_info[i];
            FnTypeParamInfo *expected_param_info = &wanted_type->data.fn.fn_type_id.param_info[i];

            ConstCastOnly arg_child = types_match_const_cast_only(ira, actual_param_info->type,
                    expected_param_info->type, source_node, false);
            if (arg_child.id == ConstCastResultIdInvalid)
                return arg_child;
            if (arg_child.id != ConstCastResultIdOk) {
                result.id = ConstCastResultIdFnArg;
                result.data.fn_arg.arg_index = i;
                result.data.fn_arg.actual_param_type = actual_param_info->type;
                result.data.fn_arg.expected_param_type = expected_param_info->type;
                result.data.fn_arg.child = allocate_nonzero<ConstCastOnly>(1);
                *result.data.fn_arg.child = arg_child;
                return result;
            }

            if (expected_param_info->is_noalias != actual_param_info->is_noalias) {
                result.id = ConstCastResultIdFnArgNoAlias;
                result.data.arg_no_alias.arg_index = i;
                return result;
            }
        }
        return result;
    }

    result.id = ConstCastResultIdType;
    result.data.type_mismatch = allocate_nonzero<ConstCastTypeMismatch>(1);
    result.data.type_mismatch->wanted_type = wanted_type;
    result.data.type_mismatch->actual_type = actual_type;
    return result;
}

static void update_errors_helper(CodeGen *g, ErrorTableEntry ***errors, size_t *errors_count) {
    size_t old_errors_count = *errors_count;
    *errors_count = g->errors_by_index.length;
    *errors = reallocate(*errors, old_errors_count, *errors_count);
}

static ZigType *ir_resolve_peer_types(IrAnalyze *ira, AstNode *source_node, ZigType *expected_type,
        IrInstruction **instructions, size_t instruction_count)
{
    Error err;
    assert(instruction_count >= 1);
    IrInstruction *prev_inst = instructions[0];
    if (type_is_invalid(prev_inst->value.type)) {
        return ira->codegen->builtin_types.entry_invalid;
    }
    ErrorTableEntry **errors = nullptr;
    size_t errors_count = 0;
    ZigType *err_set_type = nullptr;
    if (prev_inst->value.type->id == ZigTypeIdErrorSet) {
        if (!resolve_inferred_error_set(ira->codegen, prev_inst->value.type, prev_inst->source_node)) {
            return ira->codegen->builtin_types.entry_invalid;
        }
        if (type_is_global_error_set(prev_inst->value.type)) {
            err_set_type = ira->codegen->builtin_types.entry_global_error_set;
        } else {
            err_set_type = prev_inst->value.type;
            update_errors_helper(ira->codegen, &errors, &errors_count);

            for (uint32_t i = 0; i < err_set_type->data.error_set.err_count; i += 1) {
                ErrorTableEntry *error_entry = err_set_type->data.error_set.errors[i];
                assert(errors[error_entry->value] == nullptr);
                errors[error_entry->value] = error_entry;
            }
        }
    }

    bool any_are_null = (prev_inst->value.type->id == ZigTypeIdNull);
    bool convert_to_const_slice = false;
    for (size_t i = 1; i < instruction_count; i += 1) {
        IrInstruction *cur_inst = instructions[i];
        ZigType *cur_type = cur_inst->value.type;
        ZigType *prev_type = prev_inst->value.type;

        if (type_is_invalid(cur_type)) {
            return cur_type;
        }

        if (prev_type->id == ZigTypeIdUnreachable) {
            prev_inst = cur_inst;
            continue;
        }

        if (cur_type->id == ZigTypeIdUnreachable) {
            continue;
        }

        if (prev_type->id == ZigTypeIdErrorSet) {
            assert(err_set_type != nullptr);
            if (cur_type->id == ZigTypeIdErrorSet) {
                if (type_is_global_error_set(err_set_type)) {
                    continue;
                }
                if (!resolve_inferred_error_set(ira->codegen, cur_type, cur_inst->source_node)) {
                    return ira->codegen->builtin_types.entry_invalid;
                }
                if (type_is_global_error_set(cur_type)) {
                    err_set_type = ira->codegen->builtin_types.entry_global_error_set;
                    prev_inst = cur_inst;
                    continue;
                }

                // number of declared errors might have increased now
                update_errors_helper(ira->codegen, &errors, &errors_count);

                // if err_set_type is a superset of cur_type, keep err_set_type.
                // if cur_type is a superset of err_set_type, switch err_set_type to cur_type
                bool prev_is_superset = true;
                for (uint32_t i = 0; i < cur_type->data.error_set.err_count; i += 1) {
                    ErrorTableEntry *contained_error_entry = cur_type->data.error_set.errors[i];
                    ErrorTableEntry *error_entry = errors[contained_error_entry->value];
                    if (error_entry == nullptr) {
                        prev_is_superset = false;
                        break;
                    }
                }
                if (prev_is_superset) {
                    continue;
                }

                // unset everything in errors
                for (uint32_t i = 0; i < err_set_type->data.error_set.err_count; i += 1) {
                    ErrorTableEntry *error_entry = err_set_type->data.error_set.errors[i];
                    errors[error_entry->value] = nullptr;
                }
                for (uint32_t i = 0, count = ira->codegen->errors_by_index.length; i < count; i += 1) {
                    assert(errors[i] == nullptr);
                }
                for (uint32_t i = 0; i < cur_type->data.error_set.err_count; i += 1) {
                    ErrorTableEntry *error_entry = cur_type->data.error_set.errors[i];
                    assert(errors[error_entry->value] == nullptr);
                    errors[error_entry->value] = error_entry;
                }
                bool cur_is_superset = true;
                for (uint32_t i = 0; i < err_set_type->data.error_set.err_count; i += 1) {
                    ErrorTableEntry *contained_error_entry = err_set_type->data.error_set.errors[i];
                    ErrorTableEntry *error_entry = errors[contained_error_entry->value];
                    if (error_entry == nullptr) {
                        cur_is_superset = false;
                        break;
                    }
                }
                if (cur_is_superset) {
                    err_set_type = cur_type;
                    prev_inst = cur_inst;
                    assert(errors != nullptr);
                    continue;
                }

                // neither of them are supersets. so we invent a new error set type that is a union of both of them
                err_set_type = get_error_set_union(ira->codegen, errors, cur_type, err_set_type);
                assert(errors != nullptr);
                continue;
            } else if (cur_type->id == ZigTypeIdErrorUnion) {
                if (type_is_global_error_set(err_set_type)) {
                    prev_inst = cur_inst;
                    continue;
                }
                ZigType *cur_err_set_type = cur_type->data.error_union.err_set_type;
                if (!resolve_inferred_error_set(ira->codegen, cur_err_set_type, cur_inst->source_node)) {
                    return ira->codegen->builtin_types.entry_invalid;
                }
                if (type_is_global_error_set(cur_err_set_type)) {
                    err_set_type = ira->codegen->builtin_types.entry_global_error_set;
                    prev_inst = cur_inst;
                    continue;
                }

                update_errors_helper(ira->codegen, &errors, &errors_count);

                // test if err_set_type is a subset of cur_type's error set
                // unset everything in errors
                for (uint32_t i = 0; i < err_set_type->data.error_set.err_count; i += 1) {
                    ErrorTableEntry *error_entry = err_set_type->data.error_set.errors[i];
                    errors[error_entry->value] = nullptr;
                }
                for (uint32_t i = 0, count = ira->codegen->errors_by_index.length; i < count; i += 1) {
                    assert(errors[i] == nullptr);
                }
                for (uint32_t i = 0; i < cur_err_set_type->data.error_set.err_count; i += 1) {
                    ErrorTableEntry *error_entry = cur_err_set_type->data.error_set.errors[i];
                    assert(errors[error_entry->value] == nullptr);
                    errors[error_entry->value] = error_entry;
                }
                bool cur_is_superset = true;
                for (uint32_t i = 0; i < err_set_type->data.error_set.err_count; i += 1) {
                    ErrorTableEntry *contained_error_entry = err_set_type->data.error_set.errors[i];
                    ErrorTableEntry *error_entry = errors[contained_error_entry->value];
                    if (error_entry == nullptr) {
                        cur_is_superset = false;
                        break;
                    }
                }
                if (cur_is_superset) {
                    err_set_type = cur_err_set_type;
                    prev_inst = cur_inst;
                    assert(errors != nullptr);
                    continue;
                }

                // not a subset. invent new error set type, union of both of them
                err_set_type = get_error_set_union(ira->codegen, errors, cur_err_set_type, err_set_type);
                prev_inst = cur_inst;
                assert(errors != nullptr);
                continue;
            } else {
                prev_inst = cur_inst;
                continue;
            }
        }

        if (cur_type->id == ZigTypeIdErrorSet) {
            if (prev_type->id == ZigTypeIdArray) {
                convert_to_const_slice = true;
            }
            if (!resolve_inferred_error_set(ira->codegen, cur_type, cur_inst->source_node)) {
                return ira->codegen->builtin_types.entry_invalid;
            }
            if (type_is_global_error_set(cur_type)) {
                err_set_type = ira->codegen->builtin_types.entry_global_error_set;
                continue;
            }
            if (err_set_type != nullptr && type_is_global_error_set(err_set_type)) {
                continue;
            }

            update_errors_helper(ira->codegen, &errors, &errors_count);

            if (err_set_type == nullptr) {
                if (prev_type->id == ZigTypeIdErrorUnion) {
                    err_set_type = prev_type->data.error_union.err_set_type;
                } else {
                    err_set_type = cur_type;
                }
                for (uint32_t i = 0; i < err_set_type->data.error_set.err_count; i += 1) {
                    ErrorTableEntry *error_entry = err_set_type->data.error_set.errors[i];
                    assert(errors[error_entry->value] == nullptr);
                    errors[error_entry->value] = error_entry;
                }
                if (err_set_type == cur_type) {
                    continue;
                }
            }
            // check if the cur type error set is a subset
            bool prev_is_superset = true;
            for (uint32_t i = 0; i < cur_type->data.error_set.err_count; i += 1) {
                ErrorTableEntry *contained_error_entry = cur_type->data.error_set.errors[i];
                ErrorTableEntry *error_entry = errors[contained_error_entry->value];
                if (error_entry == nullptr) {
                    prev_is_superset = false;
                    break;
                }
            }
            if (prev_is_superset) {
                continue;
            }
            // not a subset. invent new error set type, union of both of them
            err_set_type = get_error_set_union(ira->codegen, errors, err_set_type, cur_type);
            assert(errors != nullptr);
            continue;
        }

        if (prev_type->id == ZigTypeIdErrorUnion && cur_type->id == ZigTypeIdErrorUnion) {
            ZigType *prev_payload_type = prev_type->data.error_union.payload_type;
            ZigType *cur_payload_type = cur_type->data.error_union.payload_type;

            bool const_cast_prev = types_match_const_cast_only(ira, prev_payload_type, cur_payload_type,
                    source_node, false).id == ConstCastResultIdOk;
            bool const_cast_cur = types_match_const_cast_only(ira, cur_payload_type, prev_payload_type,
                    source_node, false).id == ConstCastResultIdOk;

            if (const_cast_prev || const_cast_cur) {
                if (const_cast_cur) {
                    prev_inst = cur_inst;
                }

                ZigType *prev_err_set_type = (err_set_type == nullptr) ? prev_type->data.error_union.err_set_type : err_set_type;
                ZigType *cur_err_set_type = cur_type->data.error_union.err_set_type;

                if (!resolve_inferred_error_set(ira->codegen, prev_err_set_type, cur_inst->source_node)) {
                    return ira->codegen->builtin_types.entry_invalid;
                }

                if (!resolve_inferred_error_set(ira->codegen, cur_err_set_type, cur_inst->source_node)) {
                    return ira->codegen->builtin_types.entry_invalid;
                }

                if (type_is_global_error_set(prev_err_set_type) || type_is_global_error_set(cur_err_set_type)) {
                    err_set_type = ira->codegen->builtin_types.entry_global_error_set;
                    continue;
                }

                update_errors_helper(ira->codegen, &errors, &errors_count);

                if (err_set_type == nullptr) {
                    err_set_type = prev_err_set_type;
                    for (uint32_t i = 0; i < prev_err_set_type->data.error_set.err_count; i += 1) {
                        ErrorTableEntry *error_entry = prev_err_set_type->data.error_set.errors[i];
                        assert(errors[error_entry->value] == nullptr);
                        errors[error_entry->value] = error_entry;
                    }
                }
                bool prev_is_superset = true;
                for (uint32_t i = 0; i < cur_err_set_type->data.error_set.err_count; i += 1) {
                    ErrorTableEntry *contained_error_entry = cur_err_set_type->data.error_set.errors[i];
                    ErrorTableEntry *error_entry = errors[contained_error_entry->value];
                    if (error_entry == nullptr) {
                        prev_is_superset = false;
                        break;
                    }
                }
                if (prev_is_superset) {
                    continue;
                }
                // unset all the errors
                for (uint32_t i = 0; i < err_set_type->data.error_set.err_count; i += 1) {
                    ErrorTableEntry *error_entry = err_set_type->data.error_set.errors[i];
                    errors[error_entry->value] = nullptr;
                }
                for (uint32_t i = 0, count = ira->codegen->errors_by_index.length; i < count; i += 1) {
                    assert(errors[i] == nullptr);
                }
                for (uint32_t i = 0; i < cur_err_set_type->data.error_set.err_count; i += 1) {
                    ErrorTableEntry *error_entry = cur_err_set_type->data.error_set.errors[i];
                    assert(errors[error_entry->value] == nullptr);
                    errors[error_entry->value] = error_entry;
                }
                bool cur_is_superset = true;
                for (uint32_t i = 0; i < prev_err_set_type->data.error_set.err_count; i += 1) {
                    ErrorTableEntry *contained_error_entry = prev_err_set_type->data.error_set.errors[i];
                    ErrorTableEntry *error_entry = errors[contained_error_entry->value];
                    if (error_entry == nullptr) {
                        cur_is_superset = false;
                        break;
                    }
                }
                if (cur_is_superset) {
                    err_set_type = cur_err_set_type;
                    continue;
                }

                err_set_type = get_error_set_union(ira->codegen, errors, cur_err_set_type, prev_err_set_type);
                continue;
            }
        }

        if (prev_type->id == ZigTypeIdNull) {
            prev_inst = cur_inst;
            continue;
        }

        if (cur_type->id == ZigTypeIdNull) {
            any_are_null = true;
            continue;
        }

        if (prev_type->id == ZigTypeIdPointer && prev_type->data.pointer.ptr_len == PtrLenC &&
            (cur_type->id == ZigTypeIdComptimeInt || cur_type->id == ZigTypeIdInt))
        {
            continue;
        }

        if (cur_type->id == ZigTypeIdPointer && cur_type->data.pointer.ptr_len == PtrLenC &&
            (prev_type->id == ZigTypeIdComptimeInt || prev_type->id == ZigTypeIdInt))
        {
            prev_inst = cur_inst;
            continue;
        }

        if (prev_type->id == ZigTypeIdPointer && cur_type->id == ZigTypeIdPointer) {
            if (prev_type->data.pointer.ptr_len == PtrLenC &&
                types_match_const_cast_only(ira, prev_type->data.pointer.child_type,
                    cur_type->data.pointer.child_type, source_node,
                    !prev_type->data.pointer.is_const).id == ConstCastResultIdOk)
            {
                continue;
            }
            if (cur_type->data.pointer.ptr_len == PtrLenC &&
                types_match_const_cast_only(ira, cur_type->data.pointer.child_type,
                    prev_type->data.pointer.child_type, source_node,
                    !cur_type->data.pointer.is_const).id == ConstCastResultIdOk)
            {
                prev_inst = cur_inst;
                continue;
            }
        }

        if (types_match_const_cast_only(ira, prev_type, cur_type, source_node, false).id == ConstCastResultIdOk) {
            continue;
        }

        if (types_match_const_cast_only(ira, cur_type, prev_type, source_node, false).id == ConstCastResultIdOk) {
            prev_inst = cur_inst;
            continue;
        }

        if (prev_type->id == ZigTypeIdInt &&
                   cur_type->id == ZigTypeIdInt &&
                   prev_type->data.integral.is_signed == cur_type->data.integral.is_signed)
        {
            if (cur_type->data.integral.bit_count > prev_type->data.integral.bit_count) {
                prev_inst = cur_inst;
            }
            continue;
        }

        if (prev_type->id == ZigTypeIdFloat && cur_type->id == ZigTypeIdFloat) {
            if (cur_type->data.floating.bit_count > prev_type->data.floating.bit_count) {
                prev_inst = cur_inst;
            }
            continue;
        }

        if (prev_type->id == ZigTypeIdErrorUnion &&
            types_match_const_cast_only(ira, prev_type->data.error_union.payload_type, cur_type,
                source_node, false).id == ConstCastResultIdOk)
        {
            continue;
        }

        if (cur_type->id == ZigTypeIdErrorUnion &&
            types_match_const_cast_only(ira, cur_type->data.error_union.payload_type, prev_type,
                source_node, false).id == ConstCastResultIdOk)
        {
            if (err_set_type != nullptr) {
                ZigType *cur_err_set_type = cur_type->data.error_union.err_set_type;
                if (!resolve_inferred_error_set(ira->codegen, cur_err_set_type, cur_inst->source_node)) {
                    return ira->codegen->builtin_types.entry_invalid;
                }
                if (type_is_global_error_set(cur_err_set_type) || type_is_global_error_set(err_set_type)) {
                    err_set_type = ira->codegen->builtin_types.entry_global_error_set;
                    prev_inst = cur_inst;
                    continue;
                }

                update_errors_helper(ira->codegen, &errors, &errors_count);

                err_set_type = get_error_set_union(ira->codegen, errors, err_set_type, cur_err_set_type);
            }
            prev_inst = cur_inst;
            continue;
        }

        if (prev_type->id == ZigTypeIdOptional &&
            types_match_const_cast_only(ira, prev_type->data.maybe.child_type, cur_type,
                source_node, false).id == ConstCastResultIdOk)
        {
            continue;
        }

        if (cur_type->id == ZigTypeIdOptional &&
            types_match_const_cast_only(ira, cur_type->data.maybe.child_type, prev_type,
                source_node, false).id == ConstCastResultIdOk)
        {
            prev_inst = cur_inst;
            continue;
        }

        if (cur_type->id == ZigTypeIdUndefined) {
            continue;
        }

        if (prev_type->id == ZigTypeIdUndefined) {
            prev_inst = cur_inst;
            continue;
        }

        if (prev_type->id == ZigTypeIdComptimeInt ||
                    prev_type->id == ZigTypeIdComptimeFloat)
        {
            if (ir_num_lit_fits_in_other_type(ira, prev_inst, cur_type, false)) {
                prev_inst = cur_inst;
                continue;
            } else {
                return ira->codegen->builtin_types.entry_invalid;
            }
        }

        if (cur_type->id == ZigTypeIdComptimeInt ||
                   cur_type->id == ZigTypeIdComptimeFloat)
        {
            if (ir_num_lit_fits_in_other_type(ira, cur_inst, prev_type, false)) {
                continue;
            } else {
                return ira->codegen->builtin_types.entry_invalid;
            }
        }

        if (cur_type->id == ZigTypeIdArray && prev_type->id == ZigTypeIdArray &&
            cur_type->data.array.len != prev_type->data.array.len &&
            types_match_const_cast_only(ira, cur_type->data.array.child_type, prev_type->data.array.child_type,
                source_node, false).id == ConstCastResultIdOk)
        {
            convert_to_const_slice = true;
            prev_inst = cur_inst;
            continue;
        }

        if (cur_type->id == ZigTypeIdArray && prev_type->id == ZigTypeIdArray &&
            cur_type->data.array.len != prev_type->data.array.len &&
            types_match_const_cast_only(ira, prev_type->data.array.child_type, cur_type->data.array.child_type,
                source_node, false).id == ConstCastResultIdOk)
        {
            convert_to_const_slice = true;
            continue;
        }

        if (cur_type->id == ZigTypeIdArray && is_slice(prev_type) &&
            (prev_type->data.structure.fields[slice_ptr_index].type_entry->data.pointer.is_const ||
            cur_type->data.array.len == 0) &&
            types_match_const_cast_only(ira,
                prev_type->data.structure.fields[slice_ptr_index].type_entry->data.pointer.child_type,
                cur_type->data.array.child_type, source_node, false).id == ConstCastResultIdOk)
        {
            convert_to_const_slice = false;
            continue;
        }

        if (prev_type->id == ZigTypeIdArray && is_slice(cur_type) &&
            (cur_type->data.structure.fields[slice_ptr_index].type_entry->data.pointer.is_const ||
            prev_type->data.array.len == 0) &&
            types_match_const_cast_only(ira,
                cur_type->data.structure.fields[slice_ptr_index].type_entry->data.pointer.child_type,
                prev_type->data.array.child_type, source_node, false).id == ConstCastResultIdOk)
        {
            prev_inst = cur_inst;
            convert_to_const_slice = false;
            continue;
        }

        if (prev_type->id == ZigTypeIdEnum && cur_type->id == ZigTypeIdUnion &&
            (cur_type->data.unionation.decl_node->data.container_decl.auto_enum || cur_type->data.unionation.decl_node->data.container_decl.init_arg_expr != nullptr))
        {
            if ((err = type_resolve(ira->codegen, cur_type, ResolveStatusZeroBitsKnown)))
                return ira->codegen->builtin_types.entry_invalid;
            if (cur_type->data.unionation.tag_type == prev_type) {
                continue;
            }
        }

        if (cur_type->id == ZigTypeIdEnum && prev_type->id == ZigTypeIdUnion &&
            (prev_type->data.unionation.decl_node->data.container_decl.auto_enum || prev_type->data.unionation.decl_node->data.container_decl.init_arg_expr != nullptr))
        {
            if ((err = type_resolve(ira->codegen, prev_type, ResolveStatusZeroBitsKnown)))
                return ira->codegen->builtin_types.entry_invalid;
            if (prev_type->data.unionation.tag_type == cur_type) {
                prev_inst = cur_inst;
                continue;
            }
        }

        ErrorMsg *msg = ir_add_error_node(ira, source_node,
            buf_sprintf("incompatible types: '%s' and '%s'",
                buf_ptr(&prev_type->name), buf_ptr(&cur_type->name)));
        add_error_note(ira->codegen, msg, prev_inst->source_node,
            buf_sprintf("type '%s' here", buf_ptr(&prev_type->name)));
        add_error_note(ira->codegen, msg, cur_inst->source_node,
            buf_sprintf("type '%s' here", buf_ptr(&cur_type->name)));

        return ira->codegen->builtin_types.entry_invalid;
    }

    free(errors);

    if (convert_to_const_slice) {
        assert(prev_inst->value.type->id == ZigTypeIdArray);
        ZigType *ptr_type = get_pointer_to_type_extra(
                ira->codegen, prev_inst->value.type->data.array.child_type,
                true, false, PtrLenUnknown,
                0, 0, 0);
        ZigType *slice_type = get_slice_type(ira->codegen, ptr_type);
        if (err_set_type != nullptr) {
            return get_error_union_type(ira->codegen, err_set_type, slice_type);
        } else {
            return slice_type;
        }
    } else if (err_set_type != nullptr) {
        if (prev_inst->value.type->id == ZigTypeIdErrorSet) {
            return err_set_type;
        } else if (prev_inst->value.type->id == ZigTypeIdErrorUnion) {
            ZigType *payload_type = prev_inst->value.type->data.error_union.payload_type;
            if ((err = type_resolve(ira->codegen, payload_type, ResolveStatusSizeKnown)))
                return ira->codegen->builtin_types.entry_invalid;
            return get_error_union_type(ira->codegen, err_set_type, payload_type);
        } else if (expected_type != nullptr && expected_type->id == ZigTypeIdErrorUnion) {
            ZigType *payload_type = expected_type->data.error_union.payload_type;
            if ((err = type_resolve(ira->codegen, payload_type, ResolveStatusSizeKnown)))
                return ira->codegen->builtin_types.entry_invalid;
            return get_error_union_type(ira->codegen, err_set_type, payload_type);
        } else {
            if (prev_inst->value.type->id == ZigTypeIdComptimeInt ||
                prev_inst->value.type->id == ZigTypeIdComptimeFloat)
            {
                ir_add_error_node(ira, source_node,
                    buf_sprintf("unable to make error union out of number literal"));
                return ira->codegen->builtin_types.entry_invalid;
            } else if (prev_inst->value.type->id == ZigTypeIdNull) {
                ir_add_error_node(ira, source_node,
                    buf_sprintf("unable to make error union out of null literal"));
                return ira->codegen->builtin_types.entry_invalid;
            } else {
                if ((err = type_resolve(ira->codegen, prev_inst->value.type, ResolveStatusSizeKnown)))
                    return ira->codegen->builtin_types.entry_invalid;
                return get_error_union_type(ira->codegen, err_set_type, prev_inst->value.type);
            }
        }
    } else if (any_are_null && prev_inst->value.type->id != ZigTypeIdNull) {
        if (prev_inst->value.type->id == ZigTypeIdComptimeInt ||
            prev_inst->value.type->id == ZigTypeIdComptimeFloat)
        {
            ir_add_error_node(ira, source_node,
                buf_sprintf("unable to make maybe out of number literal"));
            return ira->codegen->builtin_types.entry_invalid;
        } else if (prev_inst->value.type->id == ZigTypeIdOptional) {
            return prev_inst->value.type;
        } else {
            return get_optional_type(ira->codegen, prev_inst->value.type);
        }
    } else {
        return prev_inst->value.type;
    }
}

static void ir_add_alloca(IrAnalyze *ira, IrInstruction *instruction, ZigType *type_entry) {
    if (type_has_bits(type_entry) && handle_is_ptr(type_entry)) {
        ZigFn *fn_entry = exec_fn_entry(ira->new_irb.exec);
        if (fn_entry != nullptr) {
            fn_entry->alloca_list.append(instruction);
        }
    }
}

static void copy_const_val(ConstExprValue *dest, ConstExprValue *src, bool same_global_refs) {
    ConstGlobalRefs *global_refs = dest->global_refs;
    *dest = *src;
    if (!same_global_refs) {
        dest->global_refs = global_refs;
        if (dest->type->id == ZigTypeIdStruct) {
            dest->data.x_struct.fields = allocate_nonzero<ConstExprValue>(dest->type->data.structure.src_field_count);
            memcpy(dest->data.x_struct.fields, src->data.x_struct.fields, sizeof(ConstExprValue) * dest->type->data.structure.src_field_count);
        }
    }
}

static bool eval_const_expr_implicit_cast(IrAnalyze *ira, IrInstruction *source_instr,
        CastOp cast_op,
        ConstExprValue *other_val, ZigType *other_type,
        ConstExprValue *const_val, ZigType *new_type)
{
    const_val->special = other_val->special;

    assert(other_val != const_val);
    switch (cast_op) {
        case CastOpNoCast:
            zig_unreachable();
        case CastOpErrSet:
        case CastOpBitCast:
        case CastOpPtrOfArrayToSlice:
            zig_panic("TODO");
        case CastOpNoop:
            {
                bool same_global_refs = other_val->special == ConstValSpecialStatic;
                copy_const_val(const_val, other_val, same_global_refs);
                const_val->type = new_type;
                break;
            }
        case CastOpNumLitToConcrete:
            if (other_val->type->id == ZigTypeIdComptimeFloat) {
                assert(new_type->id == ZigTypeIdFloat);
                switch (new_type->data.floating.bit_count) {
                    case 16:
                        const_val->data.x_f16 = bigfloat_to_f16(&other_val->data.x_bigfloat);
                        break;
                    case 32:
                        const_val->data.x_f32 = bigfloat_to_f32(&other_val->data.x_bigfloat);
                        break;
                    case 64:
                        const_val->data.x_f64 = bigfloat_to_f64(&other_val->data.x_bigfloat);
                        break;
                    case 128:
                        const_val->data.x_f128 = bigfloat_to_f128(&other_val->data.x_bigfloat);
                        break;
                    default:
                        zig_unreachable();
                }
            } else if (other_val->type->id == ZigTypeIdComptimeInt) {
                bigint_init_bigint(&const_val->data.x_bigint, &other_val->data.x_bigint);
            } else {
                zig_unreachable();
            }
            const_val->type = new_type;
            break;
        case CastOpIntToFloat:
            {
                assert(new_type->id == ZigTypeIdFloat);

                BigFloat bigfloat;
                bigfloat_init_bigint(&bigfloat, &other_val->data.x_bigint);
                switch (new_type->data.floating.bit_count) {
                    case 16:
                        const_val->data.x_f16 = bigfloat_to_f16(&bigfloat);
                        break;
                    case 32:
                        const_val->data.x_f32 = bigfloat_to_f32(&bigfloat);
                        break;
                    case 64:
                        const_val->data.x_f64 = bigfloat_to_f64(&bigfloat);
                        break;
                    case 128:
                        const_val->data.x_f128 = bigfloat_to_f128(&bigfloat);
                        break;
                    default:
                        zig_unreachable();
                }
                const_val->special = ConstValSpecialStatic;
                break;
            }
        case CastOpFloatToInt:
            float_init_bigint(&const_val->data.x_bigint, other_val);
            if (new_type->id == ZigTypeIdInt) {
                if (!bigint_fits_in_bits(&const_val->data.x_bigint, new_type->data.integral.bit_count,
                    new_type->data.integral.is_signed))
                {
                    Buf *int_buf = buf_alloc();
                    bigint_append_buf(int_buf, &const_val->data.x_bigint, 10);

                    ir_add_error(ira, source_instr,
                        buf_sprintf("integer value '%s' cannot be stored in type '%s'",
                            buf_ptr(int_buf), buf_ptr(&new_type->name)));
                    return false;
                }
            }

            const_val->special = ConstValSpecialStatic;
            break;
        case CastOpBoolToInt:
            bigint_init_unsigned(&const_val->data.x_bigint, other_val->data.x_bool ? 1 : 0);
            const_val->special = ConstValSpecialStatic;
            break;
    }
    return true;
}

static IrInstruction *ir_const(IrAnalyze *ira, IrInstruction *old_instruction, ZigType *ty) {
    IrInstructionConst *const_instruction = ir_create_instruction<IrInstructionConst>(&ira->new_irb,
            old_instruction->scope, old_instruction->source_node);
    IrInstruction *new_instruction = &const_instruction->base;
    new_instruction->value.type = ty;
    new_instruction->value.special = ConstValSpecialStatic;
    return new_instruction;
}

static IrInstruction *ir_resolve_cast(IrAnalyze *ira, IrInstruction *source_instr, IrInstruction *value,
        ZigType *wanted_type, CastOp cast_op, bool need_alloca)
{
    if (instr_is_comptime(value) || !type_has_bits(wanted_type)) {
        IrInstruction *result = ir_const(ira, source_instr, wanted_type);
        if (!eval_const_expr_implicit_cast(ira, source_instr, cast_op, &value->value, value->value.type,
            &result->value, wanted_type))
        {
            return ira->codegen->invalid_instruction;
        }
        return result;
    } else {
        IrInstruction *result = ir_build_cast(&ira->new_irb, source_instr->scope, source_instr->source_node, wanted_type, value, cast_op);
        result->value.type = wanted_type;
        if (need_alloca) {
            ir_add_alloca(ira, result, wanted_type);
        }
        return result;
    }
}

static IrInstruction *ir_resolve_ptr_of_array_to_unknown_len_ptr(IrAnalyze *ira, IrInstruction *source_instr,
        IrInstruction *value, ZigType *wanted_type)
{
    assert(value->value.type->id == ZigTypeIdPointer);

    Error err;

    if ((err = type_resolve(ira->codegen, value->value.type->data.pointer.child_type,
                    ResolveStatusAlignmentKnown)))
    {
        return ira->codegen->invalid_instruction;
    }

    wanted_type = adjust_ptr_align(ira->codegen, wanted_type, get_ptr_align(ira->codegen, value->value.type));

    if (instr_is_comptime(value)) {
        ConstExprValue *pointee = const_ptr_pointee(ira, ira->codegen, &value->value, source_instr->source_node);
        if (pointee == nullptr)
            return ira->codegen->invalid_instruction;
        if (pointee->special != ConstValSpecialRuntime) {
            IrInstruction *result = ir_const(ira, source_instr, wanted_type);
            result->value.data.x_ptr.special = ConstPtrSpecialBaseArray;
            result->value.data.x_ptr.mut = value->value.data.x_ptr.mut;
            result->value.data.x_ptr.data.base_array.array_val = pointee;
            result->value.data.x_ptr.data.base_array.elem_index = 0;
            result->value.data.x_ptr.data.base_array.is_cstr = false;
            return result;
        }
    }

    IrInstruction *result = ir_build_cast(&ira->new_irb, source_instr->scope, source_instr->source_node,
            wanted_type, value, CastOpBitCast);
    result->value.type = wanted_type;
    return result;
}

static IrInstruction *ir_resolve_ptr_of_array_to_slice(IrAnalyze *ira, IrInstruction *source_instr,
        IrInstruction *value, ZigType *wanted_type)
{
    Error err;

    if ((err = type_resolve(ira->codegen, value->value.type->data.pointer.child_type,
                    ResolveStatusAlignmentKnown)))
    {
        return ira->codegen->invalid_instruction;
    }

    wanted_type = adjust_slice_align(ira->codegen, wanted_type, get_ptr_align(ira->codegen, value->value.type));

    if (instr_is_comptime(value)) {
        ConstExprValue *pointee = const_ptr_pointee(ira, ira->codegen, &value->value, source_instr->source_node);
        if (pointee == nullptr)
            return ira->codegen->invalid_instruction;
        if (pointee->special != ConstValSpecialRuntime) {
            assert(value->value.type->id == ZigTypeIdPointer);
            ZigType *array_type = value->value.type->data.pointer.child_type;
            assert(is_slice(wanted_type));
            bool is_const = wanted_type->data.structure.fields[slice_ptr_index].type_entry->data.pointer.is_const;

            IrInstruction *result = ir_const(ira, source_instr, wanted_type);
            init_const_slice(ira->codegen, &result->value, pointee, 0, array_type->data.array.len, is_const);
            result->value.data.x_struct.fields[slice_ptr_index].data.x_ptr.mut =
                value->value.data.x_ptr.mut;
            result->value.type = wanted_type;
            return result;
        }
    }

    IrInstruction *result = ir_build_cast(&ira->new_irb, source_instr->scope, source_instr->source_node,
            wanted_type, value, CastOpPtrOfArrayToSlice);
    result->value.type = wanted_type;
    ir_add_alloca(ira, result, wanted_type);
    return result;
}

static bool is_container(ZigType *type) {
    return type->id == ZigTypeIdStruct ||
        type->id == ZigTypeIdEnum ||
        type->id == ZigTypeIdUnion;
}

static IrBasicBlock *ir_get_new_bb(IrAnalyze *ira, IrBasicBlock *old_bb, IrInstruction *ref_old_instruction) {
    assert(old_bb);

    if (old_bb->other) {
        if (ref_old_instruction == nullptr || old_bb->other->ref_instruction != ref_old_instruction) {
            return old_bb->other;
        }
    }

    IrBasicBlock *new_bb = ir_build_bb_from(&ira->new_irb, old_bb);
    new_bb->ref_instruction = ref_old_instruction;

    return new_bb;
}

static IrBasicBlock *ir_get_new_bb_runtime(IrAnalyze *ira, IrBasicBlock *old_bb, IrInstruction *ref_old_instruction) {
    assert(ref_old_instruction != nullptr);
    IrBasicBlock *new_bb = ir_get_new_bb(ira, old_bb, ref_old_instruction);
    if (new_bb->must_be_comptime_source_instr) {
        ErrorMsg *msg = ir_add_error(ira, ref_old_instruction,
            buf_sprintf("control flow attempts to use compile-time variable at runtime"));
        add_error_note(ira->codegen, msg, new_bb->must_be_comptime_source_instr->source_node,
                buf_sprintf("compile-time variable assigned here"));
        return nullptr;
    }
    return new_bb;
}

static void ir_start_bb(IrAnalyze *ira, IrBasicBlock *old_bb, IrBasicBlock *const_predecessor_bb) {
    ira->instruction_index = 0;
    ira->old_irb.current_basic_block = old_bb;
    ira->const_predecessor_bb = const_predecessor_bb;
}

static void ir_finish_bb(IrAnalyze *ira) {
    ira->new_irb.exec->basic_block_list.append(ira->new_irb.current_basic_block);
    ira->instruction_index += 1;
    while (ira->instruction_index < ira->old_irb.current_basic_block->instruction_list.length) {
        IrInstruction *next_instruction = ira->old_irb.current_basic_block->instruction_list.at(ira->instruction_index);
        if (!next_instruction->is_gen) {
            ir_add_error(ira, next_instruction, buf_sprintf("unreachable code"));
            break;
        }
        ira->instruction_index += 1;
    }

    ira->old_bb_index += 1;

    bool need_repeat = true;
    for (;;) {
        while (ira->old_bb_index < ira->old_irb.exec->basic_block_list.length) {
            IrBasicBlock *old_bb = ira->old_irb.exec->basic_block_list.at(ira->old_bb_index);
            if (old_bb->other == nullptr) {
                ira->old_bb_index += 1;
                continue;
            }
            if (old_bb->other->instruction_list.length != 0) {
                ira->old_bb_index += 1;
                continue;
            }
            ira->new_irb.current_basic_block = old_bb->other;

            ir_start_bb(ira, old_bb, nullptr);
            return;
        }
        if (!need_repeat)
            return;
        need_repeat = false;
        ira->old_bb_index = 0;
        continue;
    }
}

static IrInstruction *ir_unreach_error(IrAnalyze *ira) {
    ira->old_bb_index = SIZE_MAX;
    ira->new_irb.exec->invalid = true;
    return ira->codegen->unreach_instruction;
}

static bool ir_emit_backward_branch(IrAnalyze *ira, IrInstruction *source_instruction) {
    size_t *bbc = ira->new_irb.exec->backward_branch_count;
    size_t quota = ira->new_irb.exec->backward_branch_quota;

    // If we're already over quota, we've already given an error message for this.
    if (*bbc > quota) {
        return false;
    }

    *bbc += 1;
    if (*bbc > quota) {
        ir_add_error(ira, source_instruction, buf_sprintf("evaluation exceeded %" ZIG_PRI_usize " backwards branches", quota));
        return false;
    }
    return true;
}

static IrInstruction *ir_inline_bb(IrAnalyze *ira, IrInstruction *source_instruction, IrBasicBlock *old_bb) {
    if (old_bb->debug_id <= ira->old_irb.current_basic_block->debug_id) {
        if (!ir_emit_backward_branch(ira, source_instruction))
            return ir_unreach_error(ira);
    }

    old_bb->other = ira->old_irb.current_basic_block->other;
    ir_start_bb(ira, old_bb, ira->old_irb.current_basic_block);
    return ira->codegen->unreach_instruction;
}

static IrInstruction *ir_finish_anal(IrAnalyze *ira, IrInstruction *instruction) {
    if (instruction->value.type->id == ZigTypeIdUnreachable)
        ir_finish_bb(ira);
    return instruction;
}

static IrInstruction *ir_const_type(IrAnalyze *ira, IrInstruction *source_instruction, ZigType *ty) {
    IrInstruction *result = ir_const(ira, source_instruction, ira->codegen->builtin_types.entry_type);
    result->value.data.x_type = ty;
    return result;
}

static IrInstruction *ir_const_bool(IrAnalyze *ira, IrInstruction *source_instruction, bool value) {
    IrInstruction *result = ir_const(ira, source_instruction, ira->codegen->builtin_types.entry_bool);
    result->value.data.x_bool = value;
    return result;
}

static IrInstruction *ir_const_void(IrAnalyze *ira, IrInstruction *source_instruction) {
    return ir_const(ira, source_instruction, ira->codegen->builtin_types.entry_void);
}

static IrInstruction *ir_const_unsigned(IrAnalyze *ira, IrInstruction *source_instruction, uint64_t value) {
    IrInstruction *result = ir_const(ira, source_instruction, ira->codegen->builtin_types.entry_num_lit_int);
    bigint_init_unsigned(&result->value.data.x_bigint, value);
    return result;
}

static IrInstruction *ir_get_const_ptr(IrAnalyze *ira, IrInstruction *instruction,
        ConstExprValue *pointee, ZigType *pointee_type,
        ConstPtrMut ptr_mut, bool ptr_is_const, bool ptr_is_volatile, uint32_t ptr_align)
{
    ZigType *ptr_type = get_pointer_to_type_extra(ira->codegen, pointee_type,
            ptr_is_const, ptr_is_volatile, PtrLenSingle, ptr_align, 0, 0);
    IrInstruction *const_instr = ir_const(ira, instruction, ptr_type);
    ConstExprValue *const_val = &const_instr->value;
    const_val->data.x_ptr.special = ConstPtrSpecialRef;
    const_val->data.x_ptr.mut = ptr_mut;
    const_val->data.x_ptr.data.ref.pointee = pointee;
    return const_instr;
}

static ConstExprValue *ir_resolve_const(IrAnalyze *ira, IrInstruction *value, UndefAllowed undef_allowed) {
    switch (value->value.special) {
        case ConstValSpecialStatic:
            return &value->value;
        case ConstValSpecialRuntime:
            if (!type_has_bits(value->value.type)) {
                return &value->value;
            }
            ir_add_error(ira, value, buf_sprintf("unable to evaluate constant expression"));
            return nullptr;
        case ConstValSpecialUndef:
            if (undef_allowed == UndefOk) {
                return &value->value;
            } else {
                ir_add_error(ira, value, buf_sprintf("use of undefined value here causes undefined behavior"));
                return nullptr;
            }
    }
    zig_unreachable();
}

ConstExprValue *ir_eval_const_value(CodeGen *codegen, Scope *scope, AstNode *node,
        ZigType *expected_type, size_t *backward_branch_count, size_t backward_branch_quota,
        ZigFn *fn_entry, Buf *c_import_buf, AstNode *source_node, Buf *exec_name,
        IrExecutable *parent_exec, AstNode *expected_type_source_node)
{
    if (expected_type != nullptr && type_is_invalid(expected_type))
        return &codegen->invalid_instruction->value;

    IrExecutable *ir_executable = allocate<IrExecutable>(1);
    ir_executable->source_node = source_node;
    ir_executable->parent_exec = parent_exec;
    ir_executable->name = exec_name;
    ir_executable->is_inline = true;
    ir_executable->fn_entry = fn_entry;
    ir_executable->c_import_buf = c_import_buf;
    ir_executable->begin_scope = scope;
    ir_gen(codegen, node, scope, ir_executable);

    if (ir_executable->invalid)
        return &codegen->invalid_instruction->value;

    if (codegen->verbose_ir) {
        fprintf(stderr, "\nSource: ");
        ast_render(codegen, stderr, node, 4);
        fprintf(stderr, "\n{ // (IR)\n");
        ir_print(codegen, stderr, ir_executable, 2);
        fprintf(stderr, "}\n");
    }
    IrExecutable *analyzed_executable = allocate<IrExecutable>(1);
    analyzed_executable->source_node = source_node;
    analyzed_executable->parent_exec = parent_exec;
    analyzed_executable->source_exec = ir_executable;
    analyzed_executable->name = exec_name;
    analyzed_executable->is_inline = true;
    analyzed_executable->fn_entry = fn_entry;
    analyzed_executable->c_import_buf = c_import_buf;
    analyzed_executable->backward_branch_count = backward_branch_count;
    analyzed_executable->backward_branch_quota = backward_branch_quota;
    analyzed_executable->begin_scope = scope;
    ZigType *result_type = ir_analyze(codegen, ir_executable, analyzed_executable, expected_type, expected_type_source_node);
    if (type_is_invalid(result_type))
        return &codegen->invalid_instruction->value;

    if (codegen->verbose_ir) {
        fprintf(stderr, "{ // (analyzed)\n");
        ir_print(codegen, stderr, analyzed_executable, 2);
        fprintf(stderr, "}\n");
    }

    return ir_exec_const_result(codegen, analyzed_executable);
}

static ZigType *ir_resolve_type(IrAnalyze *ira, IrInstruction *type_value) {
    if (type_is_invalid(type_value->value.type))
        return ira->codegen->builtin_types.entry_invalid;

    if (type_value->value.type->id != ZigTypeIdMetaType) {
        ir_add_error(ira, type_value,
                buf_sprintf("expected type 'type', found '%s'", buf_ptr(&type_value->value.type->name)));
        return ira->codegen->builtin_types.entry_invalid;
    }

    ConstExprValue *const_val = ir_resolve_const(ira, type_value, UndefBad);
    if (!const_val)
        return ira->codegen->builtin_types.entry_invalid;

    assert(const_val->data.x_type != nullptr);
    return const_val->data.x_type;
}

static ZigType *ir_resolve_error_set_type(IrAnalyze *ira, IrInstruction *op_source, IrInstruction *type_value) {
    if (type_is_invalid(type_value->value.type))
        return ira->codegen->builtin_types.entry_invalid;

    if (type_value->value.type->id != ZigTypeIdMetaType) {
        ErrorMsg *msg = ir_add_error(ira, type_value,
                buf_sprintf("expected error set type, found '%s'", buf_ptr(&type_value->value.type->name)));
        add_error_note(ira->codegen, msg, op_source->source_node,
                buf_sprintf("`||` merges error sets; `or` performs boolean OR"));
        return ira->codegen->builtin_types.entry_invalid;
    }

    ConstExprValue *const_val = ir_resolve_const(ira, type_value, UndefBad);
    if (!const_val)
        return ira->codegen->builtin_types.entry_invalid;

    assert(const_val->data.x_type != nullptr);
    ZigType *result_type = const_val->data.x_type;
    if (result_type->id != ZigTypeIdErrorSet) {
        ErrorMsg *msg = ir_add_error(ira, type_value,
                buf_sprintf("expected error set type, found type '%s'", buf_ptr(&result_type->name)));
        add_error_note(ira->codegen, msg, op_source->source_node,
                buf_sprintf("`||` merges error sets; `or` performs boolean OR"));
        return ira->codegen->builtin_types.entry_invalid;
    }
    return result_type;
}

static ZigFn *ir_resolve_fn(IrAnalyze *ira, IrInstruction *fn_value) {
    if (fn_value == ira->codegen->invalid_instruction)
        return nullptr;

    if (type_is_invalid(fn_value->value.type))
        return nullptr;

    if (fn_value->value.type->id != ZigTypeIdFn) {
        ir_add_error_node(ira, fn_value->source_node,
                buf_sprintf("expected function type, found '%s'", buf_ptr(&fn_value->value.type->name)));
        return nullptr;
    }

    ConstExprValue *const_val = ir_resolve_const(ira, fn_value, UndefBad);
    if (!const_val)
        return nullptr;

    assert(const_val->data.x_ptr.special == ConstPtrSpecialFunction);
    return const_val->data.x_ptr.data.fn.fn_entry;
}

static IrInstruction *ir_analyze_optional_wrap(IrAnalyze *ira, IrInstruction *source_instr, IrInstruction *value,
        ZigType *wanted_type)
{
    assert(wanted_type->id == ZigTypeIdOptional);

    if (instr_is_comptime(value)) {
        ZigType *payload_type = wanted_type->data.maybe.child_type;
        IrInstruction *casted_payload = ir_implicit_cast(ira, value, payload_type);
        if (type_is_invalid(casted_payload->value.type))
            return ira->codegen->invalid_instruction;

        ConstExprValue *val = ir_resolve_const(ira, casted_payload, UndefOk);
        if (!val)
            return ira->codegen->invalid_instruction;

        IrInstructionConst *const_instruction = ir_create_instruction<IrInstructionConst>(&ira->new_irb,
                source_instr->scope, source_instr->source_node);
        const_instruction->base.value.special = ConstValSpecialStatic;
        if (types_have_same_zig_comptime_repr(wanted_type, payload_type)) {
            copy_const_val(&const_instruction->base.value, val, val->data.x_ptr.mut == ConstPtrMutComptimeConst);
        } else {
            const_instruction->base.value.data.x_optional = val;
        }
        const_instruction->base.value.type = wanted_type;
        return &const_instruction->base;
    }

    IrInstruction *result = ir_build_maybe_wrap(&ira->new_irb, source_instr->scope, source_instr->source_node, value);
    result->value.type = wanted_type;
    result->value.data.rh_maybe = RuntimeHintOptionalNonNull;
    ir_add_alloca(ira, result, wanted_type);
    return result;
}

static IrInstruction *ir_analyze_err_wrap_payload(IrAnalyze *ira, IrInstruction *source_instr,
        IrInstruction *value, ZigType *wanted_type)
{
    assert(wanted_type->id == ZigTypeIdErrorUnion);

    if (instr_is_comptime(value)) {
        ZigType *payload_type = wanted_type->data.error_union.payload_type;
        IrInstruction *casted_payload = ir_implicit_cast(ira, value, payload_type);
        if (type_is_invalid(casted_payload->value.type))
            return ira->codegen->invalid_instruction;

        ConstExprValue *val = ir_resolve_const(ira, casted_payload, UndefBad);
        if (!val)
            return ira->codegen->invalid_instruction;

        ConstExprValue *err_set_val = create_const_vals(1);
        err_set_val->type = wanted_type->data.error_union.err_set_type;
        err_set_val->special = ConstValSpecialStatic;
        err_set_val->data.x_err_set = nullptr;

        IrInstructionConst *const_instruction = ir_create_instruction<IrInstructionConst>(&ira->new_irb,
                source_instr->scope, source_instr->source_node);
        const_instruction->base.value.type = wanted_type;
        const_instruction->base.value.special = ConstValSpecialStatic;
        const_instruction->base.value.data.x_err_union.error_set = err_set_val;
        const_instruction->base.value.data.x_err_union.payload = val;
        return &const_instruction->base;
    }

    IrInstruction *result = ir_build_err_wrap_payload(&ira->new_irb, source_instr->scope, source_instr->source_node, value);
    result->value.type = wanted_type;
    result->value.data.rh_error_union = RuntimeHintErrorUnionNonError;
    ir_add_alloca(ira, result, wanted_type);
    return result;
}

static IrInstruction *ir_analyze_err_set_cast(IrAnalyze *ira, IrInstruction *source_instr, IrInstruction *value,
        ZigType *wanted_type)
{
    assert(value->value.type->id == ZigTypeIdErrorSet);
    assert(wanted_type->id == ZigTypeIdErrorSet);

    if (instr_is_comptime(value)) {
        ConstExprValue *val = ir_resolve_const(ira, value, UndefBad);
        if (!val)
            return ira->codegen->invalid_instruction;

        if (!resolve_inferred_error_set(ira->codegen, wanted_type, source_instr->source_node)) {
            return ira->codegen->invalid_instruction;
        }
        if (!type_is_global_error_set(wanted_type)) {
            bool subset = false;
            for (uint32_t i = 0, count = wanted_type->data.error_set.err_count; i < count; i += 1) {
                if (wanted_type->data.error_set.errors[i]->value == val->data.x_err_set->value) {
                    subset = true;
                    break;
                }
            }
            if (!subset) {
                ir_add_error(ira, source_instr,
                    buf_sprintf("error.%s not a member of error set '%s'",
                        buf_ptr(&val->data.x_err_set->name), buf_ptr(&wanted_type->name)));
                return ira->codegen->invalid_instruction;
            }
        }

        IrInstructionConst *const_instruction = ir_create_instruction<IrInstructionConst>(&ira->new_irb,
                source_instr->scope, source_instr->source_node);
        const_instruction->base.value.type = wanted_type;
        const_instruction->base.value.special = ConstValSpecialStatic;
        const_instruction->base.value.data.x_err_set = val->data.x_err_set;
        return &const_instruction->base;
    }

    IrInstruction *result = ir_build_cast(&ira->new_irb, source_instr->scope, source_instr->source_node, wanted_type, value, CastOpErrSet);
    result->value.type = wanted_type;
    return result;
}

static IrInstruction *ir_analyze_err_wrap_code(IrAnalyze *ira, IrInstruction *source_instr, IrInstruction *value, ZigType *wanted_type) {
    assert(wanted_type->id == ZigTypeIdErrorUnion);

    IrInstruction *casted_value = ir_implicit_cast(ira, value, wanted_type->data.error_union.err_set_type);

    if (instr_is_comptime(casted_value)) {
        ConstExprValue *val = ir_resolve_const(ira, casted_value, UndefBad);
        if (!val)
            return ira->codegen->invalid_instruction;

        ConstExprValue *err_set_val = create_const_vals(1);
        err_set_val->special = ConstValSpecialStatic;
        err_set_val->type = wanted_type->data.error_union.err_set_type;
        err_set_val->data.x_err_set = val->data.x_err_set;

        IrInstructionConst *const_instruction = ir_create_instruction<IrInstructionConst>(&ira->new_irb,
                source_instr->scope, source_instr->source_node);
        const_instruction->base.value.type = wanted_type;
        const_instruction->base.value.special = ConstValSpecialStatic;
        const_instruction->base.value.data.x_err_union.error_set = err_set_val;
        const_instruction->base.value.data.x_err_union.payload = nullptr;
        return &const_instruction->base;
    }

    IrInstruction *result = ir_build_err_wrap_code(&ira->new_irb, source_instr->scope, source_instr->source_node, value);
    result->value.type = wanted_type;
    result->value.data.rh_error_union = RuntimeHintErrorUnionError;
    ir_add_alloca(ira, result, wanted_type);
    return result;
}

static IrInstruction *ir_analyze_null_to_maybe(IrAnalyze *ira, IrInstruction *source_instr, IrInstruction *value, ZigType *wanted_type) {
    assert(wanted_type->id == ZigTypeIdOptional);
    assert(instr_is_comptime(value));

    ConstExprValue *val = ir_resolve_const(ira, value, UndefBad);
    assert(val);

    IrInstructionConst *const_instruction = ir_create_instruction<IrInstructionConst>(&ira->new_irb, source_instr->scope, source_instr->source_node);
    const_instruction->base.value.special = ConstValSpecialStatic;
    if (get_codegen_ptr_type(wanted_type) != nullptr) {
        const_instruction->base.value.data.x_ptr.special = ConstPtrSpecialNull;
    } else if (is_opt_err_set(wanted_type)) {
        const_instruction->base.value.data.x_err_set = nullptr;
    } else {
        const_instruction->base.value.data.x_optional = nullptr;
    }
    const_instruction->base.value.type = wanted_type;
    return &const_instruction->base;
}

static IrInstruction *ir_get_ref(IrAnalyze *ira, IrInstruction *source_instruction, IrInstruction *value,
        bool is_const, bool is_volatile)
{
    Error err;

    if (type_is_invalid(value->value.type))
        return ira->codegen->invalid_instruction;

    if ((err = type_resolve(ira->codegen, value->value.type, ResolveStatusZeroBitsKnown)))
        return ira->codegen->invalid_instruction;

    if (instr_is_comptime(value)) {
        ConstExprValue *val = ir_resolve_const(ira, value, UndefOk);
        if (!val)
            return ira->codegen->invalid_instruction;
        return ir_get_const_ptr(ira, source_instruction, val, value->value.type,
                ConstPtrMutComptimeConst, is_const, is_volatile, 0);
    }

    ZigType *ptr_type = get_pointer_to_type_extra(ira->codegen, value->value.type,
            is_const, is_volatile, PtrLenSingle, 0, 0, 0);
    IrInstruction *new_instruction = ir_build_ref(&ira->new_irb, source_instruction->scope,
            source_instruction->source_node, value, is_const, is_volatile);
    new_instruction->value.type = ptr_type;
    new_instruction->value.data.rh_ptr = RuntimeHintPtrStack;
    if (type_has_bits(ptr_type) && !handle_is_ptr(value->value.type)) {
        ZigFn *fn_entry = exec_fn_entry(ira->new_irb.exec);
        assert(fn_entry);
        fn_entry->alloca_list.append(new_instruction);
    }
    return new_instruction;
}

static IrInstruction *ir_analyze_array_to_slice(IrAnalyze *ira, IrInstruction *source_instr,
        IrInstruction *array_arg, ZigType *wanted_type)
{
    assert(is_slice(wanted_type));
    // In this function we honor the const-ness of wanted_type, because
    // we may be casting [0]T to []const T which is perfectly valid.

    IrInstruction *array_ptr = nullptr;
    IrInstruction *array;
    if (array_arg->value.type->id == ZigTypeIdPointer) {
        array = ir_get_deref(ira, source_instr, array_arg);
        array_ptr = array_arg;
    } else {
        array = array_arg;
    }
    ZigType *array_type = array->value.type;
    assert(array_type->id == ZigTypeIdArray);

    if (instr_is_comptime(array) || array_type->data.array.len == 0) {
        IrInstruction *result = ir_const(ira, source_instr, wanted_type);
        init_const_slice(ira->codegen, &result->value, &array->value, 0, array_type->data.array.len, true);
        result->value.type = wanted_type;
        return result;
    }

    IrInstruction *start = ir_const(ira, source_instr, ira->codegen->builtin_types.entry_usize);
    init_const_usize(ira->codegen, &start->value, 0);

    IrInstruction *end = ir_const(ira, source_instr, ira->codegen->builtin_types.entry_usize);
    init_const_usize(ira->codegen, &end->value, array_type->data.array.len);

    if (!array_ptr) array_ptr = ir_get_ref(ira, source_instr, array, true, false);

    IrInstruction *result = ir_build_slice(&ira->new_irb, source_instr->scope,
            source_instr->source_node, array_ptr, start, end, false);
    result->value.type = wanted_type;
    result->value.data.rh_slice.id = RuntimeHintSliceIdLen;
    result->value.data.rh_slice.len = array_type->data.array.len;
    ir_add_alloca(ira, result, result->value.type);

    return result;
}

static ZigType *ir_resolve_union_tag_type(IrAnalyze *ira, IrInstruction *source_instr, ZigType *union_type) {
    assert(union_type->id == ZigTypeIdUnion);

    Error err;
    if ((err = type_resolve(ira->codegen, union_type, ResolveStatusSizeKnown)))
        return ira->codegen->builtin_types.entry_invalid;

    AstNode *decl_node = union_type->data.unionation.decl_node;
    if (decl_node->data.container_decl.auto_enum || decl_node->data.container_decl.init_arg_expr != nullptr) {
        assert(union_type->data.unionation.tag_type != nullptr);
        return union_type->data.unionation.tag_type;
    } else {
        ErrorMsg *msg = ir_add_error(ira, source_instr, buf_sprintf("union '%s' has no tag",
            buf_ptr(&union_type->name)));
        add_error_note(ira->codegen, msg, decl_node, buf_sprintf("consider 'union(enum)' here"));
        return ira->codegen->builtin_types.entry_invalid;
    }
}

static IrInstruction *ir_analyze_enum_to_int(IrAnalyze *ira, IrInstruction *source_instr, IrInstruction *target) {
    Error err;

    IrInstruction *enum_target;
    ZigType *enum_type;
    if (target->value.type->id == ZigTypeIdUnion) {
        enum_type = ir_resolve_union_tag_type(ira, target, target->value.type);
        if (type_is_invalid(enum_type))
            return ira->codegen->invalid_instruction;
        enum_target = ir_implicit_cast(ira, target, enum_type);
        if (type_is_invalid(enum_target->value.type))
            return ira->codegen->invalid_instruction;
    } else if (target->value.type->id == ZigTypeIdEnum) {
        enum_target = target;
        enum_type = target->value.type;
    } else {
        ir_add_error(ira, target,
            buf_sprintf("expected enum, found type '%s'", buf_ptr(&target->value.type->name)));
        return ira->codegen->invalid_instruction;
    }

    if ((err = type_resolve(ira->codegen, enum_type, ResolveStatusSizeKnown)))
        return ira->codegen->invalid_instruction;

    ZigType *tag_type = enum_type->data.enumeration.tag_int_type;
    assert(tag_type->id == ZigTypeIdInt || tag_type->id == ZigTypeIdComptimeInt);

    if (instr_is_comptime(enum_target)) {
        ConstExprValue *val = ir_resolve_const(ira, enum_target, UndefBad);
        if (!val)
            return ira->codegen->invalid_instruction;
        IrInstruction *result = ir_const(ira, source_instr, tag_type);
        init_const_bigint(&result->value, tag_type, &val->data.x_enum_tag);
        return result;
    }

    // If there is only one possible tag, then we know at comptime what it is.
    if (enum_type->data.enumeration.layout == ContainerLayoutAuto &&
        enum_type->data.enumeration.src_field_count == 1)
    {
        assert(tag_type == ira->codegen->builtin_types.entry_num_lit_int);
        IrInstruction *result = ir_const(ira, source_instr, tag_type);
        init_const_bigint(&result->value, tag_type,
                &enum_type->data.enumeration.fields[0].value);
        return result;
    }

    IrInstruction *result = ir_build_widen_or_shorten(&ira->new_irb, source_instr->scope,
            source_instr->source_node, enum_target);
    result->value.type = tag_type;
    return result;
}

static IrInstruction *ir_analyze_union_to_tag(IrAnalyze *ira, IrInstruction *source_instr,
        IrInstruction *target, ZigType *wanted_type)
{
    assert(target->value.type->id == ZigTypeIdUnion);
    assert(wanted_type->id == ZigTypeIdEnum);
    assert(wanted_type == target->value.type->data.unionation.tag_type);

    if (instr_is_comptime(target)) {
        ConstExprValue *val = ir_resolve_const(ira, target, UndefBad);
        if (!val)
            return ira->codegen->invalid_instruction;
        IrInstruction *result = ir_const(ira, source_instr, wanted_type);
        result->value.special = ConstValSpecialStatic;
        result->value.type = wanted_type;
        bigint_init_bigint(&result->value.data.x_enum_tag, &val->data.x_union.tag);
        return result;
    }

    // If there is only 1 possible tag, then we know at comptime what it is.
    if (wanted_type->data.enumeration.layout == ContainerLayoutAuto &&
        wanted_type->data.enumeration.src_field_count == 1)
    {
        IrInstruction *result = ir_const(ira, source_instr, wanted_type);
        result->value.special = ConstValSpecialStatic;
        result->value.type = wanted_type;
        TypeEnumField *enum_field = target->value.type->data.unionation.fields[0].enum_field;
        bigint_init_bigint(&result->value.data.x_enum_tag, &enum_field->value);
        return result;
    }

    IrInstruction *result = ir_build_union_tag(&ira->new_irb, source_instr->scope,
            source_instr->source_node, target);
    result->value.type = wanted_type;
    return result;
}

static IrInstruction *ir_analyze_undefined_to_anything(IrAnalyze *ira, IrInstruction *source_instr,
        IrInstruction *target, ZigType *wanted_type)
{
    IrInstruction *result = ir_const(ira, source_instr, wanted_type);
    init_const_undefined(ira->codegen, &result->value);
    return result;
}

static IrInstruction *ir_analyze_enum_to_union(IrAnalyze *ira, IrInstruction *source_instr,
        IrInstruction *target, ZigType *wanted_type)
{
    Error err;
    assert(wanted_type->id == ZigTypeIdUnion);
    assert(target->value.type->id == ZigTypeIdEnum);

    if (instr_is_comptime(target)) {
        ConstExprValue *val = ir_resolve_const(ira, target, UndefBad);
        if (!val)
            return ira->codegen->invalid_instruction;
        TypeUnionField *union_field = find_union_field_by_tag(wanted_type, &val->data.x_enum_tag);
        assert(union_field != nullptr);
        if ((err = type_resolve(ira->codegen, union_field->type_entry, ResolveStatusZeroBitsKnown)))
            return ira->codegen->invalid_instruction;
        if (type_has_bits(union_field->type_entry)) {
            AstNode *field_node = wanted_type->data.unionation.decl_node->data.container_decl.fields.at(
                    union_field->enum_field->decl_index);
            ErrorMsg *msg = ir_add_error(ira, source_instr,
                    buf_sprintf("cast to union '%s' must initialize '%s' field '%s'",
                        buf_ptr(&wanted_type->name),
                        buf_ptr(&union_field->type_entry->name),
                        buf_ptr(union_field->name)));
            add_error_note(ira->codegen, msg, field_node,
                    buf_sprintf("field '%s' declared here", buf_ptr(union_field->name)));
            return ira->codegen->invalid_instruction;
        }
        IrInstruction *result = ir_const(ira, source_instr, wanted_type);
        result->value.special = ConstValSpecialStatic;
        result->value.type = wanted_type;
        bigint_init_bigint(&result->value.data.x_union.tag, &val->data.x_enum_tag);
        return result;
    }

    // if the union has all fields 0 bits, we can do it
    // and in fact it's a noop cast because the union value is just the enum value
    if (wanted_type->data.unionation.gen_field_count == 0) {
        IrInstruction *result = ir_build_cast(&ira->new_irb, target->scope, target->source_node, wanted_type, target, CastOpNoop);
        result->value.type = wanted_type;
        return result;
    }

    ErrorMsg *msg = ir_add_error(ira, source_instr,
            buf_sprintf("runtime cast to union '%s' which has non-void fields",
                buf_ptr(&wanted_type->name)));
    for (uint32_t i = 0; i < wanted_type->data.unionation.src_field_count; i += 1) {
        TypeUnionField *union_field = &wanted_type->data.unionation.fields[i];
        if (type_has_bits(union_field->type_entry)) {
            AstNode *field_node = wanted_type->data.unionation.decl_node->data.container_decl.fields.at(i);
            add_error_note(ira->codegen, msg, field_node,
                    buf_sprintf("field '%s' has type '%s'",
                        buf_ptr(union_field->name),
                        buf_ptr(&union_field->type_entry->name)));
        }
    }
    return ira->codegen->invalid_instruction;
}

static IrInstruction *ir_analyze_widen_or_shorten(IrAnalyze *ira, IrInstruction *source_instr,
        IrInstruction *target, ZigType *wanted_type)
{
    assert(wanted_type->id == ZigTypeIdInt || wanted_type->id == ZigTypeIdFloat);

    if (instr_is_comptime(target)) {
        ConstExprValue *val = ir_resolve_const(ira, target, UndefBad);
        if (!val)
            return ira->codegen->invalid_instruction;
        if (wanted_type->id == ZigTypeIdInt) {
            if (bigint_cmp_zero(&val->data.x_bigint) == CmpLT && !wanted_type->data.integral.is_signed) {
                ir_add_error(ira, source_instr,
                    buf_sprintf("attempt to cast negative value to unsigned integer"));
                return ira->codegen->invalid_instruction;
            }
            if (!bigint_fits_in_bits(&val->data.x_bigint, wanted_type->data.integral.bit_count,
                    wanted_type->data.integral.is_signed))
            {
                ir_add_error(ira, source_instr,
                    buf_sprintf("cast from '%s' to '%s' truncates bits",
                        buf_ptr(&target->value.type->name), buf_ptr(&wanted_type->name)));
                return ira->codegen->invalid_instruction;
            }
        }
        IrInstruction *result = ir_const(ira, source_instr, wanted_type);
        result->value.type = wanted_type;
        if (wanted_type->id == ZigTypeIdInt) {
            bigint_init_bigint(&result->value.data.x_bigint, &val->data.x_bigint);
        } else {
            float_init_float(&result->value, val);
        }
        return result;
    }

    // If the destination integer type has no bits, then we can emit a comptime
    // zero. However, we still want to emit a runtime safety check to make sure
    // the target is zero.
    if (!type_has_bits(wanted_type)) {
        assert(wanted_type->id == ZigTypeIdInt);
        assert(type_has_bits(target->value.type));
        ir_build_assert_zero(ira, source_instr, target);
        IrInstruction *result = ir_const_unsigned(ira, source_instr, 0);
        result->value.type = wanted_type;
        return result;
    }

    IrInstruction *result = ir_build_widen_or_shorten(&ira->new_irb, source_instr->scope,
            source_instr->source_node, target);
    result->value.type = wanted_type;
    return result;
}

static IrInstruction *ir_analyze_int_to_enum(IrAnalyze *ira, IrInstruction *source_instr,
        IrInstruction *target, ZigType *wanted_type)
{
    Error err;
    assert(wanted_type->id == ZigTypeIdEnum);

    ZigType *actual_type = target->value.type;

    if ((err = ensure_complete_type(ira->codegen, wanted_type)))
        return ira->codegen->invalid_instruction;

    if (actual_type != wanted_type->data.enumeration.tag_int_type) {
        ir_add_error(ira, source_instr,
                buf_sprintf("integer to enum cast from '%s' instead of its tag type, '%s'",
                    buf_ptr(&actual_type->name),
                    buf_ptr(&wanted_type->data.enumeration.tag_int_type->name)));
        return ira->codegen->invalid_instruction;
    }

    assert(actual_type->id == ZigTypeIdInt || actual_type->id == ZigTypeIdComptimeInt);

    if (instr_is_comptime(target)) {
        ConstExprValue *val = ir_resolve_const(ira, target, UndefBad);
        if (!val)
            return ira->codegen->invalid_instruction;

        TypeEnumField *field = find_enum_field_by_tag(wanted_type, &val->data.x_bigint);
        if (field == nullptr) {
            Buf *val_buf = buf_alloc();
            bigint_append_buf(val_buf, &val->data.x_bigint, 10);
            ErrorMsg *msg = ir_add_error(ira, source_instr,
                buf_sprintf("enum '%s' has no tag matching integer value %s",
                    buf_ptr(&wanted_type->name), buf_ptr(val_buf)));
            add_error_note(ira->codegen, msg, wanted_type->data.enumeration.decl_node,
                    buf_sprintf("'%s' declared here", buf_ptr(&wanted_type->name)));
            return ira->codegen->invalid_instruction;
        }

        IrInstruction *result = ir_const(ira, source_instr, wanted_type);
        bigint_init_bigint(&result->value.data.x_enum_tag, &val->data.x_bigint);
        return result;
    }

    IrInstruction *result = ir_build_int_to_enum(&ira->new_irb, source_instr->scope,
            source_instr->source_node, nullptr, target);
    result->value.type = wanted_type;
    return result;
}

static IrInstruction *ir_analyze_number_to_literal(IrAnalyze *ira, IrInstruction *source_instr,
        IrInstruction *target, ZigType *wanted_type)
{
    ConstExprValue *val = ir_resolve_const(ira, target, UndefBad);
    if (!val)
        return ira->codegen->invalid_instruction;

    IrInstruction *result = ir_const(ira, source_instr, wanted_type);
    if (wanted_type->id == ZigTypeIdComptimeFloat) {
        float_init_float(&result->value, val);
    } else if (wanted_type->id == ZigTypeIdComptimeInt) {
        bigint_init_bigint(&result->value.data.x_bigint, &val->data.x_bigint);
    } else {
        zig_unreachable();
    }
    return result;
}

static IrInstruction *ir_analyze_int_to_err(IrAnalyze *ira, IrInstruction *source_instr, IrInstruction *target,
    ZigType *wanted_type)
{
    assert(target->value.type->id == ZigTypeIdInt);
    assert(!target->value.type->data.integral.is_signed);
    assert(wanted_type->id == ZigTypeIdErrorSet);

    if (instr_is_comptime(target)) {
        ConstExprValue *val = ir_resolve_const(ira, target, UndefBad);
        if (!val)
            return ira->codegen->invalid_instruction;

        IrInstruction *result = ir_const(ira, source_instr, wanted_type);

        if (!resolve_inferred_error_set(ira->codegen, wanted_type, source_instr->source_node)) {
            return ira->codegen->invalid_instruction;
        }

        if (type_is_global_error_set(wanted_type)) {
            BigInt err_count;
            bigint_init_unsigned(&err_count, ira->codegen->errors_by_index.length);

            if (bigint_cmp_zero(&val->data.x_bigint) == CmpEQ || bigint_cmp(&val->data.x_bigint, &err_count) != CmpLT) {
                Buf *val_buf = buf_alloc();
                bigint_append_buf(val_buf, &val->data.x_bigint, 10);
                ir_add_error(ira, source_instr,
                    buf_sprintf("integer value %s represents no error", buf_ptr(val_buf)));
                return ira->codegen->invalid_instruction;
            }

            size_t index = bigint_as_unsigned(&val->data.x_bigint);
            result->value.data.x_err_set = ira->codegen->errors_by_index.at(index);
            return result;
        } else {
            ErrorTableEntry *err = nullptr;
            BigInt err_int;

            for (uint32_t i = 0, count = wanted_type->data.error_set.err_count; i < count; i += 1) {
                ErrorTableEntry *this_err = wanted_type->data.error_set.errors[i];
                bigint_init_unsigned(&err_int, this_err->value);
                if (bigint_cmp(&val->data.x_bigint, &err_int) == CmpEQ) {
                    err = this_err;
                    break;
                }
            }

            if (err == nullptr) {
                Buf *val_buf = buf_alloc();
                bigint_append_buf(val_buf, &val->data.x_bigint, 10);
                ir_add_error(ira, source_instr,
                    buf_sprintf("integer value %s represents no error in '%s'", buf_ptr(val_buf), buf_ptr(&wanted_type->name)));
                return ira->codegen->invalid_instruction;
            }

            result->value.data.x_err_set = err;
            return result;
        }
    }

    IrInstruction *result = ir_build_int_to_err(&ira->new_irb, source_instr->scope, source_instr->source_node, target);
    result->value.type = wanted_type;
    return result;
}

static IrInstruction *ir_analyze_err_to_int(IrAnalyze *ira, IrInstruction *source_instr, IrInstruction *target,
        ZigType *wanted_type)
{
    assert(wanted_type->id == ZigTypeIdInt);

    ZigType *err_type = target->value.type;

    if (instr_is_comptime(target)) {
        ConstExprValue *val = ir_resolve_const(ira, target, UndefBad);
        if (!val)
            return ira->codegen->invalid_instruction;

        IrInstruction *result = ir_const(ira, source_instr, wanted_type);

        ErrorTableEntry *err;
        if (err_type->id == ZigTypeIdErrorUnion) {
            err = val->data.x_err_union.error_set->data.x_err_set;
        } else if (err_type->id == ZigTypeIdErrorSet) {
            err = val->data.x_err_set;
        } else {
            zig_unreachable();
        }
        result->value.type = wanted_type;
        uint64_t err_value = err ? err->value : 0;
        bigint_init_unsigned(&result->value.data.x_bigint, err_value);

        if (!bigint_fits_in_bits(&result->value.data.x_bigint,
            wanted_type->data.integral.bit_count, wanted_type->data.integral.is_signed))
        {
            ir_add_error_node(ira, source_instr->source_node,
                    buf_sprintf("error code '%s' does not fit in '%s'",
                        buf_ptr(&err->name), buf_ptr(&wanted_type->name)));
            return ira->codegen->invalid_instruction;
        }

        return result;
    }

    ZigType *err_set_type;
    if (err_type->id == ZigTypeIdErrorUnion) {
        err_set_type = err_type->data.error_union.err_set_type;
    } else if (err_type->id == ZigTypeIdErrorSet) {
        err_set_type = err_type;
    } else {
        zig_unreachable();
    }
    if (!type_is_global_error_set(err_set_type)) {
        if (!resolve_inferred_error_set(ira->codegen, err_set_type, source_instr->source_node)) {
            return ira->codegen->invalid_instruction;
        }
        if (err_set_type->data.error_set.err_count == 0) {
            IrInstruction *result = ir_const(ira, source_instr, wanted_type);
            bigint_init_unsigned(&result->value.data.x_bigint, 0);
            return result;
        } else if (err_set_type->data.error_set.err_count == 1) {
            IrInstruction *result = ir_const(ira, source_instr, wanted_type);
            ErrorTableEntry *err = err_set_type->data.error_set.errors[0];
            bigint_init_unsigned(&result->value.data.x_bigint, err->value);
            return result;
        }
    }

    BigInt bn;
    bigint_init_unsigned(&bn, ira->codegen->errors_by_index.length);
    if (!bigint_fits_in_bits(&bn, wanted_type->data.integral.bit_count, wanted_type->data.integral.is_signed)) {
        ir_add_error_node(ira, source_instr->source_node,
                buf_sprintf("too many error values to fit in '%s'", buf_ptr(&wanted_type->name)));
        return ira->codegen->invalid_instruction;
    }

    IrInstruction *result = ir_build_err_to_int(&ira->new_irb, source_instr->scope, source_instr->source_node, target);
    result->value.type = wanted_type;
    return result;
}

static IrInstruction *ir_analyze_ptr_to_array(IrAnalyze *ira, IrInstruction *source_instr, IrInstruction *target,
        ZigType *wanted_type)
{
    assert(wanted_type->id == ZigTypeIdPointer);
    Error err;
    if ((err = type_resolve(ira->codegen, target->value.type->data.pointer.child_type, ResolveStatusAlignmentKnown)))
        return ira->codegen->invalid_instruction;
    wanted_type = adjust_ptr_align(ira->codegen, wanted_type, get_ptr_align(ira->codegen, target->value.type));
    ZigType *array_type = wanted_type->data.pointer.child_type;
    assert(array_type->id == ZigTypeIdArray);
    assert(array_type->data.array.len == 1);

    if (instr_is_comptime(target)) {
        ConstExprValue *val = ir_resolve_const(ira, target, UndefBad);
        if (!val)
            return ira->codegen->invalid_instruction;

        assert(val->type->id == ZigTypeIdPointer);
        ConstExprValue *pointee = const_ptr_pointee(ira, ira->codegen, val, source_instr->source_node);
        if (pointee == nullptr)
            return ira->codegen->invalid_instruction;
        if (pointee->special != ConstValSpecialRuntime) {
            ConstExprValue *array_val = create_const_vals(1);
            array_val->special = ConstValSpecialStatic;
            array_val->type = array_type;
            array_val->data.x_array.special = ConstArraySpecialNone;
            array_val->data.x_array.data.s_none.elements = pointee;
            array_val->parent.id = ConstParentIdScalar;
            array_val->parent.data.p_scalar.scalar_val = pointee;

            IrInstructionConst *const_instruction = ir_create_instruction<IrInstructionConst>(&ira->new_irb,
                    source_instr->scope, source_instr->source_node);
            const_instruction->base.value.type = wanted_type;
            const_instruction->base.value.special = ConstValSpecialStatic;
            const_instruction->base.value.data.x_ptr.special = ConstPtrSpecialRef;
            const_instruction->base.value.data.x_ptr.data.ref.pointee = array_val;
            const_instruction->base.value.data.x_ptr.mut = val->data.x_ptr.mut;
            return &const_instruction->base;
        }
    }

    // pointer to array and pointer to single item are represented the same way at runtime
    IrInstruction *result = ir_build_cast(&ira->new_irb, target->scope, target->source_node,
            wanted_type, target, CastOpBitCast);
    result->value.type = wanted_type;
    return result;
}

static void report_recursive_error(IrAnalyze *ira, AstNode *source_node, ConstCastOnly *cast_result,
        ErrorMsg *parent_msg)
{
    switch (cast_result->id) {
        case ConstCastResultIdOk:
            zig_unreachable();
        case ConstCastResultIdInvalid:
            zig_unreachable();
        case ConstCastResultIdOptionalChild: {
            ErrorMsg *msg = add_error_note(ira->codegen, parent_msg, source_node,
                    buf_sprintf("optional type child '%s' cannot cast into optional type child '%s'",
                        buf_ptr(&cast_result->data.optional->actual_child->name),
                        buf_ptr(&cast_result->data.optional->wanted_child->name)));
            report_recursive_error(ira, source_node, &cast_result->data.optional->child, msg);
            break;
        }
        case ConstCastResultIdErrorUnionErrorSet: {
            ErrorMsg *msg = add_error_note(ira->codegen, parent_msg, source_node,
                    buf_sprintf("error set '%s' cannot cast into error set '%s'",
                        buf_ptr(&cast_result->data.error_union_error_set->actual_err_set->name),
                        buf_ptr(&cast_result->data.error_union_error_set->wanted_err_set->name)));
            report_recursive_error(ira, source_node, &cast_result->data.error_union_error_set->child, msg);
            break;
        }
        case ConstCastResultIdErrSet: {
            ZigList<ErrorTableEntry *> *missing_errors = &cast_result->data.error_set_mismatch->missing_errors;
            for (size_t i = 0; i < missing_errors->length; i += 1) {
                ErrorTableEntry *error_entry = missing_errors->at(i);
                add_error_note(ira->codegen, parent_msg, error_entry->decl_node,
                    buf_sprintf("'error.%s' not a member of destination error set", buf_ptr(&error_entry->name)));
            }
            break;
        }
        case ConstCastResultIdErrSetGlobal: {
            add_error_note(ira->codegen, parent_msg, source_node,
                    buf_sprintf("cannot cast global error set into smaller set"));
            break;
        }
        case ConstCastResultIdPointerChild: {
            ErrorMsg *msg = add_error_note(ira->codegen, parent_msg, source_node,
                    buf_sprintf("pointer type child '%s' cannot cast into pointer type child '%s'",
                        buf_ptr(&cast_result->data.pointer_mismatch->actual_child->name),
                        buf_ptr(&cast_result->data.pointer_mismatch->wanted_child->name)));
            report_recursive_error(ira, source_node, &cast_result->data.pointer_mismatch->child, msg);
            break;
        }
        case ConstCastResultIdSliceChild: {
            ErrorMsg *msg = add_error_note(ira->codegen, parent_msg, source_node,
                    buf_sprintf("slice type child '%s' cannot cast into slice type child '%s'",
                        buf_ptr(&cast_result->data.slice_mismatch->actual_child->name),
                        buf_ptr(&cast_result->data.slice_mismatch->wanted_child->name)));
            report_recursive_error(ira, source_node, &cast_result->data.slice_mismatch->child, msg);
            break;
        }
        case ConstCastResultIdErrorUnionPayload: {
            ErrorMsg *msg = add_error_note(ira->codegen, parent_msg, source_node,
                    buf_sprintf("error union payload '%s' cannot cast into error union payload '%s'",
                        buf_ptr(&cast_result->data.error_union_payload->actual_payload->name),
                        buf_ptr(&cast_result->data.error_union_payload->wanted_payload->name)));
            report_recursive_error(ira, source_node, &cast_result->data.error_union_payload->child, msg);
            break;
        }
        case ConstCastResultIdType: {
            AstNode *wanted_decl_node = type_decl_node(cast_result->data.type_mismatch->wanted_type);
            AstNode *actual_decl_node = type_decl_node(cast_result->data.type_mismatch->actual_type);
            if (wanted_decl_node != nullptr) {
                add_error_note(ira->codegen, parent_msg, wanted_decl_node,
                    buf_sprintf("%s declared here",
                        buf_ptr(&cast_result->data.type_mismatch->wanted_type->name)));
            }
            if (actual_decl_node != nullptr) {
                add_error_note(ira->codegen, parent_msg, actual_decl_node,
                    buf_sprintf("%s declared here",
                        buf_ptr(&cast_result->data.type_mismatch->actual_type->name)));
            }
            break;
        }
        case ConstCastResultIdFnArg: {
            ErrorMsg *msg = add_error_note(ira->codegen, parent_msg, source_node,
                    buf_sprintf("parameter %" ZIG_PRI_usize ": '%s' cannot cast into '%s'",
                        cast_result->data.fn_arg.arg_index,
                        buf_ptr(&cast_result->data.fn_arg.actual_param_type->name),
                        buf_ptr(&cast_result->data.fn_arg.expected_param_type->name)));
            report_recursive_error(ira, source_node, cast_result->data.fn_arg.child, msg);
            break;
        }
        case ConstCastResultIdBadAllowsZero: {
            ZigType *wanted_type = cast_result->data.bad_allows_zero->wanted_type;
            ZigType *actual_type = cast_result->data.bad_allows_zero->actual_type;
            bool wanted_allows_zero = ptr_allows_addr_zero(wanted_type);
            bool actual_allows_zero = ptr_allows_addr_zero(actual_type);
            if (actual_allows_zero && !wanted_allows_zero) {
                add_error_note(ira->codegen, parent_msg, source_node,
                        buf_sprintf("'%s' could have null values which are illegal in type '%s'",
                            buf_ptr(&actual_type->name),
                            buf_ptr(&wanted_type->name)));
            } else {
                add_error_note(ira->codegen, parent_msg, source_node,
                        buf_sprintf("mutable '%s' allows illegal null values stored to type '%s'",
                            buf_ptr(&wanted_type->name),
                            buf_ptr(&actual_type->name)));
            }
            break;
        }
        case ConstCastResultIdFnIsGeneric:
            add_error_note(ira->codegen, parent_msg, source_node,
                    buf_sprintf("only one of the functions is generic"));
            break;
        case ConstCastResultIdFnAlign: // TODO
        case ConstCastResultIdFnCC: // TODO
        case ConstCastResultIdFnVarArgs: // TODO
        case ConstCastResultIdFnReturnType: // TODO
        case ConstCastResultIdFnArgCount: // TODO
        case ConstCastResultIdFnGenericArgCount: // TODO
        case ConstCastResultIdFnArgNoAlias: // TODO
        case ConstCastResultIdUnresolvedInferredErrSet: // TODO
        case ConstCastResultIdAsyncAllocatorType: // TODO
            break;
    }
}

static IrInstruction *ir_analyze_array_to_vector(IrAnalyze *ira, IrInstruction *source_instr,
    IrInstruction *array, ZigType *vector_type)
{
    if (instr_is_comptime(array)) {
        // arrays and vectors have the same ConstExprValue representation
        IrInstruction *result = ir_const(ira, source_instr, vector_type);
        copy_const_val(&result->value, &array->value, false);
        result->value.type = vector_type;
        return result;
    }
    return ir_build_array_to_vector(ira, source_instr, array, vector_type);
}

static IrInstruction *ir_analyze_vector_to_array(IrAnalyze *ira, IrInstruction *source_instr,
    IrInstruction *vector, ZigType *array_type)
{
    if (instr_is_comptime(vector)) {
        // arrays and vectors have the same ConstExprValue representation
        IrInstruction *result = ir_const(ira, source_instr, array_type);
        copy_const_val(&result->value, &vector->value, false);
        result->value.type = array_type;
        return result;
    }
    return ir_build_vector_to_array(ira, source_instr, vector, array_type);
}

static IrInstruction *ir_analyze_int_to_c_ptr(IrAnalyze *ira, IrInstruction *source_instr,
        IrInstruction *integer, ZigType *dest_type)
{
    IrInstruction *unsigned_integer;
    if (instr_is_comptime(integer)) {
        unsigned_integer = integer;
    } else {
        assert(integer->value.type->id == ZigTypeIdInt);

        if (integer->value.type->data.integral.bit_count >
            ira->codegen->builtin_types.entry_usize->data.integral.bit_count)
        {
            ir_add_error(ira, source_instr,
                buf_sprintf("integer type '%s' too big for implicit @intToPtr to type '%s'",
                    buf_ptr(&integer->value.type->name),
                    buf_ptr(&dest_type->name)));
            return ira->codegen->invalid_instruction;
        }

        if (integer->value.type->data.integral.is_signed) {
            ZigType *unsigned_int_type = get_int_type(ira->codegen, false,
                    integer->value.type->data.integral.bit_count);
            unsigned_integer = ir_analyze_bit_cast(ira, source_instr, integer, unsigned_int_type);
            if (type_is_invalid(unsigned_integer->value.type))
                return ira->codegen->invalid_instruction;
        } else {
            unsigned_integer = integer;
        }
    }

    return ir_analyze_int_to_ptr(ira, source_instr, unsigned_integer, dest_type);
}

static IrInstruction *ir_analyze_cast(IrAnalyze *ira, IrInstruction *source_instr,
    ZigType *wanted_type, IrInstruction *value)
{
    Error err;
    ZigType *actual_type = value->value.type;
    AstNode *source_node = source_instr->source_node;

    if (type_is_invalid(wanted_type) || type_is_invalid(actual_type)) {
        return ira->codegen->invalid_instruction;
    }

    // perfect match or non-const to const
    ConstCastOnly const_cast_result = types_match_const_cast_only(ira, wanted_type, actual_type,
            source_node, false);
    if (const_cast_result.id == ConstCastResultIdInvalid)
        return ira->codegen->invalid_instruction;
    if (const_cast_result.id == ConstCastResultIdOk) {
        return ir_resolve_cast(ira, source_instr, value, wanted_type, CastOpNoop, false);
    }

    // cast from T to ?T
    // note that the *T to ?*T case is handled via the "ConstCastOnly" mechanism
    if (wanted_type->id == ZigTypeIdOptional) {
        ZigType *wanted_child_type = wanted_type->data.maybe.child_type;
        if (types_match_const_cast_only(ira, wanted_child_type, actual_type, source_node,
            false).id == ConstCastResultIdOk)
        {
            return ir_analyze_optional_wrap(ira, source_instr, value, wanted_type);
        } else if (actual_type->id == ZigTypeIdComptimeInt ||
                   actual_type->id == ZigTypeIdComptimeFloat)
        {
            if (ir_num_lit_fits_in_other_type(ira, value, wanted_child_type, true)) {
                return ir_analyze_optional_wrap(ira, source_instr, value, wanted_type);
            } else {
                return ira->codegen->invalid_instruction;
            }
        } else if (
            wanted_child_type->id == ZigTypeIdPointer &&
            wanted_child_type->data.pointer.ptr_len == PtrLenUnknown &&
            actual_type->id == ZigTypeIdPointer &&
            actual_type->data.pointer.ptr_len == PtrLenSingle &&
            actual_type->data.pointer.child_type->id == ZigTypeIdArray)
        {
            if ((err = type_resolve(ira->codegen, actual_type->data.pointer.child_type, ResolveStatusAlignmentKnown)))
                return ira->codegen->invalid_instruction;
            if ((err = type_resolve(ira->codegen, wanted_child_type->data.pointer.child_type, ResolveStatusAlignmentKnown)))
                return ira->codegen->invalid_instruction;
            if (get_ptr_align(ira->codegen, actual_type) >= get_ptr_align(ira->codegen, wanted_child_type) &&
                types_match_const_cast_only(ira, wanted_child_type->data.pointer.child_type,
                actual_type->data.pointer.child_type->data.array.child_type, source_node,
                !wanted_child_type->data.pointer.is_const).id == ConstCastResultIdOk)
            {
                IrInstruction *cast1 = ir_resolve_ptr_of_array_to_unknown_len_ptr(ira, source_instr, value,
                        wanted_child_type);
                if (type_is_invalid(cast1->value.type))
                    return ira->codegen->invalid_instruction;
                return ir_analyze_optional_wrap(ira, source_instr, cast1, wanted_type);
            }
        }
    }

    // T to E!T
    if (wanted_type->id == ZigTypeIdErrorUnion) {
        if (types_match_const_cast_only(ira, wanted_type->data.error_union.payload_type, actual_type,
            source_node, false).id == ConstCastResultIdOk)
        {
            return ir_analyze_err_wrap_payload(ira, source_instr, value, wanted_type);
        } else if (actual_type->id == ZigTypeIdComptimeInt ||
                   actual_type->id == ZigTypeIdComptimeFloat)
        {
            if (ir_num_lit_fits_in_other_type(ira, value, wanted_type->data.error_union.payload_type, true)) {
                return ir_analyze_err_wrap_payload(ira, source_instr, value, wanted_type);
            } else {
                return ira->codegen->invalid_instruction;
            }
        }
    }

    // cast from T to E!?T
    if (wanted_type->id == ZigTypeIdErrorUnion &&
        wanted_type->data.error_union.payload_type->id == ZigTypeIdOptional &&
        actual_type->id != ZigTypeIdOptional)
    {
        ZigType *wanted_child_type = wanted_type->data.error_union.payload_type->data.maybe.child_type;
        if (types_match_const_cast_only(ira, wanted_child_type, actual_type, source_node, false).id == ConstCastResultIdOk ||
            actual_type->id == ZigTypeIdNull ||
            actual_type->id == ZigTypeIdComptimeInt ||
            actual_type->id == ZigTypeIdComptimeFloat)
        {
            IrInstruction *cast1 = ir_analyze_cast(ira, source_instr, wanted_type->data.error_union.payload_type, value);
            if (type_is_invalid(cast1->value.type))
                return ira->codegen->invalid_instruction;

            IrInstruction *cast2 = ir_analyze_cast(ira, source_instr, wanted_type, cast1);
            if (type_is_invalid(cast2->value.type))
                return ira->codegen->invalid_instruction;

            return cast2;
        }
    }


    // cast from comptime-known number to another number type
    if (instr_is_comptime(value) &&
        (actual_type->id == ZigTypeIdInt || actual_type->id == ZigTypeIdComptimeInt ||
        actual_type->id == ZigTypeIdFloat || actual_type->id == ZigTypeIdComptimeFloat) &&
        (wanted_type->id == ZigTypeIdInt || wanted_type->id == ZigTypeIdComptimeInt ||
        wanted_type->id == ZigTypeIdFloat || wanted_type->id == ZigTypeIdComptimeFloat))
    {
        if (value->value.special == ConstValSpecialUndef) {
            IrInstruction *result = ir_const(ira, source_instr, wanted_type);
            result->value.special = ConstValSpecialUndef;
            return result;
        }
        if (ir_num_lit_fits_in_other_type(ira, value, wanted_type, true)) {
            if (wanted_type->id == ZigTypeIdComptimeInt || wanted_type->id == ZigTypeIdInt) {
                IrInstruction *result = ir_const(ira, source_instr, wanted_type);
                if (actual_type->id == ZigTypeIdComptimeInt || actual_type->id == ZigTypeIdInt) {
                    bigint_init_bigint(&result->value.data.x_bigint, &value->value.data.x_bigint);
                } else {
                    float_init_bigint(&result->value.data.x_bigint, &value->value);
                }
                return result;
            } else if (wanted_type->id == ZigTypeIdComptimeFloat || wanted_type->id == ZigTypeIdFloat) {
                IrInstruction *result = ir_const(ira, source_instr, wanted_type);
                if (actual_type->id == ZigTypeIdComptimeInt || actual_type->id == ZigTypeIdInt) {
                    BigFloat bf;
                    bigfloat_init_bigint(&bf, &value->value.data.x_bigint);
                    float_init_bigfloat(&result->value, &bf);
                } else {
                    float_init_float(&result->value, &value->value);
                }
                return result;
            }
            zig_unreachable();
        } else {
            return ira->codegen->invalid_instruction;
        }
    }

    // widening conversion
    if (wanted_type->id == ZigTypeIdInt &&
        actual_type->id == ZigTypeIdInt &&
        wanted_type->data.integral.is_signed == actual_type->data.integral.is_signed &&
        wanted_type->data.integral.bit_count >= actual_type->data.integral.bit_count)
    {
        return ir_analyze_widen_or_shorten(ira, source_instr, value, wanted_type);
    }

    // small enough unsigned ints can get casted to large enough signed ints
    if (wanted_type->id == ZigTypeIdInt && wanted_type->data.integral.is_signed &&
        actual_type->id == ZigTypeIdInt && !actual_type->data.integral.is_signed &&
        wanted_type->data.integral.bit_count > actual_type->data.integral.bit_count)
    {
        return ir_analyze_widen_or_shorten(ira, source_instr, value, wanted_type);
    }

    // float widening conversion
    if (wanted_type->id == ZigTypeIdFloat &&
        actual_type->id == ZigTypeIdFloat &&
        wanted_type->data.floating.bit_count >= actual_type->data.floating.bit_count)
    {
        return ir_analyze_widen_or_shorten(ira, source_instr, value, wanted_type);
    }


    // cast from [N]T to []const T
    // TODO: once https://github.com/ziglang/zig/issues/265 lands, remove this
    if (is_slice(wanted_type) && actual_type->id == ZigTypeIdArray) {
        ZigType *ptr_type = wanted_type->data.structure.fields[slice_ptr_index].type_entry;
        assert(ptr_type->id == ZigTypeIdPointer);
        if ((ptr_type->data.pointer.is_const || actual_type->data.array.len == 0) &&
            types_match_const_cast_only(ira, ptr_type->data.pointer.child_type, actual_type->data.array.child_type,
                source_node, false).id == ConstCastResultIdOk)
        {
            return ir_analyze_array_to_slice(ira, source_instr, value, wanted_type);
        }
    }

    // cast from [N]T to ?[]const T
    // TODO: once https://github.com/ziglang/zig/issues/265 lands, remove this
    if (wanted_type->id == ZigTypeIdOptional &&
        is_slice(wanted_type->data.maybe.child_type) &&
        actual_type->id == ZigTypeIdArray)
    {
        ZigType *ptr_type =
            wanted_type->data.maybe.child_type->data.structure.fields[slice_ptr_index].type_entry;
        assert(ptr_type->id == ZigTypeIdPointer);
        if ((ptr_type->data.pointer.is_const || actual_type->data.array.len == 0) &&
            types_match_const_cast_only(ira, ptr_type->data.pointer.child_type, actual_type->data.array.child_type,
                source_node, false).id == ConstCastResultIdOk)
        {
            IrInstruction *cast1 = ir_analyze_cast(ira, source_instr, wanted_type->data.maybe.child_type, value);
            if (type_is_invalid(cast1->value.type))
                return ira->codegen->invalid_instruction;

            IrInstruction *cast2 = ir_analyze_cast(ira, source_instr, wanted_type, cast1);
            if (type_is_invalid(cast2->value.type))
                return ira->codegen->invalid_instruction;

            return cast2;
        }
    }

    // *[N]T to [*]T
    if (wanted_type->id == ZigTypeIdPointer &&
        wanted_type->data.pointer.ptr_len == PtrLenUnknown &&
        actual_type->id == ZigTypeIdPointer &&
        actual_type->data.pointer.ptr_len == PtrLenSingle &&
        actual_type->data.pointer.child_type->id == ZigTypeIdArray)
    {
        if ((err = type_resolve(ira->codegen, actual_type->data.pointer.child_type, ResolveStatusAlignmentKnown)))
            return ira->codegen->invalid_instruction;
        if ((err = type_resolve(ira->codegen, wanted_type->data.pointer.child_type, ResolveStatusAlignmentKnown)))
            return ira->codegen->invalid_instruction;
        if (get_ptr_align(ira->codegen, actual_type) >= get_ptr_align(ira->codegen, wanted_type) &&
            types_match_const_cast_only(ira, wanted_type->data.pointer.child_type,
                actual_type->data.pointer.child_type->data.array.child_type, source_node,
                !wanted_type->data.pointer.is_const).id == ConstCastResultIdOk)
        {
            return ir_resolve_ptr_of_array_to_unknown_len_ptr(ira, source_instr, value, wanted_type);
        }
    }

    // *[N]T to []T
    if (is_slice(wanted_type) &&
        actual_type->id == ZigTypeIdPointer &&
        actual_type->data.pointer.ptr_len == PtrLenSingle &&
        actual_type->data.pointer.child_type->id == ZigTypeIdArray)
    {
        ZigType *slice_ptr_type = wanted_type->data.structure.fields[slice_ptr_index].type_entry;
        assert(slice_ptr_type->id == ZigTypeIdPointer);
        ZigType *array_type = actual_type->data.pointer.child_type;
        bool const_ok = (slice_ptr_type->data.pointer.is_const || array_type->data.array.len == 0
                || !actual_type->data.pointer.is_const);
        if (const_ok && types_match_const_cast_only(ira, slice_ptr_type->data.pointer.child_type,
            array_type->data.array.child_type, source_node,
            !slice_ptr_type->data.pointer.is_const).id == ConstCastResultIdOk)
        {
            return ir_resolve_ptr_of_array_to_slice(ira, source_instr, value, wanted_type);
        }
    }


    // cast from null literal to maybe type
    if (wanted_type->id == ZigTypeIdOptional &&
        actual_type->id == ZigTypeIdNull)
    {
        return ir_analyze_null_to_maybe(ira, source_instr, value, wanted_type);
    }

    // cast from [N]T to E![]const T
    if (wanted_type->id == ZigTypeIdErrorUnion &&
        is_slice(wanted_type->data.error_union.payload_type) &&
        actual_type->id == ZigTypeIdArray)
    {
        ZigType *ptr_type =
            wanted_type->data.error_union.payload_type->data.structure.fields[slice_ptr_index].type_entry;
        assert(ptr_type->id == ZigTypeIdPointer);
        if ((ptr_type->data.pointer.is_const || actual_type->data.array.len == 0) &&
            types_match_const_cast_only(ira, ptr_type->data.pointer.child_type, actual_type->data.array.child_type,
                source_node, false).id == ConstCastResultIdOk)
        {
            IrInstruction *cast1 = ir_analyze_cast(ira, source_instr, wanted_type->data.error_union.payload_type, value);
            if (type_is_invalid(cast1->value.type))
                return ira->codegen->invalid_instruction;

            IrInstruction *cast2 = ir_analyze_cast(ira, source_instr, wanted_type, cast1);
            if (type_is_invalid(cast2->value.type))
                return ira->codegen->invalid_instruction;

            return cast2;
        }
    }

    // cast from E to E!T
    if (wanted_type->id == ZigTypeIdErrorUnion &&
        actual_type->id == ZigTypeIdErrorSet)
    {
        return ir_analyze_err_wrap_code(ira, source_instr, value, wanted_type);
    }

    // cast from typed number to integer or float literal.
    // works when the number is known at compile time
    if (instr_is_comptime(value) &&
        ((actual_type->id == ZigTypeIdInt && wanted_type->id == ZigTypeIdComptimeInt) ||
        (actual_type->id == ZigTypeIdFloat && wanted_type->id == ZigTypeIdComptimeFloat)))
    {
        return ir_analyze_number_to_literal(ira, source_instr, value, wanted_type);
    }

    // cast from union to the enum type of the union
    if (actual_type->id == ZigTypeIdUnion && wanted_type->id == ZigTypeIdEnum) {
        if ((err = type_resolve(ira->codegen, actual_type, ResolveStatusZeroBitsKnown)))
            return ira->codegen->invalid_instruction;

        if (actual_type->data.unionation.tag_type == wanted_type) {
            return ir_analyze_union_to_tag(ira, source_instr, value, wanted_type);
        }
    }

    // enum to union which has the enum as the tag type
    if (wanted_type->id == ZigTypeIdUnion && actual_type->id == ZigTypeIdEnum &&
        (wanted_type->data.unionation.decl_node->data.container_decl.auto_enum ||
        wanted_type->data.unionation.decl_node->data.container_decl.init_arg_expr != nullptr))
    {
        if ((err = type_resolve(ira->codegen, wanted_type, ResolveStatusZeroBitsKnown)))
            return ira->codegen->invalid_instruction;

        if (wanted_type->data.unionation.tag_type == actual_type) {
            return ir_analyze_enum_to_union(ira, source_instr, value, wanted_type);
        }
    }

    // cast from *T to *[1]T
    if (wanted_type->id == ZigTypeIdPointer && wanted_type->data.pointer.ptr_len == PtrLenSingle &&
        actual_type->id == ZigTypeIdPointer && actual_type->data.pointer.ptr_len == PtrLenSingle)
    {
        ZigType *array_type = wanted_type->data.pointer.child_type;
        if (array_type->id == ZigTypeIdArray && array_type->data.array.len == 1 &&
            types_match_const_cast_only(ira, array_type->data.array.child_type,
            actual_type->data.pointer.child_type, source_node,
            !wanted_type->data.pointer.is_const).id == ConstCastResultIdOk)
        {
            if ((err = type_resolve(ira->codegen, wanted_type->data.pointer.child_type,
                            ResolveStatusAlignmentKnown)))
            {
                return ira->codegen->invalid_instruction;
            }
            if ((err = type_resolve(ira->codegen, actual_type->data.pointer.child_type,
                            ResolveStatusAlignmentKnown)))
            {
                return ira->codegen->invalid_instruction;
            }
            uint32_t wanted_align = get_ptr_align(ira->codegen, wanted_type);
            uint32_t actual_align = get_ptr_align(ira->codegen, actual_type);
            if (wanted_align > actual_align) {
                ErrorMsg *msg = ir_add_error(ira, source_instr, buf_sprintf("cast increases pointer alignment"));
                add_error_note(ira->codegen, msg, value->source_node,
                        buf_sprintf("'%s' has alignment %" PRIu32, buf_ptr(&actual_type->name), actual_align));
                add_error_note(ira->codegen, msg, source_instr->source_node,
                        buf_sprintf("'%s' has alignment %" PRIu32, buf_ptr(&wanted_type->name), wanted_align));
                return ira->codegen->invalid_instruction;
            }
            return ir_analyze_ptr_to_array(ira, source_instr, value, wanted_type);
        }
    }

    // cast from *T and [*]T to *c_void and ?*c_void
    // but don't do it if the actual type is a double pointer
    if (actual_type->id == ZigTypeIdPointer && actual_type->data.pointer.child_type->id != ZigTypeIdPointer) {
        ZigType *dest_ptr_type = nullptr;
        if (wanted_type->id == ZigTypeIdPointer &&
            wanted_type->data.pointer.ptr_len == PtrLenSingle &&
            wanted_type->data.pointer.child_type == ira->codegen->builtin_types.entry_c_void)
        {
            dest_ptr_type = wanted_type;
        } else if (wanted_type->id == ZigTypeIdOptional &&
            wanted_type->data.maybe.child_type->id == ZigTypeIdPointer &&
            wanted_type->data.maybe.child_type->data.pointer.ptr_len == PtrLenSingle &&
            wanted_type->data.maybe.child_type->data.pointer.child_type == ira->codegen->builtin_types.entry_c_void)
        {
            dest_ptr_type = wanted_type->data.maybe.child_type;
        }
        if (dest_ptr_type != nullptr &&
            (!actual_type->data.pointer.is_const || dest_ptr_type->data.pointer.is_const) &&
            (!actual_type->data.pointer.is_volatile || dest_ptr_type->data.pointer.is_volatile) &&
            actual_type->data.pointer.bit_offset_in_host == dest_ptr_type->data.pointer.bit_offset_in_host &&
            actual_type->data.pointer.host_int_bytes == dest_ptr_type->data.pointer.host_int_bytes &&
            get_ptr_align(ira->codegen, actual_type) >= get_ptr_align(ira->codegen, dest_ptr_type))
        {
            return ir_analyze_ptr_cast(ira, source_instr, value, wanted_type, source_instr, true);
        }
    }

    // cast from T to *T where T is zero bits
    if (wanted_type->id == ZigTypeIdPointer && wanted_type->data.pointer.ptr_len == PtrLenSingle &&
        types_match_const_cast_only(ira, wanted_type->data.pointer.child_type,
            actual_type, source_node, !wanted_type->data.pointer.is_const).id == ConstCastResultIdOk)
    {
        if ((err = type_resolve(ira->codegen, actual_type, ResolveStatusZeroBitsKnown))) {
            return ira->codegen->invalid_instruction;
        }
        if (!type_has_bits(actual_type)) {
            return ir_get_ref(ira, source_instr, value, false, false);
        }
    }

    // cast from @Vector(N, T) to [N]T
    if (wanted_type->id == ZigTypeIdArray && actual_type->id == ZigTypeIdVector &&
        wanted_type->data.array.len == actual_type->data.vector.len &&
        types_match_const_cast_only(ira, wanted_type->data.array.child_type,
            actual_type->data.vector.elem_type, source_node, false).id == ConstCastResultIdOk)
    {
        return ir_analyze_vector_to_array(ira, source_instr, value, wanted_type);
    }

    // cast from [N]T to @Vector(N, T)
    if (actual_type->id == ZigTypeIdArray && wanted_type->id == ZigTypeIdVector &&
        actual_type->data.array.len == wanted_type->data.vector.len &&
        types_match_const_cast_only(ira, actual_type->data.array.child_type,
            wanted_type->data.vector.elem_type, source_node, false).id == ConstCastResultIdOk)
    {
        return ir_analyze_array_to_vector(ira, source_instr, value, wanted_type);
    }

    // casting between C pointers and normal pointers
    if (wanted_type->id == ZigTypeIdPointer && actual_type->id == ZigTypeIdPointer &&
        (wanted_type->data.pointer.ptr_len == PtrLenC || actual_type->data.pointer.ptr_len == PtrLenC) &&
        types_match_const_cast_only(ira, wanted_type->data.pointer.child_type,
            actual_type->data.pointer.child_type, source_node,
            !wanted_type->data.pointer.is_const).id == ConstCastResultIdOk)
    {
        return ir_analyze_ptr_cast(ira, source_instr, value, wanted_type, source_instr, true);
    }

    // cast from integer to C pointer
    if (wanted_type->id == ZigTypeIdPointer && wanted_type->data.pointer.ptr_len == PtrLenC &&
        (actual_type->id == ZigTypeIdInt || actual_type->id == ZigTypeIdComptimeInt))
    {
        return ir_analyze_int_to_c_ptr(ira, source_instr, value, wanted_type);
    }

    // cast from undefined to anything
    if (actual_type->id == ZigTypeIdUndefined) {
        return ir_analyze_undefined_to_anything(ira, source_instr, value, wanted_type);
    }

    ErrorMsg *parent_msg = ir_add_error_node(ira, source_instr->source_node,
        buf_sprintf("expected type '%s', found '%s'",
            buf_ptr(&wanted_type->name),
            buf_ptr(&actual_type->name)));
    report_recursive_error(ira, source_instr->source_node, &const_cast_result, parent_msg);
    return ira->codegen->invalid_instruction;
}

static IrInstruction *ir_implicit_cast(IrAnalyze *ira, IrInstruction *value, ZigType *expected_type) {
    assert(value);
    assert(value != ira->codegen->invalid_instruction);
    assert(!expected_type || !type_is_invalid(expected_type));
    assert(value->value.type);
    assert(!type_is_invalid(value->value.type));
    if (expected_type == nullptr)
        return value; // anything will do
    if (expected_type == value->value.type)
        return value; // match
    if (value->value.type->id == ZigTypeIdUnreachable)
        return value;

    return ir_analyze_cast(ira, value, expected_type, value);
}

static IrInstruction *ir_get_deref(IrAnalyze *ira, IrInstruction *source_instruction, IrInstruction *ptr) {
    Error err;
    ZigType *type_entry = ptr->value.type;
    if (type_is_invalid(type_entry)) {
        return ira->codegen->invalid_instruction;
    } else if (type_entry->id == ZigTypeIdPointer) {
        ZigType *child_type = type_entry->data.pointer.child_type;
        // dereferencing a *u0 is comptime known to be 0
        if (child_type->id == ZigTypeIdInt && child_type->data.integral.bit_count == 0) {
            IrInstruction *result = ir_const(ira, source_instruction, child_type);
            init_const_unsigned_negative(&result->value, child_type, 0, false);
            return result;
        }
        if (instr_is_comptime(ptr)) {
            if (ptr->value.special == ConstValSpecialUndef) {
                ir_add_error(ira, ptr, buf_sprintf("attempt to dereference undefined value"));
                return ira->codegen->invalid_instruction;
            }
            if (ptr->value.data.x_ptr.mut == ConstPtrMutComptimeConst ||
                ptr->value.data.x_ptr.mut == ConstPtrMutComptimeVar)
            {
                ConstExprValue *pointee = const_ptr_pointee_unchecked(ira->codegen, &ptr->value);
                if (pointee->special != ConstValSpecialRuntime) {
                    IrInstruction *result = ir_const(ira, source_instruction, child_type);

                    if ((err = ir_read_const_ptr(ira, ira->codegen, source_instruction->source_node, &result->value,
                                    &ptr->value)))
                    {
                        return ira->codegen->invalid_instruction;
                    }
                    result->value.type = child_type;
                    return result;
                }
            }
        }
        // if the instruction is a const ref instruction we can skip it
        if (ptr->id == IrInstructionIdRef) {
            IrInstructionRef *ref_inst = reinterpret_cast<IrInstructionRef *>(ptr);
            return ref_inst->value;
        }
        IrInstruction *result = ir_build_load_ptr_gen(ira, source_instruction, ptr, child_type);
        if (type_entry->data.pointer.host_int_bytes != 0 && handle_is_ptr(child_type)) {
            ir_add_alloca(ira, result, child_type);
        }
        return result;
    } else {
        ir_add_error_node(ira, source_instruction->source_node,
            buf_sprintf("attempt to dereference non-pointer type '%s'",
                buf_ptr(&type_entry->name)));
        return ira->codegen->invalid_instruction;
    }
}

static bool ir_resolve_align(IrAnalyze *ira, IrInstruction *value, uint32_t *out) {
    if (type_is_invalid(value->value.type))
        return false;

    IrInstruction *casted_value = ir_implicit_cast(ira, value, get_align_amt_type(ira->codegen));
    if (type_is_invalid(casted_value->value.type))
        return false;

    ConstExprValue *const_val = ir_resolve_const(ira, casted_value, UndefBad);
    if (!const_val)
        return false;

    uint32_t align_bytes = bigint_as_unsigned(&const_val->data.x_bigint);
    if (align_bytes == 0) {
        ir_add_error(ira, value, buf_sprintf("alignment must be >= 1"));
        return false;
    }

    if (!is_power_of_2(align_bytes)) {
        ir_add_error(ira, value, buf_sprintf("alignment value %" PRIu32 " is not a power of 2", align_bytes));
        return false;
    }

    *out = align_bytes;
    return true;
}

static bool ir_resolve_unsigned(IrAnalyze *ira, IrInstruction *value, ZigType *int_type, uint64_t *out) {
    if (type_is_invalid(value->value.type))
        return false;

    IrInstruction *casted_value = ir_implicit_cast(ira, value, int_type);
    if (type_is_invalid(casted_value->value.type))
        return false;

    ConstExprValue *const_val = ir_resolve_const(ira, casted_value, UndefBad);
    if (!const_val)
        return false;

    *out = bigint_as_unsigned(&const_val->data.x_bigint);
    return true;
}

static bool ir_resolve_usize(IrAnalyze *ira, IrInstruction *value, uint64_t *out) {
    return ir_resolve_unsigned(ira, value, ira->codegen->builtin_types.entry_usize, out);
}

static bool ir_resolve_bool(IrAnalyze *ira, IrInstruction *value, bool *out) {
    if (type_is_invalid(value->value.type))
        return false;

    IrInstruction *casted_value = ir_implicit_cast(ira, value, ira->codegen->builtin_types.entry_bool);
    if (type_is_invalid(casted_value->value.type))
        return false;

    ConstExprValue *const_val = ir_resolve_const(ira, casted_value, UndefBad);
    if (!const_val)
        return false;

    *out = const_val->data.x_bool;
    return true;
}

static bool ir_resolve_comptime(IrAnalyze *ira, IrInstruction *value, bool *out) {
    if (!value) {
        *out = false;
        return true;
    }
    return ir_resolve_bool(ira, value, out);
}

static bool ir_resolve_atomic_order(IrAnalyze *ira, IrInstruction *value, AtomicOrder *out) {
    if (type_is_invalid(value->value.type))
        return false;

    ConstExprValue *atomic_order_val = get_builtin_value(ira->codegen, "AtomicOrder");
    assert(atomic_order_val->type->id == ZigTypeIdMetaType);
    ZigType *atomic_order_type = atomic_order_val->data.x_type;

    IrInstruction *casted_value = ir_implicit_cast(ira, value, atomic_order_type);
    if (type_is_invalid(casted_value->value.type))
        return false;

    ConstExprValue *const_val = ir_resolve_const(ira, casted_value, UndefBad);
    if (!const_val)
        return false;

    *out = (AtomicOrder)bigint_as_unsigned(&const_val->data.x_enum_tag);
    return true;
}

static bool ir_resolve_atomic_rmw_op(IrAnalyze *ira, IrInstruction *value, AtomicRmwOp *out) {
    if (type_is_invalid(value->value.type))
        return false;

    ConstExprValue *atomic_rmw_op_val = get_builtin_value(ira->codegen, "AtomicRmwOp");
    assert(atomic_rmw_op_val->type->id == ZigTypeIdMetaType);
    ZigType *atomic_rmw_op_type = atomic_rmw_op_val->data.x_type;

    IrInstruction *casted_value = ir_implicit_cast(ira, value, atomic_rmw_op_type);
    if (type_is_invalid(casted_value->value.type))
        return false;

    ConstExprValue *const_val = ir_resolve_const(ira, casted_value, UndefBad);
    if (!const_val)
        return false;

    *out = (AtomicRmwOp)bigint_as_unsigned(&const_val->data.x_enum_tag);
    return true;
}

static bool ir_resolve_global_linkage(IrAnalyze *ira, IrInstruction *value, GlobalLinkageId *out) {
    if (type_is_invalid(value->value.type))
        return false;

    ConstExprValue *global_linkage_val = get_builtin_value(ira->codegen, "GlobalLinkage");
    assert(global_linkage_val->type->id == ZigTypeIdMetaType);
    ZigType *global_linkage_type = global_linkage_val->data.x_type;

    IrInstruction *casted_value = ir_implicit_cast(ira, value, global_linkage_type);
    if (type_is_invalid(casted_value->value.type))
        return false;

    ConstExprValue *const_val = ir_resolve_const(ira, casted_value, UndefBad);
    if (!const_val)
        return false;

    *out = (GlobalLinkageId)bigint_as_unsigned(&const_val->data.x_enum_tag);
    return true;
}

static bool ir_resolve_float_mode(IrAnalyze *ira, IrInstruction *value, FloatMode *out) {
    if (type_is_invalid(value->value.type))
        return false;

    ConstExprValue *float_mode_val = get_builtin_value(ira->codegen, "FloatMode");
    assert(float_mode_val->type->id == ZigTypeIdMetaType);
    ZigType *float_mode_type = float_mode_val->data.x_type;

    IrInstruction *casted_value = ir_implicit_cast(ira, value, float_mode_type);
    if (type_is_invalid(casted_value->value.type))
        return false;

    ConstExprValue *const_val = ir_resolve_const(ira, casted_value, UndefBad);
    if (!const_val)
        return false;

    *out = (FloatMode)bigint_as_unsigned(&const_val->data.x_enum_tag);
    return true;
}


static Buf *ir_resolve_str(IrAnalyze *ira, IrInstruction *value) {
    if (type_is_invalid(value->value.type))
        return nullptr;

    ZigType *ptr_type = get_pointer_to_type_extra(ira->codegen, ira->codegen->builtin_types.entry_u8,
            true, false, PtrLenUnknown, 0, 0, 0);
    ZigType *str_type = get_slice_type(ira->codegen, ptr_type);
    IrInstruction *casted_value = ir_implicit_cast(ira, value, str_type);
    if (type_is_invalid(casted_value->value.type))
        return nullptr;

    ConstExprValue *const_val = ir_resolve_const(ira, casted_value, UndefBad);
    if (!const_val)
        return nullptr;

    ConstExprValue *ptr_field = &const_val->data.x_struct.fields[slice_ptr_index];
    ConstExprValue *len_field = &const_val->data.x_struct.fields[slice_len_index];

    assert(ptr_field->data.x_ptr.special == ConstPtrSpecialBaseArray);
    ConstExprValue *array_val = ptr_field->data.x_ptr.data.base_array.array_val;
    if (array_val->data.x_array.special == ConstArraySpecialBuf) {
        return array_val->data.x_array.data.s_buf;
    }
    expand_undef_array(ira->codegen, array_val);
    size_t len = bigint_as_unsigned(&len_field->data.x_bigint);
    Buf *result = buf_alloc();
    buf_resize(result, len);
    for (size_t i = 0; i < len; i += 1) {
        size_t new_index = ptr_field->data.x_ptr.data.base_array.elem_index + i;
        ConstExprValue *char_val = &array_val->data.x_array.data.s_none.elements[new_index];
        if (char_val->special == ConstValSpecialUndef) {
            ir_add_error(ira, casted_value, buf_sprintf("use of undefined value"));
            return nullptr;
        }
        uint64_t big_c = bigint_as_unsigned(&char_val->data.x_bigint);
        assert(big_c <= UINT8_MAX);
        uint8_t c = (uint8_t)big_c;
        buf_ptr(result)[i] = c;
    }
    return result;
}

static IrInstruction *ir_analyze_instruction_add_implicit_return_type(IrAnalyze *ira,
        IrInstructionAddImplicitReturnType *instruction)
{
    IrInstruction *value = instruction->value->child;
    if (type_is_invalid(value->value.type))
        return ir_unreach_error(ira);

    ira->src_implicit_return_type_list.append(value);

    return ir_const_void(ira, &instruction->base);
}

static IrInstruction *ir_analyze_instruction_return(IrAnalyze *ira, IrInstructionReturn *instruction) {
    IrInstruction *value = instruction->value->child;
    if (type_is_invalid(value->value.type))
        return ir_unreach_error(ira);

    IrInstruction *casted_value = ir_implicit_cast(ira, value, ira->explicit_return_type);
    if (type_is_invalid(casted_value->value.type)) {
        AstNode *source_node = ira->explicit_return_type_source_node;
        if (source_node != nullptr) {
            ErrorMsg *msg = ira->codegen->errors.last();
            add_error_note(ira->codegen, msg, source_node,
                buf_sprintf("return type declared here"));
        }
        return ir_unreach_error(ira);
    }

    if (casted_value->value.special == ConstValSpecialRuntime &&
        casted_value->value.type->id == ZigTypeIdPointer &&
        casted_value->value.data.rh_ptr == RuntimeHintPtrStack)
    {
        ir_add_error(ira, casted_value, buf_sprintf("function returns address of local variable"));
        return ir_unreach_error(ira);
    }
    IrInstruction *result = ir_build_return(&ira->new_irb, instruction->base.scope,
            instruction->base.source_node, casted_value);
    result->value.type = ira->codegen->builtin_types.entry_unreachable;
    return ir_finish_anal(ira, result);
}

static IrInstruction *ir_analyze_instruction_const(IrAnalyze *ira, IrInstructionConst *instruction) {
    IrInstruction *result = ir_const(ira, &instruction->base, nullptr);
    copy_const_val(&result->value, &instruction->base.value, true);
    return result;
}

static IrInstruction *ir_analyze_bin_op_bool(IrAnalyze *ira, IrInstructionBinOp *bin_op_instruction) {
    IrInstruction *op1 = bin_op_instruction->op1->child;
    if (type_is_invalid(op1->value.type))
        return ira->codegen->invalid_instruction;

    IrInstruction *op2 = bin_op_instruction->op2->child;
    if (type_is_invalid(op2->value.type))
        return ira->codegen->invalid_instruction;

    ZigType *bool_type = ira->codegen->builtin_types.entry_bool;

    IrInstruction *casted_op1 = ir_implicit_cast(ira, op1, bool_type);
    if (casted_op1 == ira->codegen->invalid_instruction)
        return ira->codegen->invalid_instruction;

    IrInstruction *casted_op2 = ir_implicit_cast(ira, op2, bool_type);
    if (casted_op2 == ira->codegen->invalid_instruction)
        return ira->codegen->invalid_instruction;

    if (instr_is_comptime(casted_op1) && instr_is_comptime(casted_op2)) {
        ConstExprValue *op1_val = ir_resolve_const(ira, casted_op1, UndefBad);
        if (op1_val == nullptr)
            return ira->codegen->invalid_instruction;

        ConstExprValue *op2_val = ir_resolve_const(ira, casted_op2, UndefBad);
        if (op2_val == nullptr)
            return ira->codegen->invalid_instruction;

        assert(casted_op1->value.type->id == ZigTypeIdBool);
        assert(casted_op2->value.type->id == ZigTypeIdBool);
        bool result_bool;
        if (bin_op_instruction->op_id == IrBinOpBoolOr) {
            result_bool = op1_val->data.x_bool || op2_val->data.x_bool;
        } else if (bin_op_instruction->op_id == IrBinOpBoolAnd) {
            result_bool = op1_val->data.x_bool && op2_val->data.x_bool;
        } else {
            zig_unreachable();
        }
        return ir_const_bool(ira, &bin_op_instruction->base, result_bool);
    }

    IrInstruction *result = ir_build_bin_op(&ira->new_irb,
            bin_op_instruction->base.scope, bin_op_instruction->base.source_node,
            bin_op_instruction->op_id, casted_op1, casted_op2, bin_op_instruction->safety_check_on);
    result->value.type = bool_type;
    return result;
}

static bool resolve_cmp_op_id(IrBinOp op_id, Cmp cmp) {
    switch (op_id) {
        case IrBinOpCmpEq:
            return cmp == CmpEQ;
        case IrBinOpCmpNotEq:
            return cmp != CmpEQ;
        case IrBinOpCmpLessThan:
            return cmp == CmpLT;
        case IrBinOpCmpGreaterThan:
            return cmp == CmpGT;
        case IrBinOpCmpLessOrEq:
            return cmp != CmpGT;
        case IrBinOpCmpGreaterOrEq:
            return cmp != CmpLT;
        default:
            zig_unreachable();
    }
}

static bool optional_value_is_null(ConstExprValue *val) {
    assert(val->special == ConstValSpecialStatic);
    if (get_codegen_ptr_type(val->type) != nullptr) {
        if (val->data.x_ptr.special == ConstPtrSpecialNull) {
            return true;
        } else if (val->data.x_ptr.special == ConstPtrSpecialHardCodedAddr) {
            return val->data.x_ptr.data.hard_coded_addr.addr == 0;
        } else {
            return false;
        }
    } else if (is_opt_err_set(val->type)) {
        return val->data.x_err_set == nullptr;
    } else {
        return val->data.x_optional == nullptr;
    }
}

static IrInstruction *ir_analyze_bin_op_cmp(IrAnalyze *ira, IrInstructionBinOp *bin_op_instruction) {
    IrInstruction *op1 = bin_op_instruction->op1->child;
    if (type_is_invalid(op1->value.type))
        return ira->codegen->invalid_instruction;

    IrInstruction *op2 = bin_op_instruction->op2->child;
    if (type_is_invalid(op2->value.type))
        return ira->codegen->invalid_instruction;

    AstNode *source_node = bin_op_instruction->base.source_node;

    IrBinOp op_id = bin_op_instruction->op_id;
    bool is_equality_cmp = (op_id == IrBinOpCmpEq || op_id == IrBinOpCmpNotEq);
    if (is_equality_cmp &&
        ((op1->value.type->id == ZigTypeIdNull && op2->value.type->id == ZigTypeIdOptional) ||
        (op2->value.type->id == ZigTypeIdNull && op1->value.type->id == ZigTypeIdOptional) ||
        (op1->value.type->id == ZigTypeIdNull && op2->value.type->id == ZigTypeIdNull)))
    {
        if (op1->value.type->id == ZigTypeIdNull && op2->value.type->id == ZigTypeIdNull) {
            return ir_const_bool(ira, &bin_op_instruction->base, (op_id == IrBinOpCmpEq));
        }
        IrInstruction *maybe_op;
        if (op1->value.type->id == ZigTypeIdNull) {
            maybe_op = op2;
        } else if (op2->value.type->id == ZigTypeIdNull) {
            maybe_op = op1;
        } else {
            zig_unreachable();
        }
        if (instr_is_comptime(maybe_op)) {
            ConstExprValue *maybe_val = ir_resolve_const(ira, maybe_op, UndefBad);
            if (!maybe_val)
                return ira->codegen->invalid_instruction;
            bool is_null = optional_value_is_null(maybe_val);
            bool bool_result = (op_id == IrBinOpCmpEq) ? is_null : !is_null;
            return ir_const_bool(ira, &bin_op_instruction->base, bool_result);
        }

        IrInstruction *is_non_null = ir_build_test_nonnull(&ira->new_irb, bin_op_instruction->base.scope,
            source_node, maybe_op);
        is_non_null->value.type = ira->codegen->builtin_types.entry_bool;

        if (op_id == IrBinOpCmpEq) {
            IrInstruction *result = ir_build_bool_not(&ira->new_irb, bin_op_instruction->base.scope,
                bin_op_instruction->base.source_node, is_non_null);
            result->value.type = ira->codegen->builtin_types.entry_bool;
            return result;
        } else {
            return is_non_null;
        }
    } else if (op1->value.type->id == ZigTypeIdNull || op2->value.type->id == ZigTypeIdNull) {
        ir_add_error_node(ira, source_node, buf_sprintf("comparison against null can only be done with optionals"));
        return ira->codegen->invalid_instruction;
    }

    if (op1->value.type->id == ZigTypeIdErrorSet && op2->value.type->id == ZigTypeIdErrorSet) {
        if (!is_equality_cmp) {
            ir_add_error_node(ira, source_node, buf_sprintf("operator not allowed for errors"));
            return ira->codegen->invalid_instruction;
        }
        ZigType *intersect_type = get_error_set_intersection(ira, op1->value.type, op2->value.type, source_node);
        if (type_is_invalid(intersect_type)) {
            return ira->codegen->invalid_instruction;
        }

        if (!resolve_inferred_error_set(ira->codegen, intersect_type, source_node)) {
            return ira->codegen->invalid_instruction;
        }

        // exception if one of the operators has the type of the empty error set, we allow the comparison
        // (and make it comptime known)
        // this is a function which is evaluated at comptime and returns an inferred error set will have an empty
        // error set.
        if (op1->value.type->data.error_set.err_count == 0 || op2->value.type->data.error_set.err_count == 0) {
            bool are_equal = false;
            bool answer;
            if (op_id == IrBinOpCmpEq) {
                answer = are_equal;
            } else if (op_id == IrBinOpCmpNotEq) {
                answer = !are_equal;
            } else {
                zig_unreachable();
            }
            return ir_const_bool(ira, &bin_op_instruction->base, answer);
        }

        if (!type_is_global_error_set(intersect_type)) {
            if (intersect_type->data.error_set.err_count == 0) {
                ir_add_error_node(ira, source_node,
                    buf_sprintf("error sets '%s' and '%s' have no common errors",
                        buf_ptr(&op1->value.type->name), buf_ptr(&op2->value.type->name)));
                return ira->codegen->invalid_instruction;
            }
            if (op1->value.type->data.error_set.err_count == 1 && op2->value.type->data.error_set.err_count == 1) {
                bool are_equal = true;
                bool answer;
                if (op_id == IrBinOpCmpEq) {
                    answer = are_equal;
                } else if (op_id == IrBinOpCmpNotEq) {
                    answer = !are_equal;
                } else {
                    zig_unreachable();
                }
                return ir_const_bool(ira, &bin_op_instruction->base, answer);
            }
        }

        if (instr_is_comptime(op1) && instr_is_comptime(op2)) {
            ConstExprValue *op1_val = ir_resolve_const(ira, op1, UndefBad);
            if (op1_val == nullptr)
                return ira->codegen->invalid_instruction;
            ConstExprValue *op2_val = ir_resolve_const(ira, op2, UndefBad);
            if (op2_val == nullptr)
                return ira->codegen->invalid_instruction;

            bool answer;
            bool are_equal = op1_val->data.x_err_set->value == op2_val->data.x_err_set->value;
            if (op_id == IrBinOpCmpEq) {
                answer = are_equal;
            } else if (op_id == IrBinOpCmpNotEq) {
                answer = !are_equal;
            } else {
                zig_unreachable();
            }

            return ir_const_bool(ira, &bin_op_instruction->base, answer);
        }

        IrInstruction *result = ir_build_bin_op(&ira->new_irb,
                bin_op_instruction->base.scope, bin_op_instruction->base.source_node,
                op_id, op1, op2, bin_op_instruction->safety_check_on);
        result->value.type = ira->codegen->builtin_types.entry_bool;
        return result;
    }

    IrInstruction *instructions[] = {op1, op2};
    ZigType *resolved_type = ir_resolve_peer_types(ira, source_node, nullptr, instructions, 2);
    if (type_is_invalid(resolved_type))
        return ira->codegen->invalid_instruction;

    bool operator_allowed;
    switch (resolved_type->id) {
        case ZigTypeIdInvalid:
            zig_unreachable(); // handled above

        case ZigTypeIdComptimeFloat:
        case ZigTypeIdComptimeInt:
        case ZigTypeIdInt:
        case ZigTypeIdFloat:
        case ZigTypeIdVector:
            operator_allowed = true;
            break;

        case ZigTypeIdBool:
        case ZigTypeIdMetaType:
        case ZigTypeIdVoid:
        case ZigTypeIdErrorSet:
        case ZigTypeIdFn:
        case ZigTypeIdOpaque:
        case ZigTypeIdNamespace:
        case ZigTypeIdBoundFn:
        case ZigTypeIdArgTuple:
        case ZigTypeIdPromise:
        case ZigTypeIdEnum:
            operator_allowed = is_equality_cmp;
            break;

        case ZigTypeIdPointer:
            operator_allowed = is_equality_cmp || (resolved_type->data.pointer.ptr_len == PtrLenC);
            break;

        case ZigTypeIdUnreachable:
        case ZigTypeIdArray:
        case ZigTypeIdStruct:
        case ZigTypeIdUndefined:
        case ZigTypeIdNull:
        case ZigTypeIdErrorUnion:
        case ZigTypeIdUnion:
            operator_allowed = false;
            break;
        case ZigTypeIdOptional:
            operator_allowed = is_equality_cmp && get_codegen_ptr_type(resolved_type) != nullptr;
            break;
    }
    if (!operator_allowed) {
        ir_add_error_node(ira, source_node,
            buf_sprintf("operator not allowed for type '%s'", buf_ptr(&resolved_type->name)));
        return ira->codegen->invalid_instruction;
    }

    IrInstruction *casted_op1 = ir_implicit_cast(ira, op1, resolved_type);
    if (casted_op1 == ira->codegen->invalid_instruction)
        return ira->codegen->invalid_instruction;

    IrInstruction *casted_op2 = ir_implicit_cast(ira, op2, resolved_type);
    if (casted_op2 == ira->codegen->invalid_instruction)
        return ira->codegen->invalid_instruction;

    bool one_possible_value;
    switch (type_has_one_possible_value(ira->codegen, resolved_type)) {
        case OnePossibleValueInvalid:
            return ira->codegen->invalid_instruction;
        case OnePossibleValueYes:
            one_possible_value = true;
            break;
        case OnePossibleValueNo:
            one_possible_value = false;
            break;
    }

    if (one_possible_value || (instr_is_comptime(casted_op1) && instr_is_comptime(casted_op2))) {
        ConstExprValue *op1_val = one_possible_value ? &casted_op1->value : ir_resolve_const(ira, casted_op1, UndefBad);
        if (op1_val == nullptr)
            return ira->codegen->invalid_instruction;
        ConstExprValue *op2_val = one_possible_value ? &casted_op2->value : ir_resolve_const(ira, casted_op2, UndefBad);
        if (op2_val == nullptr)
            return ira->codegen->invalid_instruction;

        if (resolved_type->id == ZigTypeIdComptimeFloat || resolved_type->id == ZigTypeIdFloat) {
            Cmp cmp_result = float_cmp(op1_val, op2_val);
            bool answer = resolve_cmp_op_id(op_id, cmp_result);
            return ir_const_bool(ira, &bin_op_instruction->base, answer);
        } else if (resolved_type->id == ZigTypeIdComptimeInt || resolved_type->id == ZigTypeIdInt) {
            Cmp cmp_result = bigint_cmp(&op1_val->data.x_bigint, &op2_val->data.x_bigint);
            bool answer = resolve_cmp_op_id(op_id, cmp_result);
            return ir_const_bool(ira, &bin_op_instruction->base, answer);
        } else if (resolved_type->id == ZigTypeIdPointer && op_id != IrBinOpCmpEq && op_id != IrBinOpCmpNotEq) {
            if ((op1_val->data.x_ptr.special == ConstPtrSpecialHardCodedAddr ||
                    op1_val->data.x_ptr.special == ConstPtrSpecialNull) &&
                (op2_val->data.x_ptr.special == ConstPtrSpecialHardCodedAddr ||
                    op2_val->data.x_ptr.special == ConstPtrSpecialNull))
            {
                uint64_t op1_addr = op1_val->data.x_ptr.special == ConstPtrSpecialNull ?
                    0 : op1_val->data.x_ptr.data.hard_coded_addr.addr;
                uint64_t op2_addr = op2_val->data.x_ptr.special == ConstPtrSpecialNull ?
                    0 : op2_val->data.x_ptr.data.hard_coded_addr.addr;
                Cmp cmp_result;
                if (op1_addr > op2_addr) {
                    cmp_result = CmpGT;
                } else if (op1_addr < op2_addr) {
                    cmp_result = CmpLT;
                } else {
                    cmp_result = CmpEQ;
                }
                bool answer = resolve_cmp_op_id(op_id, cmp_result);
                return ir_const_bool(ira, &bin_op_instruction->base, answer);
            }
        } else {
            bool are_equal = one_possible_value || const_values_equal(ira->codegen, op1_val, op2_val);
            bool answer;
            if (op_id == IrBinOpCmpEq) {
                answer = are_equal;
            } else if (op_id == IrBinOpCmpNotEq) {
                answer = !are_equal;
            } else {
                zig_unreachable();
            }
            return ir_const_bool(ira, &bin_op_instruction->base, answer);
        }
    }

    // some comparisons with unsigned numbers can be evaluated
    if (resolved_type->id == ZigTypeIdInt && !resolved_type->data.integral.is_signed) {
        ConstExprValue *known_left_val;
        IrBinOp flipped_op_id;
        if (instr_is_comptime(casted_op1)) {
            known_left_val = ir_resolve_const(ira, casted_op1, UndefBad);
            if (known_left_val == nullptr)
                return ira->codegen->invalid_instruction;

            flipped_op_id = op_id;
        } else if (instr_is_comptime(casted_op2)) {
            known_left_val = ir_resolve_const(ira, casted_op2, UndefBad);
            if (known_left_val == nullptr)
                return ira->codegen->invalid_instruction;

            if (op_id == IrBinOpCmpLessThan) {
                flipped_op_id = IrBinOpCmpGreaterThan;
            } else if (op_id == IrBinOpCmpGreaterThan) {
                flipped_op_id = IrBinOpCmpLessThan;
            } else if (op_id == IrBinOpCmpLessOrEq) {
                flipped_op_id = IrBinOpCmpGreaterOrEq;
            } else if (op_id == IrBinOpCmpGreaterOrEq) {
                flipped_op_id = IrBinOpCmpLessOrEq;
            } else {
                flipped_op_id = op_id;
            }
        } else {
            known_left_val = nullptr;
        }
        if (known_left_val != nullptr && bigint_cmp_zero(&known_left_val->data.x_bigint) == CmpEQ &&
            (flipped_op_id == IrBinOpCmpLessOrEq || flipped_op_id == IrBinOpCmpGreaterThan))
        {
            bool answer = (flipped_op_id == IrBinOpCmpLessOrEq);
            return ir_const_bool(ira, &bin_op_instruction->base, answer);
        }
    }

    IrInstruction *result = ir_build_bin_op(&ira->new_irb,
            bin_op_instruction->base.scope, bin_op_instruction->base.source_node,
            op_id, casted_op1, casted_op2, bin_op_instruction->safety_check_on);
    result->value.type = ira->codegen->builtin_types.entry_bool;
    return result;
}

static ErrorMsg *ir_eval_math_op_scalar(IrAnalyze *ira, IrInstruction *source_instr, ZigType *type_entry,
        ConstExprValue *op1_val, IrBinOp op_id, ConstExprValue *op2_val, ConstExprValue *out_val)
{
    bool is_int;
    bool is_float;
    Cmp op2_zcmp;
    if (type_entry->id == ZigTypeIdInt || type_entry->id == ZigTypeIdComptimeInt) {
        is_int = true;
        is_float = false;
        op2_zcmp = bigint_cmp_zero(&op2_val->data.x_bigint);
    } else if (type_entry->id == ZigTypeIdFloat ||
                type_entry->id == ZigTypeIdComptimeFloat)
    {
        is_int = false;
        is_float = true;
        op2_zcmp = float_cmp_zero(op2_val);
    } else {
        zig_unreachable();
    }

    if ((op_id == IrBinOpDivUnspecified || op_id == IrBinOpRemRem || op_id == IrBinOpRemMod ||
        op_id == IrBinOpDivTrunc || op_id == IrBinOpDivFloor) && op2_zcmp == CmpEQ)
    {
        return ir_add_error(ira, source_instr, buf_sprintf("division by zero"));
    }
    if ((op_id == IrBinOpRemRem || op_id == IrBinOpRemMod) && op2_zcmp == CmpLT) {
        return ir_add_error(ira, source_instr, buf_sprintf("negative denominator"));
    }

    switch (op_id) {
        case IrBinOpInvalid:
        case IrBinOpBoolOr:
        case IrBinOpBoolAnd:
        case IrBinOpCmpEq:
        case IrBinOpCmpNotEq:
        case IrBinOpCmpLessThan:
        case IrBinOpCmpGreaterThan:
        case IrBinOpCmpLessOrEq:
        case IrBinOpCmpGreaterOrEq:
        case IrBinOpArrayCat:
        case IrBinOpArrayMult:
        case IrBinOpRemUnspecified:
        case IrBinOpMergeErrorSets:
            zig_unreachable();
        case IrBinOpBinOr:
            assert(is_int);
            bigint_or(&out_val->data.x_bigint, &op1_val->data.x_bigint, &op2_val->data.x_bigint);
            break;
        case IrBinOpBinXor:
            assert(is_int);
            bigint_xor(&out_val->data.x_bigint, &op1_val->data.x_bigint, &op2_val->data.x_bigint);
            break;
        case IrBinOpBinAnd:
            assert(is_int);
            bigint_and(&out_val->data.x_bigint, &op1_val->data.x_bigint, &op2_val->data.x_bigint);
            break;
        case IrBinOpBitShiftLeftExact:
            assert(is_int);
            bigint_shl(&out_val->data.x_bigint, &op1_val->data.x_bigint, &op2_val->data.x_bigint);
            break;
        case IrBinOpBitShiftLeftLossy:
            assert(type_entry->id == ZigTypeIdInt);
            bigint_shl_trunc(&out_val->data.x_bigint, &op1_val->data.x_bigint, &op2_val->data.x_bigint,
                    type_entry->data.integral.bit_count, type_entry->data.integral.is_signed);
            break;
        case IrBinOpBitShiftRightExact:
            {
                assert(is_int);
                bigint_shr(&out_val->data.x_bigint, &op1_val->data.x_bigint, &op2_val->data.x_bigint);
                BigInt orig_bigint;
                bigint_shl(&orig_bigint, &out_val->data.x_bigint, &op2_val->data.x_bigint);
                if (bigint_cmp(&op1_val->data.x_bigint, &orig_bigint) != CmpEQ) {
                    return ir_add_error(ira, source_instr, buf_sprintf("exact shift shifted out 1 bits"));
                }
                break;
            }
        case IrBinOpBitShiftRightLossy:
            assert(is_int);
            bigint_shr(&out_val->data.x_bigint, &op1_val->data.x_bigint, &op2_val->data.x_bigint);
            break;
        case IrBinOpAdd:
            if (is_int) {
                bigint_add(&out_val->data.x_bigint, &op1_val->data.x_bigint, &op2_val->data.x_bigint);
            } else {
                float_add(out_val, op1_val, op2_val);
            }
            break;
        case IrBinOpAddWrap:
            assert(type_entry->id == ZigTypeIdInt);
            bigint_add_wrap(&out_val->data.x_bigint, &op1_val->data.x_bigint, &op2_val->data.x_bigint,
                    type_entry->data.integral.bit_count, type_entry->data.integral.is_signed);
            break;
        case IrBinOpSub:
            if (is_int) {
                bigint_sub(&out_val->data.x_bigint, &op1_val->data.x_bigint, &op2_val->data.x_bigint);
            } else {
                float_sub(out_val, op1_val, op2_val);
            }
            break;
        case IrBinOpSubWrap:
            assert(type_entry->id == ZigTypeIdInt);
            bigint_sub_wrap(&out_val->data.x_bigint, &op1_val->data.x_bigint, &op2_val->data.x_bigint,
                    type_entry->data.integral.bit_count, type_entry->data.integral.is_signed);
            break;
        case IrBinOpMult:
            if (is_int) {
                bigint_mul(&out_val->data.x_bigint, &op1_val->data.x_bigint, &op2_val->data.x_bigint);
            } else {
                float_mul(out_val, op1_val, op2_val);
            }
            break;
        case IrBinOpMultWrap:
            assert(type_entry->id == ZigTypeIdInt);
            bigint_mul_wrap(&out_val->data.x_bigint, &op1_val->data.x_bigint, &op2_val->data.x_bigint,
                    type_entry->data.integral.bit_count, type_entry->data.integral.is_signed);
            break;
        case IrBinOpDivUnspecified:
            assert(is_float);
            float_div(out_val, op1_val, op2_val);
            break;
        case IrBinOpDivTrunc:
            if (is_int) {
                bigint_div_trunc(&out_val->data.x_bigint, &op1_val->data.x_bigint, &op2_val->data.x_bigint);
            } else {
                float_div_trunc(out_val, op1_val, op2_val);
            }
            break;
        case IrBinOpDivFloor:
            if (is_int) {
                bigint_div_floor(&out_val->data.x_bigint, &op1_val->data.x_bigint, &op2_val->data.x_bigint);
            } else {
                float_div_floor(out_val, op1_val, op2_val);
            }
            break;
        case IrBinOpDivExact:
            if (is_int) {
                bigint_div_trunc(&out_val->data.x_bigint, &op1_val->data.x_bigint, &op2_val->data.x_bigint);
                BigInt remainder;
                bigint_rem(&remainder, &op1_val->data.x_bigint, &op2_val->data.x_bigint);
                if (bigint_cmp_zero(&remainder) != CmpEQ) {
                    return ir_add_error(ira, source_instr, buf_sprintf("exact division had a remainder"));
                }
            } else {
                float_div_trunc(out_val, op1_val, op2_val);
                ConstExprValue remainder;
                float_rem(&remainder, op1_val, op2_val);
                if (float_cmp_zero(&remainder) != CmpEQ) {
                    return ir_add_error(ira, source_instr, buf_sprintf("exact division had a remainder"));
                }
            }
            break;
        case IrBinOpRemRem:
            if (is_int) {
                bigint_rem(&out_val->data.x_bigint, &op1_val->data.x_bigint, &op2_val->data.x_bigint);
            } else {
                float_rem(out_val, op1_val, op2_val);
            }
            break;
        case IrBinOpRemMod:
            if (is_int) {
                bigint_mod(&out_val->data.x_bigint, &op1_val->data.x_bigint, &op2_val->data.x_bigint);
            } else {
                float_mod(out_val, op1_val, op2_val);
            }
            break;
    }

    if (type_entry->id == ZigTypeIdInt) {
        if (!bigint_fits_in_bits(&out_val->data.x_bigint, type_entry->data.integral.bit_count,
                type_entry->data.integral.is_signed))
        {
            return ir_add_error(ira, source_instr, buf_sprintf("operation caused overflow"));
        }
    }

    out_val->type = type_entry;
    out_val->special = ConstValSpecialStatic;
    return nullptr;
}

// This works on operands that have already been checked to be comptime known.
static IrInstruction *ir_analyze_math_op(IrAnalyze *ira, IrInstruction *source_instr,
        ZigType *type_entry, ConstExprValue *op1_val, IrBinOp op_id, ConstExprValue *op2_val)
{
    IrInstruction *result_instruction = ir_const(ira, source_instr, type_entry);
    ConstExprValue *out_val = &result_instruction->value;
    if (type_entry->id == ZigTypeIdVector) {
        expand_undef_array(ira->codegen, op1_val);
        expand_undef_array(ira->codegen, op2_val);
        out_val->special = ConstValSpecialUndef;
        expand_undef_array(ira->codegen, out_val);
        size_t len = type_entry->data.vector.len;
        ZigType *scalar_type = type_entry->data.vector.elem_type;
        for (size_t i = 0; i < len; i += 1) {
            ConstExprValue *scalar_op1_val = &op1_val->data.x_array.data.s_none.elements[i];
            ConstExprValue *scalar_op2_val = &op2_val->data.x_array.data.s_none.elements[i];
            ConstExprValue *scalar_out_val = &out_val->data.x_array.data.s_none.elements[i];
            assert(scalar_op1_val->type == scalar_type);
            assert(scalar_op2_val->type == scalar_type);
            assert(scalar_out_val->type == scalar_type);
            ErrorMsg *msg = ir_eval_math_op_scalar(ira, source_instr, scalar_type,
                    scalar_op1_val, op_id, scalar_op2_val, scalar_out_val);
            if (msg != nullptr) {
                add_error_note(ira->codegen, msg, source_instr->source_node,
                    buf_sprintf("when computing vector element at index %" ZIG_PRI_usize, i));
                return ira->codegen->invalid_instruction;
            }
        }
        out_val->type = type_entry;
        out_val->special = ConstValSpecialStatic;
    } else {
        if (ir_eval_math_op_scalar(ira, source_instr, type_entry, op1_val, op_id, op2_val, out_val) != nullptr) {
            return ira->codegen->invalid_instruction;
        }
    }
    return ir_implicit_cast(ira, result_instruction, type_entry);
}

static IrInstruction *ir_analyze_bit_shift(IrAnalyze *ira, IrInstructionBinOp *bin_op_instruction) {
    IrInstruction *op1 = bin_op_instruction->op1->child;
    if (type_is_invalid(op1->value.type))
        return ira->codegen->invalid_instruction;

    if (op1->value.type->id != ZigTypeIdInt && op1->value.type->id != ZigTypeIdComptimeInt) {
        ir_add_error(ira, &bin_op_instruction->base,
            buf_sprintf("bit shifting operation expected integer type, found '%s'",
                buf_ptr(&op1->value.type->name)));
        return ira->codegen->invalid_instruction;
    }

    IrInstruction *op2 = bin_op_instruction->op2->child;
    if (type_is_invalid(op2->value.type))
        return ira->codegen->invalid_instruction;

    IrInstruction *casted_op2;
    IrBinOp op_id = bin_op_instruction->op_id;
    if (op1->value.type->id == ZigTypeIdComptimeInt) {
        casted_op2 = op2;

        if (op_id == IrBinOpBitShiftLeftLossy) {
            op_id = IrBinOpBitShiftLeftExact;
        }

        if (casted_op2->value.data.x_bigint.is_negative) {
            Buf *val_buf = buf_alloc();
            bigint_append_buf(val_buf, &casted_op2->value.data.x_bigint, 10);
            ir_add_error(ira, casted_op2, buf_sprintf("shift by negative value %s", buf_ptr(val_buf)));
            return ira->codegen->invalid_instruction;
        }
    } else {
        ZigType *shift_amt_type = get_smallest_unsigned_int_type(ira->codegen,
                op1->value.type->data.integral.bit_count - 1);
        if (bin_op_instruction->op_id == IrBinOpBitShiftLeftLossy &&
            op2->value.type->id == ZigTypeIdComptimeInt) {
            if (!bigint_fits_in_bits(&op2->value.data.x_bigint,
                                     shift_amt_type->data.integral.bit_count,
                                     op2->value.data.x_bigint.is_negative)) {
                Buf *val_buf = buf_alloc();
                bigint_append_buf(val_buf, &op2->value.data.x_bigint, 10);
                ErrorMsg* msg = ir_add_error(ira,
                    &bin_op_instruction->base,
                    buf_sprintf("RHS of shift is too large for LHS type"));
                add_error_note(
                    ira->codegen,
                    msg,
                    op2->source_node,
                    buf_sprintf("value %s cannot fit into type %s",
                        buf_ptr(val_buf),
                        buf_ptr(&shift_amt_type->name)));
                return ira->codegen->invalid_instruction;
            }
        }

        casted_op2 = ir_implicit_cast(ira, op2, shift_amt_type);
        if (casted_op2 == ira->codegen->invalid_instruction)
            return ira->codegen->invalid_instruction;
    }

    if (instr_is_comptime(op1) && instr_is_comptime(casted_op2)) {
        ConstExprValue *op1_val = ir_resolve_const(ira, op1, UndefBad);
        if (op1_val == nullptr)
            return ira->codegen->invalid_instruction;

        ConstExprValue *op2_val = ir_resolve_const(ira, casted_op2, UndefBad);
        if (op2_val == nullptr)
            return ira->codegen->invalid_instruction;

        return ir_analyze_math_op(ira, &bin_op_instruction->base, op1->value.type, op1_val, op_id, op2_val);
    } else if (op1->value.type->id == ZigTypeIdComptimeInt) {
        ir_add_error(ira, &bin_op_instruction->base,
                buf_sprintf("LHS of shift must be an integer type, or RHS must be compile-time known"));
        return ira->codegen->invalid_instruction;
    } else if (instr_is_comptime(casted_op2) && bigint_cmp_zero(&casted_op2->value.data.x_bigint) == CmpEQ) {
        IrInstruction *result = ir_build_cast(&ira->new_irb, bin_op_instruction->base.scope,
                bin_op_instruction->base.source_node, op1->value.type, op1, CastOpNoop);
        result->value.type = op1->value.type;
        return result;
    }

    IrInstruction *result = ir_build_bin_op(&ira->new_irb, bin_op_instruction->base.scope,
            bin_op_instruction->base.source_node, op_id,
            op1, casted_op2, bin_op_instruction->safety_check_on);
    result->value.type = op1->value.type;
    return result;
}

static bool ok_float_op(IrBinOp op) {
    switch (op) {
        case IrBinOpInvalid:
            zig_unreachable();
        case IrBinOpAdd:
        case IrBinOpSub:
        case IrBinOpMult:
        case IrBinOpDivUnspecified:
        case IrBinOpDivTrunc:
        case IrBinOpDivFloor:
        case IrBinOpDivExact:
        case IrBinOpRemRem:
        case IrBinOpRemMod:
            return true;

        case IrBinOpBoolOr:
        case IrBinOpBoolAnd:
        case IrBinOpCmpEq:
        case IrBinOpCmpNotEq:
        case IrBinOpCmpLessThan:
        case IrBinOpCmpGreaterThan:
        case IrBinOpCmpLessOrEq:
        case IrBinOpCmpGreaterOrEq:
        case IrBinOpBinOr:
        case IrBinOpBinXor:
        case IrBinOpBinAnd:
        case IrBinOpBitShiftLeftLossy:
        case IrBinOpBitShiftLeftExact:
        case IrBinOpBitShiftRightLossy:
        case IrBinOpBitShiftRightExact:
        case IrBinOpAddWrap:
        case IrBinOpSubWrap:
        case IrBinOpMultWrap:
        case IrBinOpRemUnspecified:
        case IrBinOpArrayCat:
        case IrBinOpArrayMult:
        case IrBinOpMergeErrorSets:
            return false;
    }
    zig_unreachable();
}

static bool is_pointer_arithmetic_allowed(ZigType *lhs_type, IrBinOp op) {
    if (lhs_type->id != ZigTypeIdPointer)
        return false;
    switch (op) {
        case IrBinOpAdd:
        case IrBinOpSub:
            break;
        default:
            return false;
    }
    switch (lhs_type->data.pointer.ptr_len) {
        case PtrLenSingle:
            return false;
        case PtrLenUnknown:
        case PtrLenC:
            break;
    }
    return true;
}

static IrInstruction *ir_analyze_bin_op_math(IrAnalyze *ira, IrInstructionBinOp *instruction) {
    Error err;

    IrInstruction *op1 = instruction->op1->child;
    if (type_is_invalid(op1->value.type))
        return ira->codegen->invalid_instruction;

    IrInstruction *op2 = instruction->op2->child;
    if (type_is_invalid(op2->value.type))
        return ira->codegen->invalid_instruction;

    IrBinOp op_id = instruction->op_id;

    // look for pointer math
    if (is_pointer_arithmetic_allowed(op1->value.type, op_id)) {
        IrInstruction *casted_op2 = ir_implicit_cast(ira, op2, ira->codegen->builtin_types.entry_usize);
        if (type_is_invalid(casted_op2->value.type))
            return ira->codegen->invalid_instruction;

        if (op1->value.special == ConstValSpecialUndef || casted_op2->value.special == ConstValSpecialUndef) {
            IrInstruction *result = ir_const(ira, &instruction->base, op1->value.type);
            result->value.special = ConstValSpecialUndef;
            return result;
        }
        if (casted_op2->value.special == ConstValSpecialStatic && op1->value.special == ConstValSpecialStatic &&
            (op1->value.data.x_ptr.special == ConstPtrSpecialHardCodedAddr ||
            op1->value.data.x_ptr.special == ConstPtrSpecialNull))
        {
            uint64_t start_addr = (op1->value.data.x_ptr.special == ConstPtrSpecialNull) ?
                0 : op1->value.data.x_ptr.data.hard_coded_addr.addr;
            uint64_t elem_offset;
            if (!ir_resolve_usize(ira, casted_op2, &elem_offset))
                return ira->codegen->invalid_instruction;
            ZigType *elem_type = op1->value.type->data.pointer.child_type;
            if ((err = type_resolve(ira->codegen, elem_type, ResolveStatusSizeKnown)))
                return ira->codegen->invalid_instruction;
            uint64_t byte_offset = type_size(ira->codegen, elem_type) * elem_offset;
            uint64_t new_addr;
            if (op_id == IrBinOpAdd) {
                new_addr = start_addr + byte_offset;
            } else if (op_id == IrBinOpSub) {
                new_addr = start_addr - byte_offset;
            } else {
                zig_unreachable();
            }
            IrInstruction *result = ir_const(ira, &instruction->base, op1->value.type);
            result->value.data.x_ptr.special = ConstPtrSpecialHardCodedAddr;
            result->value.data.x_ptr.mut = ConstPtrMutRuntimeVar;
            result->value.data.x_ptr.data.hard_coded_addr.addr = new_addr;
            return result;
        }

        IrInstruction *result = ir_build_bin_op(&ira->new_irb, instruction->base.scope,
                instruction->base.source_node, op_id, op1, casted_op2, true);
        result->value.type = op1->value.type;
        return result;
    }

    IrInstruction *instructions[] = {op1, op2};
    ZigType *resolved_type = ir_resolve_peer_types(ira, instruction->base.source_node, nullptr, instructions, 2);
    if (type_is_invalid(resolved_type))
        return ira->codegen->invalid_instruction;

    bool is_int = resolved_type->id == ZigTypeIdInt || resolved_type->id == ZigTypeIdComptimeInt;
    bool is_float = resolved_type->id == ZigTypeIdFloat || resolved_type->id == ZigTypeIdComptimeFloat;
    bool is_signed_div = (
        (resolved_type->id == ZigTypeIdInt && resolved_type->data.integral.is_signed) ||
        resolved_type->id == ZigTypeIdFloat ||
        (resolved_type->id == ZigTypeIdComptimeFloat &&
            ((bigfloat_cmp_zero(&op1->value.data.x_bigfloat) != CmpGT) !=
             (bigfloat_cmp_zero(&op2->value.data.x_bigfloat) != CmpGT))) ||
        (resolved_type->id == ZigTypeIdComptimeInt &&
            ((bigint_cmp_zero(&op1->value.data.x_bigint) != CmpGT) !=
             (bigint_cmp_zero(&op2->value.data.x_bigint) != CmpGT)))
    );
    if (op_id == IrBinOpDivUnspecified && is_int) {
        if (is_signed_div) {
            bool ok = false;
            if (instr_is_comptime(op1) && instr_is_comptime(op2)) {
                ConstExprValue *op1_val = ir_resolve_const(ira, op1, UndefBad);
                if (op1_val == nullptr)
                    return ira->codegen->invalid_instruction;

                ConstExprValue *op2_val = ir_resolve_const(ira, op2, UndefBad);
                if (op2_val == nullptr)
                    return ira->codegen->invalid_instruction;

                if (bigint_cmp_zero(&op2_val->data.x_bigint) == CmpEQ) {
                    // the division by zero error will be caught later, but we don't have a
                    // division function ambiguity problem.
                    op_id = IrBinOpDivTrunc;
                    ok = true;
                } else {
                    BigInt trunc_result;
                    BigInt floor_result;
                    bigint_div_trunc(&trunc_result, &op1_val->data.x_bigint, &op2_val->data.x_bigint);
                    bigint_div_floor(&floor_result, &op1_val->data.x_bigint, &op2_val->data.x_bigint);
                    if (bigint_cmp(&trunc_result, &floor_result) == CmpEQ) {
                        ok = true;
                        op_id = IrBinOpDivTrunc;
                    }
                }
            }
            if (!ok) {
                ir_add_error(ira, &instruction->base,
                    buf_sprintf("division with '%s' and '%s': signed integers must use @divTrunc, @divFloor, or @divExact",
                        buf_ptr(&op1->value.type->name),
                        buf_ptr(&op2->value.type->name)));
                return ira->codegen->invalid_instruction;
            }
        } else {
            op_id = IrBinOpDivTrunc;
        }
    } else if (op_id == IrBinOpRemUnspecified) {
        if (is_signed_div && (is_int || is_float)) {
            bool ok = false;
            if (instr_is_comptime(op1) && instr_is_comptime(op2)) {
                ConstExprValue *op1_val = ir_resolve_const(ira, op1, UndefBad);
                if (op1_val == nullptr)
                    return ira->codegen->invalid_instruction;

                if (is_int) {
                    ConstExprValue *op2_val = ir_resolve_const(ira, op2, UndefBad);
                    if (op2_val == nullptr)
                        return ira->codegen->invalid_instruction;

                    if (bigint_cmp_zero(&op2->value.data.x_bigint) == CmpEQ) {
                        // the division by zero error will be caught later, but we don't
                        // have a remainder function ambiguity problem
                        ok = true;
                    } else {
                        BigInt rem_result;
                        BigInt mod_result;
                        bigint_rem(&rem_result, &op1_val->data.x_bigint, &op2_val->data.x_bigint);
                        bigint_mod(&mod_result, &op1_val->data.x_bigint, &op2_val->data.x_bigint);
                        ok = bigint_cmp(&rem_result, &mod_result) == CmpEQ;
                    }
                } else {
                    IrInstruction *casted_op2 = ir_implicit_cast(ira, op2, resolved_type);
                    if (casted_op2 == ira->codegen->invalid_instruction)
                        return ira->codegen->invalid_instruction;

                    ConstExprValue *op2_val = ir_resolve_const(ira, casted_op2, UndefBad);
                    if (op2_val == nullptr)
                        return ira->codegen->invalid_instruction;

                    if (float_cmp_zero(&casted_op2->value) == CmpEQ) {
                        // the division by zero error will be caught later, but we don't
                        // have a remainder function ambiguity problem
                        ok = true;
                    } else {
                        ConstExprValue rem_result;
                        ConstExprValue mod_result;
                        float_rem(&rem_result, op1_val, op2_val);
                        float_mod(&mod_result, op1_val, op2_val);
                        ok = float_cmp(&rem_result, &mod_result) == CmpEQ;
                    }
                }
            }
            if (!ok) {
                ir_add_error(ira, &instruction->base,
                    buf_sprintf("remainder division with '%s' and '%s': signed integers and floats must use @rem or @mod",
                        buf_ptr(&op1->value.type->name),
                        buf_ptr(&op2->value.type->name)));
                return ira->codegen->invalid_instruction;
            }
        }
        op_id = IrBinOpRemRem;
    }

    bool ok = false;
    if (is_int) {
        ok = true;
    } else if (is_float && ok_float_op(op_id)) {
        ok = true;
    } else if (resolved_type->id == ZigTypeIdVector) {
        ZigType *elem_type = resolved_type->data.vector.elem_type;
        if (elem_type->id == ZigTypeIdInt || elem_type->id == ZigTypeIdComptimeInt) {
            ok = true;
        } else if ((elem_type->id == ZigTypeIdFloat || elem_type->id == ZigTypeIdComptimeFloat) && ok_float_op(op_id)) {
            ok = true;
        }
    }
    if (!ok) {
        AstNode *source_node = instruction->base.source_node;
        ir_add_error_node(ira, source_node,
            buf_sprintf("invalid operands to binary expression: '%s' and '%s'",
                buf_ptr(&op1->value.type->name),
                buf_ptr(&op2->value.type->name)));
        return ira->codegen->invalid_instruction;
    }

    if (resolved_type->id == ZigTypeIdComptimeInt) {
        if (op_id == IrBinOpAddWrap) {
            op_id = IrBinOpAdd;
        } else if (op_id == IrBinOpSubWrap) {
            op_id = IrBinOpSub;
        } else if (op_id == IrBinOpMultWrap) {
            op_id = IrBinOpMult;
        }
    }

    IrInstruction *casted_op1 = ir_implicit_cast(ira, op1, resolved_type);
    if (casted_op1 == ira->codegen->invalid_instruction)
        return ira->codegen->invalid_instruction;

    IrInstruction *casted_op2 = ir_implicit_cast(ira, op2, resolved_type);
    if (casted_op2 == ira->codegen->invalid_instruction)
        return ira->codegen->invalid_instruction;

    if (instr_is_comptime(casted_op1) && instr_is_comptime(casted_op2)) {
        ConstExprValue *op1_val = ir_resolve_const(ira, casted_op1, UndefBad);
        if (op1_val == nullptr)
            return ira->codegen->invalid_instruction;
        ConstExprValue *op2_val = ir_resolve_const(ira, casted_op2, UndefBad);
        if (op2_val == nullptr)
            return ira->codegen->invalid_instruction;

        return ir_analyze_math_op(ira, &instruction->base, resolved_type, op1_val, op_id, op2_val);
    }

    IrInstruction *result = ir_build_bin_op(&ira->new_irb, instruction->base.scope,
            instruction->base.source_node, op_id, casted_op1, casted_op2, instruction->safety_check_on);
    result->value.type = resolved_type;
    return result;
}

static IrInstruction *ir_analyze_array_cat(IrAnalyze *ira, IrInstructionBinOp *instruction) {
    IrInstruction *op1 = instruction->op1->child;
    ZigType *op1_type = op1->value.type;
    if (type_is_invalid(op1_type))
        return ira->codegen->invalid_instruction;

    IrInstruction *op2 = instruction->op2->child;
    ZigType *op2_type = op2->value.type;
    if (type_is_invalid(op2_type))
        return ira->codegen->invalid_instruction;

    ConstExprValue *op1_val = ir_resolve_const(ira, op1, UndefBad);
    if (!op1_val)
        return ira->codegen->invalid_instruction;

    ConstExprValue *op2_val = ir_resolve_const(ira, op2, UndefBad);
    if (!op2_val)
        return ira->codegen->invalid_instruction;

    ConstExprValue *op1_array_val;
    size_t op1_array_index;
    size_t op1_array_end;
    ZigType *child_type;
    if (op1_type->id == ZigTypeIdArray) {
        child_type = op1_type->data.array.child_type;
        op1_array_val = op1_val;
        op1_array_index = 0;
        op1_array_end = op1_type->data.array.len;
    } else if (op1_type->id == ZigTypeIdPointer &&
        op1_type->data.pointer.child_type == ira->codegen->builtin_types.entry_u8 &&
        op1_val->data.x_ptr.special == ConstPtrSpecialBaseArray &&
        op1_val->data.x_ptr.data.base_array.is_cstr)
    {
        child_type = op1_type->data.pointer.child_type;
        op1_array_val = op1_val->data.x_ptr.data.base_array.array_val;
        op1_array_index = op1_val->data.x_ptr.data.base_array.elem_index;
        op1_array_end = op1_array_val->type->data.array.len - 1;
    } else if (is_slice(op1_type)) {
        ZigType *ptr_type = op1_type->data.structure.fields[slice_ptr_index].type_entry;
        child_type = ptr_type->data.pointer.child_type;
        ConstExprValue *ptr_val = &op1_val->data.x_struct.fields[slice_ptr_index];
        assert(ptr_val->data.x_ptr.special == ConstPtrSpecialBaseArray);
        op1_array_val = ptr_val->data.x_ptr.data.base_array.array_val;
        op1_array_index = ptr_val->data.x_ptr.data.base_array.elem_index;
        ConstExprValue *len_val = &op1_val->data.x_struct.fields[slice_len_index];
        op1_array_end = op1_array_index + bigint_as_unsigned(&len_val->data.x_bigint);
    } else {
        ir_add_error(ira, op1,
            buf_sprintf("expected array or C string literal, found '%s'", buf_ptr(&op1->value.type->name)));
        return ira->codegen->invalid_instruction;
    }

    ConstExprValue *op2_array_val;
    size_t op2_array_index;
    size_t op2_array_end;
    bool op2_type_valid;
    if (op2_type->id == ZigTypeIdArray) {
        op2_type_valid = op2_type->data.array.child_type == child_type;
        op2_array_val = op2_val;
        op2_array_index = 0;
        op2_array_end = op2_array_val->type->data.array.len;
    } else if (op2_type->id == ZigTypeIdPointer &&
        op2_type->data.pointer.child_type == ira->codegen->builtin_types.entry_u8 &&
        op2_val->data.x_ptr.special == ConstPtrSpecialBaseArray &&
        op2_val->data.x_ptr.data.base_array.is_cstr)
    {
        op2_type_valid = child_type == ira->codegen->builtin_types.entry_u8;
        op2_array_val = op2_val->data.x_ptr.data.base_array.array_val;
        op2_array_index = op2_val->data.x_ptr.data.base_array.elem_index;
        op2_array_end = op2_array_val->type->data.array.len - 1;
    } else if (is_slice(op2_type)) {
        ZigType *ptr_type = op2_type->data.structure.fields[slice_ptr_index].type_entry;
        op2_type_valid = ptr_type->data.pointer.child_type == child_type;
        ConstExprValue *ptr_val = &op2_val->data.x_struct.fields[slice_ptr_index];
        assert(ptr_val->data.x_ptr.special == ConstPtrSpecialBaseArray);
        op2_array_val = ptr_val->data.x_ptr.data.base_array.array_val;
        op2_array_index = ptr_val->data.x_ptr.data.base_array.elem_index;
        ConstExprValue *len_val = &op2_val->data.x_struct.fields[slice_len_index];
        op2_array_end = op2_array_index + bigint_as_unsigned(&len_val->data.x_bigint);
    } else {
        ir_add_error(ira, op2,
            buf_sprintf("expected array or C string literal, found '%s'", buf_ptr(&op2->value.type->name)));
        return ira->codegen->invalid_instruction;
    }
    if (!op2_type_valid) {
        ir_add_error(ira, op2, buf_sprintf("expected array of type '%s', found '%s'",
                    buf_ptr(&child_type->name),
                    buf_ptr(&op2->value.type->name)));
        return ira->codegen->invalid_instruction;
    }

    // The type of result is populated in the following if blocks
    IrInstruction *result = ir_const(ira, &instruction->base, nullptr);
    ConstExprValue *out_val = &result->value;

    ConstExprValue *out_array_val;
    size_t new_len = (op1_array_end - op1_array_index) + (op2_array_end - op2_array_index);
    if (op1_type->id == ZigTypeIdArray || op2_type->id == ZigTypeIdArray) {
        result->value.type = get_array_type(ira->codegen, child_type, new_len);

        out_array_val = out_val;
    } else if (is_slice(op1_type) || is_slice(op2_type)) {
        ZigType *ptr_type = get_pointer_to_type_extra(ira->codegen, child_type,
                true, false, PtrLenUnknown, 0, 0, 0);
        result->value.type = get_slice_type(ira->codegen, ptr_type);
        out_array_val = create_const_vals(1);
        out_array_val->special = ConstValSpecialStatic;
        out_array_val->type = get_array_type(ira->codegen, child_type, new_len);

        out_val->data.x_struct.fields = create_const_vals(2);

        out_val->data.x_struct.fields[slice_ptr_index].type = ptr_type;
        out_val->data.x_struct.fields[slice_ptr_index].special = ConstValSpecialStatic;
        out_val->data.x_struct.fields[slice_ptr_index].data.x_ptr.special = ConstPtrSpecialBaseArray;
        out_val->data.x_struct.fields[slice_ptr_index].data.x_ptr.data.base_array.array_val = out_array_val;
        out_val->data.x_struct.fields[slice_ptr_index].data.x_ptr.data.base_array.elem_index = 0;

        out_val->data.x_struct.fields[slice_len_index].type = ira->codegen->builtin_types.entry_usize;
        out_val->data.x_struct.fields[slice_len_index].special = ConstValSpecialStatic;
        bigint_init_unsigned(&out_val->data.x_struct.fields[slice_len_index].data.x_bigint, new_len);
    } else {
        new_len += 1; // null byte

        // TODO make this `[*]null T` instead of `[*]T`
        result->value.type = get_pointer_to_type_extra(ira->codegen, child_type, true, false, PtrLenUnknown, 0, 0, 0);

        out_array_val = create_const_vals(1);
        out_array_val->special = ConstValSpecialStatic;
        out_array_val->type = get_array_type(ira->codegen, child_type, new_len);
        out_val->data.x_ptr.special = ConstPtrSpecialBaseArray;
        out_val->data.x_ptr.data.base_array.is_cstr = true;
        out_val->data.x_ptr.data.base_array.array_val = out_array_val;
        out_val->data.x_ptr.data.base_array.elem_index = 0;
    }

    if (op1_array_val->data.x_array.special == ConstArraySpecialUndef &&
        op2_array_val->data.x_array.special == ConstArraySpecialUndef) {
        out_array_val->data.x_array.special = ConstArraySpecialUndef;
        return result;
    }

    out_array_val->data.x_array.data.s_none.elements = create_const_vals(new_len);
    // TODO handle the buf case here for an optimization
    expand_undef_array(ira->codegen, op1_array_val);
    expand_undef_array(ira->codegen, op2_array_val);

    size_t next_index = 0;
    for (size_t i = op1_array_index; i < op1_array_end; i += 1, next_index += 1) {
        out_array_val->data.x_array.data.s_none.elements[next_index] = op1_array_val->data.x_array.data.s_none.elements[i];
    }
    for (size_t i = op2_array_index; i < op2_array_end; i += 1, next_index += 1) {
        out_array_val->data.x_array.data.s_none.elements[next_index] = op2_array_val->data.x_array.data.s_none.elements[i];
    }
    if (next_index < new_len) {
        ConstExprValue *null_byte = &out_array_val->data.x_array.data.s_none.elements[next_index];
        init_const_unsigned_negative(null_byte, child_type, 0, false);
        next_index += 1;
    }
    assert(next_index == new_len);

    return result;
}

static IrInstruction *ir_analyze_array_mult(IrAnalyze *ira, IrInstructionBinOp *instruction) {
    IrInstruction *op1 = instruction->op1->child;
    if (type_is_invalid(op1->value.type))
        return ira->codegen->invalid_instruction;

    IrInstruction *op2 = instruction->op2->child;
    if (type_is_invalid(op2->value.type))
        return ira->codegen->invalid_instruction;

    ConstExprValue *array_val = ir_resolve_const(ira, op1, UndefBad);
    if (!array_val)
        return ira->codegen->invalid_instruction;

    uint64_t mult_amt;
    if (!ir_resolve_usize(ira, op2, &mult_amt))
        return ira->codegen->invalid_instruction;

    ZigType *array_type = op1->value.type;
    if (array_type->id != ZigTypeIdArray) {
        ir_add_error(ira, op1, buf_sprintf("expected array type, found '%s'", buf_ptr(&op1->value.type->name)));
        return ira->codegen->invalid_instruction;
    }

    uint64_t old_array_len = array_type->data.array.len;
    uint64_t new_array_len;

    if (mul_u64_overflow(old_array_len, mult_amt, &new_array_len))
    {
        ir_add_error(ira, &instruction->base, buf_sprintf("operation results in overflow"));
        return ira->codegen->invalid_instruction;
    }

    ZigType *child_type = array_type->data.array.child_type;

    IrInstruction *result = ir_const(ira, &instruction->base,
        get_array_type(ira->codegen, child_type, new_array_len));
    ConstExprValue *out_val = &result->value;
    if (array_val->data.x_array.special == ConstArraySpecialUndef) {
        out_val->data.x_array.special = ConstArraySpecialUndef;
        return result;
    }

    // TODO optimize the buf case
    expand_undef_array(ira->codegen, array_val);
    out_val->data.x_array.data.s_none.elements = create_const_vals(new_array_len);

    uint64_t i = 0;
    for (uint64_t x = 0; x < mult_amt; x += 1) {
        for (uint64_t y = 0; y < old_array_len; y += 1) {
            out_val->data.x_array.data.s_none.elements[i] = array_val->data.x_array.data.s_none.elements[y];
            i += 1;
        }
    }
    assert(i == new_array_len);

    return result;
}

static IrInstruction *ir_analyze_merge_error_sets(IrAnalyze *ira, IrInstructionBinOp *instruction) {
    ZigType *op1_type = ir_resolve_error_set_type(ira, &instruction->base, instruction->op1->child);
    if (type_is_invalid(op1_type))
        return ira->codegen->invalid_instruction;

    ZigType *op2_type = ir_resolve_error_set_type(ira, &instruction->base, instruction->op2->child);
    if (type_is_invalid(op2_type))
        return ira->codegen->invalid_instruction;

    if (type_is_global_error_set(op1_type) ||
        type_is_global_error_set(op2_type))
    {
        return ir_const_type(ira, &instruction->base, ira->codegen->builtin_types.entry_global_error_set);
    }

    if (!resolve_inferred_error_set(ira->codegen, op1_type, instruction->op1->child->source_node)) {
        return ira->codegen->invalid_instruction;
    }

    if (!resolve_inferred_error_set(ira->codegen, op2_type, instruction->op2->child->source_node)) {
        return ira->codegen->invalid_instruction;
    }

    ErrorTableEntry **errors = allocate<ErrorTableEntry *>(ira->codegen->errors_by_index.length);
    for (uint32_t i = 0, count = op1_type->data.error_set.err_count; i < count; i += 1) {
        ErrorTableEntry *error_entry = op1_type->data.error_set.errors[i];
        assert(errors[error_entry->value] == nullptr);
        errors[error_entry->value] = error_entry;
    }
    ZigType *result_type = get_error_set_union(ira->codegen, errors, op1_type, op2_type);
    free(errors);

    return ir_const_type(ira, &instruction->base, result_type);
}

static IrInstruction *ir_analyze_instruction_bin_op(IrAnalyze *ira, IrInstructionBinOp *bin_op_instruction) {
    IrBinOp op_id = bin_op_instruction->op_id;
    switch (op_id) {
        case IrBinOpInvalid:
            zig_unreachable();
        case IrBinOpBoolOr:
        case IrBinOpBoolAnd:
            return ir_analyze_bin_op_bool(ira, bin_op_instruction);
        case IrBinOpCmpEq:
        case IrBinOpCmpNotEq:
        case IrBinOpCmpLessThan:
        case IrBinOpCmpGreaterThan:
        case IrBinOpCmpLessOrEq:
        case IrBinOpCmpGreaterOrEq:
            return ir_analyze_bin_op_cmp(ira, bin_op_instruction);
        case IrBinOpBitShiftLeftLossy:
        case IrBinOpBitShiftLeftExact:
        case IrBinOpBitShiftRightLossy:
        case IrBinOpBitShiftRightExact:
            return ir_analyze_bit_shift(ira, bin_op_instruction);
        case IrBinOpBinOr:
        case IrBinOpBinXor:
        case IrBinOpBinAnd:
        case IrBinOpAdd:
        case IrBinOpAddWrap:
        case IrBinOpSub:
        case IrBinOpSubWrap:
        case IrBinOpMult:
        case IrBinOpMultWrap:
        case IrBinOpDivUnspecified:
        case IrBinOpDivTrunc:
        case IrBinOpDivFloor:
        case IrBinOpDivExact:
        case IrBinOpRemUnspecified:
        case IrBinOpRemRem:
        case IrBinOpRemMod:
            return ir_analyze_bin_op_math(ira, bin_op_instruction);
        case IrBinOpArrayCat:
            return ir_analyze_array_cat(ira, bin_op_instruction);
        case IrBinOpArrayMult:
            return ir_analyze_array_mult(ira, bin_op_instruction);
        case IrBinOpMergeErrorSets:
            return ir_analyze_merge_error_sets(ira, bin_op_instruction);
    }
    zig_unreachable();
}

static IrInstruction *ir_analyze_instruction_decl_var(IrAnalyze *ira,
        IrInstructionDeclVarSrc *decl_var_instruction)
{
    Error err;
    ZigVar *var = decl_var_instruction->var;

    IrInstruction *init_value = decl_var_instruction->init_value->child;
    if (type_is_invalid(init_value->value.type)) {
        var->var_type = ira->codegen->builtin_types.entry_invalid;
        return ira->codegen->invalid_instruction;
    }

    ZigType *explicit_type = nullptr;
    IrInstruction *var_type = nullptr;
    if (decl_var_instruction->var_type != nullptr) {
        var_type = decl_var_instruction->var_type->child;
        ZigType *proposed_type = ir_resolve_type(ira, var_type);
        explicit_type = validate_var_type(ira->codegen, var_type->source_node, proposed_type);
        if (type_is_invalid(explicit_type)) {
            var->var_type = ira->codegen->builtin_types.entry_invalid;
            return ira->codegen->invalid_instruction;
        }
    }

    AstNode *source_node = decl_var_instruction->base.source_node;

    IrInstruction *casted_init_value = ir_implicit_cast(ira, init_value, explicit_type);
    bool is_comptime_var = ir_get_var_is_comptime(var);

    bool var_class_requires_const = false;

    ZigType *result_type = casted_init_value->value.type;
    if (type_is_invalid(result_type)) {
        result_type = ira->codegen->builtin_types.entry_invalid;
    } else if (result_type->id == ZigTypeIdUnreachable || result_type->id == ZigTypeIdOpaque) {
        ir_add_error_node(ira, source_node,
            buf_sprintf("variable of type '%s' not allowed", buf_ptr(&result_type->name)));
        result_type = ira->codegen->builtin_types.entry_invalid;
    }

    switch (type_requires_comptime(ira->codegen, result_type)) {
    case ReqCompTimeInvalid:
        result_type = ira->codegen->builtin_types.entry_invalid;
        break;
    case ReqCompTimeYes:
        var_class_requires_const = true;
        if (!var->gen_is_const && !is_comptime_var) {
            ir_add_error_node(ira, source_node,
                buf_sprintf("variable of type '%s' must be const or comptime",
                    buf_ptr(&result_type->name)));
            result_type = ira->codegen->builtin_types.entry_invalid;
        }
        break;
    case ReqCompTimeNo:
        if (casted_init_value->value.special == ConstValSpecialStatic &&
            casted_init_value->value.type->id == ZigTypeIdFn &&
            casted_init_value->value.data.x_ptr.special != ConstPtrSpecialHardCodedAddr &&
            casted_init_value->value.data.x_ptr.data.fn.fn_entry->fn_inline == FnInlineAlways)
        {
            var_class_requires_const = true;
            if (!var->src_is_const && !is_comptime_var) {
                ErrorMsg *msg = ir_add_error_node(ira, source_node,
                    buf_sprintf("functions marked inline must be stored in const or comptime var"));
                AstNode *proto_node = casted_init_value->value.data.x_ptr.data.fn.fn_entry->proto_node;
                add_error_note(ira->codegen, msg, proto_node, buf_sprintf("declared here"));
                result_type = ira->codegen->builtin_types.entry_invalid;
            }
        }
        break;
    }

    if (var->var_type != nullptr && !is_comptime_var) {
        // This is at least the second time we've seen this variable declaration during analysis.
        // This means that this is actually a different variable due to, e.g. an inline while loop.
        // We make a new variable so that it can hold a different type, and so the debug info can
        // be distinct.
        ZigVar *new_var = create_local_var(ira->codegen, var->decl_node, var->child_scope,
            &var->name, var->src_is_const, var->gen_is_const, var->shadowable, var->is_comptime, true);
        new_var->owner_exec = var->owner_exec;
        new_var->align_bytes = var->align_bytes;
        if (var->mem_slot_index != SIZE_MAX) {
            ConstExprValue *vals = create_const_vals(1);
            new_var->mem_slot_index = ira->exec_context.mem_slot_list.length;
            ira->exec_context.mem_slot_list.append(vals);
        }

        var->next_var = new_var;
        var = new_var;
    }

    // This must be done after possibly creating a new variable above
    var->ref_count = 0;

    var->var_type = result_type;
    assert(var->var_type);

    if (type_is_invalid(result_type)) {
        return ir_const_void(ira, &decl_var_instruction->base);
    }

    if (decl_var_instruction->align_value == nullptr) {
        if ((err = type_resolve(ira->codegen, result_type, ResolveStatusAlignmentKnown))) {
            var->var_type = ira->codegen->builtin_types.entry_invalid;
            return ir_const_void(ira, &decl_var_instruction->base);
        }
        var->align_bytes = get_abi_alignment(ira->codegen, result_type);
    } else {
        if (!ir_resolve_align(ira, decl_var_instruction->align_value->child, &var->align_bytes)) {
            var->var_type = ira->codegen->builtin_types.entry_invalid;
        }
    }

    if (casted_init_value->value.special != ConstValSpecialRuntime) {
        if (var->mem_slot_index != SIZE_MAX) {
            assert(var->mem_slot_index < ira->exec_context.mem_slot_list.length);
            ConstExprValue *mem_slot = ira->exec_context.mem_slot_list.at(var->mem_slot_index);
            copy_const_val(mem_slot, &casted_init_value->value, !is_comptime_var || var->gen_is_const);

            if (is_comptime_var || (var_class_requires_const && var->gen_is_const)) {
                return ir_const_void(ira, &decl_var_instruction->base);
            }
        }
    } else if (is_comptime_var) {
        ir_add_error(ira, &decl_var_instruction->base,
                buf_sprintf("cannot store runtime value in compile time variable"));
        var->var_type = ira->codegen->builtin_types.entry_invalid;
        return ira->codegen->invalid_instruction;
    }

    ZigFn *fn_entry = exec_fn_entry(ira->new_irb.exec);
    if (fn_entry)
        fn_entry->variable_list.append(var);

    return ir_build_var_decl_gen(ira, &decl_var_instruction->base, var, casted_init_value);
}

static VarLinkage global_linkage_to_var_linkage(GlobalLinkageId id) {
    switch (id) {
        case GlobalLinkageIdStrong:
            return VarLinkageExportStrong;
        case GlobalLinkageIdWeak:
            return VarLinkageExportWeak;
        case GlobalLinkageIdLinkOnce:
            return VarLinkageExportLinkOnce;
        case GlobalLinkageIdInternal:
            return VarLinkageInternal;
    }
    zig_unreachable();
}

static IrInstruction *ir_analyze_instruction_export(IrAnalyze *ira, IrInstructionExport *instruction) {
    IrInstruction *name = instruction->name->child;
    Buf *symbol_name = ir_resolve_str(ira, name);
    if (symbol_name == nullptr) {
        return ira->codegen->invalid_instruction;
    }

    IrInstruction *target = instruction->target->child;
    if (type_is_invalid(target->value.type)) {
        return ira->codegen->invalid_instruction;
    }

    GlobalLinkageId global_linkage_id = GlobalLinkageIdStrong;
    if (instruction->linkage != nullptr) {
        IrInstruction *linkage_value = instruction->linkage->child;
        if (!ir_resolve_global_linkage(ira, linkage_value, &global_linkage_id)) {
            return ira->codegen->invalid_instruction;
        }
    }

    // TODO: This function needs to be audited.
    // It's not clear how all the different types are supposed to be handled.
    // Need comprehensive tests for exporting one thing in one file and declaring an extern var
    // in another file.
    TldFn *tld_fn = allocate<TldFn>(1);
    tld_fn->base.id = TldIdFn;
    tld_fn->base.source_node = instruction->base.source_node;

    auto entry = ira->codegen->exported_symbol_names.put_unique(symbol_name, &tld_fn->base);
    if (entry) {
        AstNode *other_export_node = entry->value->source_node;
        ErrorMsg *msg = ir_add_error(ira, &instruction->base,
                buf_sprintf("exported symbol collision: '%s'", buf_ptr(symbol_name)));
        add_error_note(ira->codegen, msg, other_export_node, buf_sprintf("other symbol is here"));
        return ira->codegen->invalid_instruction;
    }

    bool want_var_export = false;
    switch (target->value.type->id) {
        case ZigTypeIdInvalid:
        case ZigTypeIdUnreachable:
            zig_unreachable();
        case ZigTypeIdFn: {
            assert(target->value.data.x_ptr.special == ConstPtrSpecialFunction);
            ZigFn *fn_entry = target->value.data.x_ptr.data.fn.fn_entry;
            tld_fn->fn_entry = fn_entry;
            CallingConvention cc = fn_entry->type_entry->data.fn.fn_type_id.cc;
            switch (cc) {
                case CallingConventionUnspecified: {
                    ErrorMsg *msg = ir_add_error(ira, target,
                        buf_sprintf("exported function must specify calling convention"));
                    add_error_note(ira->codegen, msg, fn_entry->proto_node, buf_sprintf("declared here"));
                } break;
                case CallingConventionAsync: {
                    ErrorMsg *msg = ir_add_error(ira, target,
                        buf_sprintf("exported function cannot be async"));
                    add_error_note(ira->codegen, msg, fn_entry->proto_node, buf_sprintf("declared here"));
                } break;
                case CallingConventionC:
                case CallingConventionNaked:
                case CallingConventionCold:
                case CallingConventionStdcall:
                    add_fn_export(ira->codegen, fn_entry, symbol_name, global_linkage_id, cc == CallingConventionC);
                    break;
            }
        } break;
        case ZigTypeIdStruct:
            if (is_slice(target->value.type)) {
                ir_add_error(ira, target,
                    buf_sprintf("unable to export value of type '%s'", buf_ptr(&target->value.type->name)));
            } else if (target->value.type->data.structure.layout != ContainerLayoutExtern) {
                ErrorMsg *msg = ir_add_error(ira, target,
                    buf_sprintf("exported struct value must be declared extern"));
                add_error_note(ira->codegen, msg, target->value.type->data.structure.decl_node, buf_sprintf("declared here"));
            } else {
                want_var_export = true;
            }
            break;
        case ZigTypeIdUnion:
            if (target->value.type->data.unionation.layout != ContainerLayoutExtern) {
                ErrorMsg *msg = ir_add_error(ira, target,
                    buf_sprintf("exported union value must be declared extern"));
                add_error_note(ira->codegen, msg, target->value.type->data.unionation.decl_node, buf_sprintf("declared here"));
            } else {
                want_var_export = true;
            }
            break;
        case ZigTypeIdEnum:
            if (target->value.type->data.enumeration.layout != ContainerLayoutExtern) {
                ErrorMsg *msg = ir_add_error(ira, target,
                    buf_sprintf("exported enum value must be declared extern"));
                add_error_note(ira->codegen, msg, target->value.type->data.enumeration.decl_node, buf_sprintf("declared here"));
            } else {
                want_var_export = true;
            }
            break;
        case ZigTypeIdMetaType: {
            ZigType *type_value = target->value.data.x_type;
            switch (type_value->id) {
                case ZigTypeIdInvalid:
                    zig_unreachable();
                case ZigTypeIdStruct:
                    if (is_slice(type_value)) {
                        ir_add_error(ira, target,
                            buf_sprintf("unable to export type '%s'", buf_ptr(&type_value->name)));
                    } else if (type_value->data.structure.layout != ContainerLayoutExtern) {
                        ErrorMsg *msg = ir_add_error(ira, target,
                            buf_sprintf("exported struct must be declared extern"));
                        add_error_note(ira->codegen, msg, type_value->data.structure.decl_node, buf_sprintf("declared here"));
                    }
                    break;
                case ZigTypeIdUnion:
                    if (type_value->data.unionation.layout != ContainerLayoutExtern) {
                        ErrorMsg *msg = ir_add_error(ira, target,
                            buf_sprintf("exported union must be declared extern"));
                        add_error_note(ira->codegen, msg, type_value->data.unionation.decl_node, buf_sprintf("declared here"));
                    }
                    break;
                case ZigTypeIdEnum:
                    if (type_value->data.enumeration.layout != ContainerLayoutExtern) {
                        ErrorMsg *msg = ir_add_error(ira, target,
                            buf_sprintf("exported enum must be declared extern"));
                        add_error_note(ira->codegen, msg, type_value->data.enumeration.decl_node, buf_sprintf("declared here"));
                    }
                    break;
                case ZigTypeIdFn: {
                    if (type_value->data.fn.fn_type_id.cc == CallingConventionUnspecified) {
                        ir_add_error(ira, target,
                            buf_sprintf("exported function type must specify calling convention"));
                    }
                } break;
                case ZigTypeIdInt:
                case ZigTypeIdFloat:
                case ZigTypeIdPointer:
                case ZigTypeIdArray:
                case ZigTypeIdBool:
                case ZigTypeIdVector:
                    break;
                case ZigTypeIdMetaType:
                case ZigTypeIdVoid:
                case ZigTypeIdUnreachable:
                case ZigTypeIdComptimeFloat:
                case ZigTypeIdComptimeInt:
                case ZigTypeIdUndefined:
                case ZigTypeIdNull:
                case ZigTypeIdOptional:
                case ZigTypeIdErrorUnion:
                case ZigTypeIdErrorSet:
                case ZigTypeIdNamespace:
                case ZigTypeIdBoundFn:
                case ZigTypeIdArgTuple:
                case ZigTypeIdOpaque:
                case ZigTypeIdPromise:
                    ir_add_error(ira, target,
                        buf_sprintf("invalid export target '%s'", buf_ptr(&type_value->name)));
                    break;
            }
        } break;
        case ZigTypeIdVoid:
        case ZigTypeIdBool:
        case ZigTypeIdInt:
        case ZigTypeIdFloat:
        case ZigTypeIdPointer:
        case ZigTypeIdArray:
        case ZigTypeIdComptimeFloat:
        case ZigTypeIdComptimeInt:
        case ZigTypeIdUndefined:
        case ZigTypeIdNull:
        case ZigTypeIdOptional:
        case ZigTypeIdErrorUnion:
        case ZigTypeIdErrorSet:
        case ZigTypeIdVector:
            zig_panic("TODO export const value of type %s", buf_ptr(&target->value.type->name));
        case ZigTypeIdNamespace:
        case ZigTypeIdBoundFn:
        case ZigTypeIdArgTuple:
        case ZigTypeIdOpaque:
        case ZigTypeIdPromise:
            ir_add_error(ira, target,
                    buf_sprintf("invalid export target type '%s'", buf_ptr(&target->value.type->name)));
            break;
    }

    // TODO audit the various ways to use @export
    if (want_var_export && target->id == IrInstructionIdLoadPtrGen) {
        IrInstructionLoadPtrGen *load_ptr = reinterpret_cast<IrInstructionLoadPtrGen *>(target);
        if (load_ptr->ptr->id == IrInstructionIdVarPtr) {
            IrInstructionVarPtr *var_ptr = reinterpret_cast<IrInstructionVarPtr *>(load_ptr->ptr);
            ZigVar *var = var_ptr->var;
            var->linkage = global_linkage_to_var_linkage(global_linkage_id);
        }
    }

    return ir_const_void(ira, &instruction->base);
}

static bool exec_has_err_ret_trace(CodeGen *g, IrExecutable *exec) {
    ZigFn *fn_entry = exec_fn_entry(exec);
    return fn_entry != nullptr && fn_entry->calls_or_awaits_errorable_fn && g->have_err_ret_tracing;
}

static IrInstruction *ir_analyze_instruction_error_return_trace(IrAnalyze *ira,
        IrInstructionErrorReturnTrace *instruction)
{
    if (instruction->optional == IrInstructionErrorReturnTrace::Null) {
        ZigType *ptr_to_stack_trace_type = get_ptr_to_stack_trace_type(ira->codegen);
        ZigType *optional_type = get_optional_type(ira->codegen, ptr_to_stack_trace_type);
        if (!exec_has_err_ret_trace(ira->codegen, ira->new_irb.exec)) {
            IrInstruction *result = ir_const(ira, &instruction->base, optional_type);
            ConstExprValue *out_val = &result->value;
            assert(get_codegen_ptr_type(optional_type) != nullptr);
            out_val->data.x_ptr.special = ConstPtrSpecialHardCodedAddr;
            out_val->data.x_ptr.data.hard_coded_addr.addr = 0;
            return result;
        }
        IrInstruction *new_instruction = ir_build_error_return_trace(&ira->new_irb, instruction->base.scope,
                instruction->base.source_node, instruction->optional);
        new_instruction->value.type = optional_type;
        return new_instruction;
    } else {
        assert(ira->codegen->have_err_ret_tracing);
        IrInstruction *new_instruction = ir_build_error_return_trace(&ira->new_irb, instruction->base.scope,
                instruction->base.source_node, instruction->optional);
        new_instruction->value.type = get_ptr_to_stack_trace_type(ira->codegen);
        return new_instruction;
    }
}

static IrInstruction *ir_analyze_instruction_error_union(IrAnalyze *ira,
        IrInstructionErrorUnion *instruction)
{
    Error err;

    ZigType *err_set_type = ir_resolve_type(ira, instruction->err_set->child);
    if (type_is_invalid(err_set_type))
        return ira->codegen->invalid_instruction;

    ZigType *payload_type = ir_resolve_type(ira, instruction->payload->child);
    if (type_is_invalid(payload_type))
        return ira->codegen->invalid_instruction;

    if (err_set_type->id != ZigTypeIdErrorSet) {
        ir_add_error(ira, instruction->err_set->child,
            buf_sprintf("expected error set type, found type '%s'",
                buf_ptr(&err_set_type->name)));
        return ira->codegen->invalid_instruction;
    }

    if ((err = type_resolve(ira->codegen, payload_type, ResolveStatusSizeKnown)))
        return ira->codegen->invalid_instruction;
    ZigType *result_type = get_error_union_type(ira->codegen, err_set_type, payload_type);

    return ir_const_type(ira, &instruction->base, result_type);
}

IrInstruction *ir_get_implicit_allocator(IrAnalyze *ira, IrInstruction *source_instr, ImplicitAllocatorId id) {
    ZigFn *parent_fn_entry = exec_fn_entry(ira->new_irb.exec);
    if (parent_fn_entry == nullptr) {
        ir_add_error(ira, source_instr, buf_sprintf("no implicit allocator available"));
        return ira->codegen->invalid_instruction;
    }

    FnTypeId *parent_fn_type = &parent_fn_entry->type_entry->data.fn.fn_type_id;
    if (parent_fn_type->cc != CallingConventionAsync) {
        ir_add_error(ira, source_instr, buf_sprintf("async function call from non-async caller requires allocator parameter"));
        return ira->codegen->invalid_instruction;
    }

    assert(parent_fn_type->async_allocator_type != nullptr);

    switch (id) {
        case ImplicitAllocatorIdArg:
            {
                IrInstruction *result = ir_build_get_implicit_allocator(&ira->new_irb, source_instr->scope,
                        source_instr->source_node, ImplicitAllocatorIdArg);
                result->value.type = parent_fn_type->async_allocator_type;
                return result;
            }
        case ImplicitAllocatorIdLocalVar:
            {
                ZigVar *coro_allocator_var = ira->old_irb.exec->coro_allocator_var;
                assert(coro_allocator_var != nullptr);
                IrInstruction *var_ptr_inst = ir_get_var_ptr(ira, source_instr, coro_allocator_var);
                IrInstruction *result = ir_get_deref(ira, source_instr, var_ptr_inst);
                assert(result->value.type != nullptr);
                return result;
            }
    }
    zig_unreachable();
}

static IrInstruction *ir_analyze_async_call(IrAnalyze *ira, IrInstructionCall *call_instruction, ZigFn *fn_entry, ZigType *fn_type,
    IrInstruction *fn_ref, IrInstruction **casted_args, size_t arg_count, IrInstruction *async_allocator_inst)
{
    Buf *alloc_field_name = buf_create_from_str(ASYNC_ALLOC_FIELD_NAME);
    //Buf *free_field_name = buf_create_from_str("freeFn");
    assert(async_allocator_inst->value.type->id == ZigTypeIdPointer);
    ZigType *container_type = async_allocator_inst->value.type->data.pointer.child_type;
    IrInstruction *field_ptr_inst = ir_analyze_container_field_ptr(ira, alloc_field_name, &call_instruction->base,
            async_allocator_inst, container_type);
    if (type_is_invalid(field_ptr_inst->value.type)) {
        return ira->codegen->invalid_instruction;
    }
    ZigType *ptr_to_alloc_fn_type = field_ptr_inst->value.type;
    assert(ptr_to_alloc_fn_type->id == ZigTypeIdPointer);

    ZigType *alloc_fn_type = ptr_to_alloc_fn_type->data.pointer.child_type;
    if (alloc_fn_type->id != ZigTypeIdFn) {
        ir_add_error(ira, &call_instruction->base,
                buf_sprintf("expected allocation function, found '%s'", buf_ptr(&alloc_fn_type->name)));
        return ira->codegen->invalid_instruction;
    }

    ZigType *alloc_fn_return_type = alloc_fn_type->data.fn.fn_type_id.return_type;
    if (alloc_fn_return_type->id != ZigTypeIdErrorUnion) {
        ir_add_error(ira, fn_ref,
            buf_sprintf("expected allocation function to return error union, but it returns '%s'", buf_ptr(&alloc_fn_return_type->name)));
        return ira->codegen->invalid_instruction;
    }
    ZigType *alloc_fn_error_set_type = alloc_fn_return_type->data.error_union.err_set_type;
    ZigType *return_type = fn_type->data.fn.fn_type_id.return_type;
    ZigType *promise_type = get_promise_type(ira->codegen, return_type);
    ZigType *async_return_type = get_error_union_type(ira->codegen, alloc_fn_error_set_type, promise_type);

    IrInstruction *result = ir_build_call(&ira->new_irb, call_instruction->base.scope, call_instruction->base.source_node,
        fn_entry, fn_ref, arg_count, casted_args, false, FnInlineAuto, true, async_allocator_inst, nullptr);
    result->value.type = async_return_type;
    return result;
}

static bool ir_analyze_fn_call_inline_arg(IrAnalyze *ira, AstNode *fn_proto_node,
    IrInstruction *arg, Scope **exec_scope, size_t *next_proto_i)
{
    AstNode *param_decl_node = fn_proto_node->data.fn_proto.params.at(*next_proto_i);
    assert(param_decl_node->type == NodeTypeParamDecl);

    IrInstruction *casted_arg;
    if (param_decl_node->data.param_decl.var_token == nullptr) {
        AstNode *param_type_node = param_decl_node->data.param_decl.type;
        ZigType *param_type = analyze_type_expr(ira->codegen, *exec_scope, param_type_node);
        if (type_is_invalid(param_type))
            return false;

        casted_arg = ir_implicit_cast(ira, arg, param_type);
        if (type_is_invalid(casted_arg->value.type))
            return false;
    } else {
        casted_arg = arg;
    }

    ConstExprValue *arg_val = ir_resolve_const(ira, casted_arg, UndefBad);
    if (!arg_val)
        return false;

    Buf *param_name = param_decl_node->data.param_decl.name;
    ZigVar *var = add_variable(ira->codegen, param_decl_node,
        *exec_scope, param_name, true, arg_val, nullptr, arg_val->type);
    *exec_scope = var->child_scope;
    *next_proto_i += 1;

    return true;
}

static bool ir_analyze_fn_call_generic_arg(IrAnalyze *ira, AstNode *fn_proto_node,
    IrInstruction *arg, Scope **child_scope, size_t *next_proto_i,
    GenericFnTypeId *generic_id, FnTypeId *fn_type_id, IrInstruction **casted_args,
    ZigFn *impl_fn)
{
    AstNode *param_decl_node = fn_proto_node->data.fn_proto.params.at(*next_proto_i);
    assert(param_decl_node->type == NodeTypeParamDecl);
    bool is_var_args = param_decl_node->data.param_decl.is_var_args;
    bool arg_part_of_generic_id = false;
    IrInstruction *casted_arg;
    if (is_var_args) {
        arg_part_of_generic_id = true;
        casted_arg = arg;
    } else {
        if (param_decl_node->data.param_decl.var_token == nullptr) {
            AstNode *param_type_node = param_decl_node->data.param_decl.type;
            ZigType *param_type = analyze_type_expr(ira->codegen, *child_scope, param_type_node);
            if (type_is_invalid(param_type))
                return false;

            casted_arg = ir_implicit_cast(ira, arg, param_type);
            if (type_is_invalid(casted_arg->value.type))
                return false;
        } else {
            arg_part_of_generic_id = true;
            casted_arg = arg;
        }
    }

    bool comptime_arg = param_decl_node->data.param_decl.is_inline ||
        casted_arg->value.type->id == ZigTypeIdComptimeInt || casted_arg->value.type->id == ZigTypeIdComptimeFloat;

    ConstExprValue *arg_val;

    if (comptime_arg) {
        arg_part_of_generic_id = true;
        arg_val = ir_resolve_const(ira, casted_arg, UndefBad);
        if (!arg_val)
            return false;
    } else {
        arg_val = create_const_runtime(casted_arg->value.type);
    }
    if (arg_part_of_generic_id) {
        generic_id->params[generic_id->param_count] = *arg_val;
        generic_id->param_count += 1;
    }

    Buf *param_name = param_decl_node->data.param_decl.name;
    if (!param_name) return false;
    if (!is_var_args) {
        ZigVar *var = add_variable(ira->codegen, param_decl_node,
            *child_scope, param_name, true, arg_val, nullptr, arg_val->type);
        *child_scope = var->child_scope;
        var->shadowable = !comptime_arg;

        *next_proto_i += 1;
    } else if (casted_arg->value.type->id == ZigTypeIdComptimeInt ||
            casted_arg->value.type->id == ZigTypeIdComptimeFloat)
    {
        ir_add_error(ira, casted_arg,
            buf_sprintf("compiler bug: integer and float literals in var args function must be casted. https://github.com/ziglang/zig/issues/557"));
        return false;
    }

    if (!comptime_arg) {
        switch (type_requires_comptime(ira->codegen, casted_arg->value.type)) {
        case ReqCompTimeYes:
            ir_add_error(ira, casted_arg,
                buf_sprintf("parameter of type '%s' requires comptime", buf_ptr(&casted_arg->value.type->name)));
            return false;
        case ReqCompTimeInvalid:
            return false;
        case ReqCompTimeNo:
            break;
        }

        casted_args[fn_type_id->param_count] = casted_arg;
        FnTypeParamInfo *param_info = &fn_type_id->param_info[fn_type_id->param_count];
        param_info->type = casted_arg->value.type;
        param_info->is_noalias = param_decl_node->data.param_decl.is_noalias;
        impl_fn->param_source_nodes[fn_type_id->param_count] = param_decl_node;
        fn_type_id->param_count += 1;
    }

    return true;
}

static ZigVar *get_fn_var_by_index(ZigFn *fn_entry, size_t index) {
    size_t next_var_i = 0;
    FnGenParamInfo *gen_param_info = fn_entry->type_entry->data.fn.gen_param_info;
    assert(gen_param_info != nullptr);
    for (size_t param_i = 0; param_i < index; param_i += 1) {
        FnGenParamInfo *info = &gen_param_info[param_i];
        if (info->gen_index == SIZE_MAX)
            continue;

        next_var_i += 1;
    }
    FnGenParamInfo *info = &gen_param_info[index];
    if (info->gen_index == SIZE_MAX)
        return nullptr;

    return fn_entry->variable_list.at(next_var_i);
}

static IrInstruction *ir_get_var_ptr(IrAnalyze *ira, IrInstruction *instruction, ZigVar *var) {
    while (var->next_var != nullptr) {
        var = var->next_var;
    }

    if (var->mem_slot_index != SIZE_MAX && var->owner_exec->analysis == nullptr) {
        assert(ira->codegen->errors.length != 0);
        return ira->codegen->invalid_instruction;
    }
    if (var->var_type == nullptr || type_is_invalid(var->var_type))
        return ira->codegen->invalid_instruction;

    ConstExprValue *mem_slot = nullptr;

    bool comptime_var_mem = ir_get_var_is_comptime(var);
    bool linkage_makes_it_runtime = var->linkage == VarLinkageExternal;
    bool is_const = var->src_is_const;
    bool is_volatile = false;

    if (linkage_makes_it_runtime)
        goto no_mem_slot;

    if (var->const_value->special == ConstValSpecialStatic) {
        mem_slot = var->const_value;
    } else {
        if (var->mem_slot_index != SIZE_MAX && (comptime_var_mem || var->gen_is_const)) {
            // find the relevant exec_context
            assert(var->owner_exec != nullptr);
            assert(var->owner_exec->analysis != nullptr);
            IrExecContext *exec_context = &var->owner_exec->analysis->exec_context;
            assert(var->mem_slot_index < exec_context->mem_slot_list.length);
            mem_slot = exec_context->mem_slot_list.at(var->mem_slot_index);
        }
    }

    if (mem_slot != nullptr) {
        switch (mem_slot->special) {
            case ConstValSpecialRuntime:
                goto no_mem_slot;
            case ConstValSpecialStatic: // fallthrough
            case ConstValSpecialUndef: {
                ConstPtrMut ptr_mut;
                if (comptime_var_mem) {
                    ptr_mut = ConstPtrMutComptimeVar;
                } else if (var->gen_is_const) {
                    ptr_mut = ConstPtrMutComptimeConst;
                } else {
                    assert(!comptime_var_mem);
                    ptr_mut = ConstPtrMutRuntimeVar;
                }
                return ir_get_const_ptr(ira, instruction, mem_slot, var->var_type,
                        ptr_mut, is_const, is_volatile, var->align_bytes);
            }
        }
        zig_unreachable();
    }

no_mem_slot:

    IrInstruction *var_ptr_instruction = ir_build_var_ptr(&ira->new_irb,
            instruction->scope, instruction->source_node, var);
    var_ptr_instruction->value.type = get_pointer_to_type_extra(ira->codegen, var->var_type,
            var->src_is_const, is_volatile, PtrLenSingle, var->align_bytes, 0, 0);

    bool in_fn_scope = (scope_fn_entry(var->parent_scope) != nullptr);
    var_ptr_instruction->value.data.rh_ptr = in_fn_scope ? RuntimeHintPtrStack : RuntimeHintPtrNonStack;

    return var_ptr_instruction;
}

static IrInstruction *ir_analyze_store_ptr(IrAnalyze *ira, IrInstruction *source_instr,
        IrInstruction *ptr, IrInstruction *uncasted_value)
{
    assert(ptr->value.type->id == ZigTypeIdPointer);

    if (ptr->value.data.x_ptr.special == ConstPtrSpecialDiscard) {
        return ir_const_void(ira, source_instr);
    }

    ZigType *child_type = ptr->value.type->data.pointer.child_type;

    if (ptr->value.type->data.pointer.is_const && !source_instr->is_gen) {
        ir_add_error(ira, source_instr, buf_sprintf("cannot assign to constant"));
        return ira->codegen->invalid_instruction;
    }

    IrInstruction *value = ir_implicit_cast(ira, uncasted_value, child_type);
    if (value == ira->codegen->invalid_instruction)
        return ira->codegen->invalid_instruction;

    switch (type_has_one_possible_value(ira->codegen, child_type)) {
        case OnePossibleValueInvalid:
            return ira->codegen->invalid_instruction;
        case OnePossibleValueYes:
            return ir_const_void(ira, source_instr);
        case OnePossibleValueNo:
            break;
    }

    if (instr_is_comptime(ptr) && ptr->value.data.x_ptr.special != ConstPtrSpecialHardCodedAddr) {
        if (ptr->value.data.x_ptr.mut == ConstPtrMutComptimeConst) {
            ir_add_error(ira, source_instr, buf_sprintf("cannot assign to constant"));
            return ira->codegen->invalid_instruction;
        }
        if (ptr->value.data.x_ptr.mut == ConstPtrMutComptimeVar) {
            if (instr_is_comptime(value)) {
                ConstExprValue *dest_val = const_ptr_pointee(ira, ira->codegen, &ptr->value, source_instr->source_node);
                if (dest_val == nullptr)
                    return ira->codegen->invalid_instruction;
                if (dest_val->special != ConstValSpecialRuntime) {
                    // TODO this allows a value stored to have the original value modified and then
                    // have that affect what should be a copy. We need some kind of advanced copy-on-write
                    // system to make these two tests pass at the same time:
                    // * "string literal used as comptime slice is memoized"
                    // * "comptime modification of const struct field" - except modified to avoid
                    //   ConstPtrMutComptimeVar, thus defeating the logic below.
                    bool same_global_refs = ptr->value.data.x_ptr.mut != ConstPtrMutComptimeVar;
                    copy_const_val(dest_val, &value->value, same_global_refs);
                    if (!ira->new_irb.current_basic_block->must_be_comptime_source_instr) {
                        ira->new_irb.current_basic_block->must_be_comptime_source_instr = source_instr;
                    }
                    return ir_const_void(ira, source_instr);
                }
            }
            ir_add_error(ira, source_instr,
                    buf_sprintf("cannot store runtime value in compile time variable"));
            ConstExprValue *dest_val = const_ptr_pointee_unchecked(ira->codegen, &ptr->value);
            dest_val->type = ira->codegen->builtin_types.entry_invalid;

            return ira->codegen->invalid_instruction;
        }
    }

    switch (type_requires_comptime(ira->codegen, child_type)) {
        case ReqCompTimeInvalid:
            return ira->codegen->invalid_instruction;
        case ReqCompTimeYes:
            switch (type_has_one_possible_value(ira->codegen, ptr->value.type)) {
                case OnePossibleValueInvalid:
                    return ira->codegen->invalid_instruction;
                case OnePossibleValueNo:
                    ir_add_error(ira, source_instr,
                            buf_sprintf("cannot store runtime value in type '%s'", buf_ptr(&child_type->name)));
                    return ira->codegen->invalid_instruction;
                case OnePossibleValueYes:
                    return ir_const_void(ira, source_instr);
            }
            zig_unreachable();
        case ReqCompTimeNo:
            break;
    }

    IrInstruction *result = ir_build_store_ptr(&ira->new_irb, source_instr->scope, source_instr->source_node,
        ptr, value);
    result->value.type = ira->codegen->builtin_types.entry_void;
    return result;
}

static IrInstruction *ir_analyze_fn_call(IrAnalyze *ira, IrInstructionCall *call_instruction,
    ZigFn *fn_entry, ZigType *fn_type, IrInstruction *fn_ref,
    IrInstruction *first_arg_ptr, bool comptime_fn_call, FnInline fn_inline)
{
    Error err;
    FnTypeId *fn_type_id = &fn_type->data.fn.fn_type_id;
    size_t first_arg_1_or_0 = first_arg_ptr ? 1 : 0;

    // for extern functions, the var args argument is not counted.
    // for zig functions, it is.
    size_t var_args_1_or_0;
    if (fn_type_id->cc == CallingConventionC) {
        var_args_1_or_0 = 0;
    } else {
        var_args_1_or_0 = fn_type_id->is_var_args ? 1 : 0;
    }
    size_t src_param_count = fn_type_id->param_count - var_args_1_or_0;

    size_t call_param_count = call_instruction->arg_count + first_arg_1_or_0;
    for (size_t i = 0; i < call_instruction->arg_count; i += 1) {
        ConstExprValue *arg_tuple_value = &call_instruction->args[i]->child->value;
        if (arg_tuple_value->type->id == ZigTypeIdArgTuple) {
            call_param_count -= 1;
            call_param_count += arg_tuple_value->data.x_arg_tuple.end_index -
                arg_tuple_value->data.x_arg_tuple.start_index;
        }
    }
    AstNode *source_node = call_instruction->base.source_node;

    AstNode *fn_proto_node = fn_entry ? fn_entry->proto_node : nullptr;;

    if (fn_type_id->cc == CallingConventionNaked) {
        ErrorMsg *msg = ir_add_error(ira, fn_ref, buf_sprintf("unable to call function with naked calling convention"));
        if (fn_proto_node) {
            add_error_note(ira->codegen, msg, fn_proto_node, buf_sprintf("declared here"));
        }
        return ira->codegen->invalid_instruction;
    }
    if (fn_type_id->cc == CallingConventionAsync && !call_instruction->is_async) {
        ErrorMsg *msg = ir_add_error(ira, fn_ref, buf_sprintf("must use async keyword to call async function"));
        if (fn_proto_node) {
            add_error_note(ira->codegen, msg, fn_proto_node, buf_sprintf("declared here"));
        }
        return ira->codegen->invalid_instruction;
    }
    if (fn_type_id->cc != CallingConventionAsync && call_instruction->is_async) {
        ErrorMsg *msg = ir_add_error(ira, fn_ref, buf_sprintf("cannot use async keyword to call non-async function"));
        if (fn_proto_node) {
            add_error_note(ira->codegen, msg, fn_proto_node, buf_sprintf("declared here"));
        }
        return ira->codegen->invalid_instruction;
    }


    if (fn_type_id->is_var_args) {
        if (call_param_count < src_param_count) {
            ErrorMsg *msg = ir_add_error_node(ira, source_node,
                buf_sprintf("expected at least %" ZIG_PRI_usize " arguments, found %" ZIG_PRI_usize "", src_param_count, call_param_count));
            if (fn_proto_node) {
                add_error_note(ira->codegen, msg, fn_proto_node,
                    buf_sprintf("declared here"));
            }
            return ira->codegen->invalid_instruction;
        }
    } else if (src_param_count != call_param_count) {
        ErrorMsg *msg = ir_add_error_node(ira, source_node,
            buf_sprintf("expected %" ZIG_PRI_usize " arguments, found %" ZIG_PRI_usize "", src_param_count, call_param_count));
        if (fn_proto_node) {
            add_error_note(ira->codegen, msg, fn_proto_node,
                buf_sprintf("declared here"));
        }
        return ira->codegen->invalid_instruction;
    }

    if (comptime_fn_call) {
        // No special handling is needed for compile time evaluation of generic functions.
        if (!fn_entry || fn_entry->body_node == nullptr) {
            ir_add_error(ira, fn_ref, buf_sprintf("unable to evaluate constant expression"));
            return ira->codegen->invalid_instruction;
        }

        if (!ir_emit_backward_branch(ira, &call_instruction->base))
            return ira->codegen->invalid_instruction;

        // Fork a scope of the function with known values for the parameters.
        Scope *exec_scope = &fn_entry->fndef_scope->base;

        size_t next_proto_i = 0;
        if (first_arg_ptr) {
            assert(first_arg_ptr->value.type->id == ZigTypeIdPointer);

            bool first_arg_known_bare = false;
            if (fn_type_id->next_param_index >= 1) {
                ZigType *param_type = fn_type_id->param_info[next_proto_i].type;
                if (type_is_invalid(param_type))
                    return ira->codegen->invalid_instruction;
                first_arg_known_bare = param_type->id != ZigTypeIdPointer;
            }

            IrInstruction *first_arg;
            if (!first_arg_known_bare && handle_is_ptr(first_arg_ptr->value.type->data.pointer.child_type)) {
                first_arg = first_arg_ptr;
            } else {
                first_arg = ir_get_deref(ira, first_arg_ptr, first_arg_ptr);
                if (type_is_invalid(first_arg->value.type))
                    return ira->codegen->invalid_instruction;
            }

            if (!ir_analyze_fn_call_inline_arg(ira, fn_proto_node, first_arg, &exec_scope, &next_proto_i))
                return ira->codegen->invalid_instruction;
        }

        if (fn_proto_node->data.fn_proto.is_var_args) {
            ir_add_error(ira, &call_instruction->base,
                    buf_sprintf("compiler bug: unable to call var args function at compile time. https://github.com/ziglang/zig/issues/313"));
            return ira->codegen->invalid_instruction;
        }


        for (size_t call_i = 0; call_i < call_instruction->arg_count; call_i += 1) {
            IrInstruction *old_arg = call_instruction->args[call_i]->child;
            if (type_is_invalid(old_arg->value.type))
                return ira->codegen->invalid_instruction;

            if (!ir_analyze_fn_call_inline_arg(ira, fn_proto_node, old_arg, &exec_scope, &next_proto_i))
                return ira->codegen->invalid_instruction;
        }

        AstNode *return_type_node = fn_proto_node->data.fn_proto.return_type;
        ZigType *specified_return_type = analyze_type_expr(ira->codegen, exec_scope, return_type_node);
        if (type_is_invalid(specified_return_type))
            return ira->codegen->invalid_instruction;
        ZigType *return_type;
        ZigType *inferred_err_set_type = nullptr;
        if (fn_proto_node->data.fn_proto.auto_err_set) {
            inferred_err_set_type = get_auto_err_set_type(ira->codegen, fn_entry);
            if ((err = type_resolve(ira->codegen, specified_return_type, ResolveStatusSizeKnown)))
                return ira->codegen->invalid_instruction;
            return_type = get_error_union_type(ira->codegen, inferred_err_set_type, specified_return_type);
        } else {
            return_type = specified_return_type;
        }

        bool cacheable = fn_eval_cacheable(exec_scope, return_type);
        ConstExprValue *result = nullptr;
        if (cacheable) {
            auto entry = ira->codegen->memoized_fn_eval_table.maybe_get(exec_scope);
            if (entry)
                result = entry->value;
        }

        if (result == nullptr) {
            // Analyze the fn body block like any other constant expression.
            AstNode *body_node = fn_entry->body_node;
            result = ir_eval_const_value(ira->codegen, exec_scope, body_node, return_type,
                ira->new_irb.exec->backward_branch_count, ira->new_irb.exec->backward_branch_quota, fn_entry,
                nullptr, call_instruction->base.source_node, nullptr, ira->new_irb.exec, return_type_node);

            if (inferred_err_set_type != nullptr) {
                inferred_err_set_type->data.error_set.infer_fn = nullptr;
                if (result->type->id == ZigTypeIdErrorUnion) {
                    ErrorTableEntry *err = result->data.x_err_union.error_set->data.x_err_set;
                    if (err != nullptr) {
                        inferred_err_set_type->data.error_set.err_count = 1;
                        inferred_err_set_type->data.error_set.errors = allocate<ErrorTableEntry *>(1);
                        inferred_err_set_type->data.error_set.errors[0] = err;
                    }
                    ZigType *fn_inferred_err_set_type = result->type->data.error_union.err_set_type;
                    inferred_err_set_type->data.error_set.err_count = fn_inferred_err_set_type->data.error_set.err_count;
                    inferred_err_set_type->data.error_set.errors = fn_inferred_err_set_type->data.error_set.errors;
                } else if (result->type->id == ZigTypeIdErrorSet) {
                    inferred_err_set_type->data.error_set.err_count = result->type->data.error_set.err_count;
                    inferred_err_set_type->data.error_set.errors = result->type->data.error_set.errors;
                }
            }

            if (cacheable) {
                ira->codegen->memoized_fn_eval_table.put(exec_scope, result);
            }

            if (type_is_invalid(result->type))
                return ira->codegen->invalid_instruction;
        }

        IrInstruction *new_instruction = ir_const(ira, &call_instruction->base, result->type);
        copy_const_val(&new_instruction->value, result, true);
        new_instruction->value.type = return_type;
        return ir_finish_anal(ira, new_instruction);
    }

    IrInstruction *casted_new_stack = nullptr;
    if (call_instruction->new_stack != nullptr) {
        ZigType *u8_ptr = get_pointer_to_type_extra(ira->codegen, ira->codegen->builtin_types.entry_u8,
                false, false, PtrLenUnknown, 0, 0, 0);
        ZigType *u8_slice = get_slice_type(ira->codegen, u8_ptr);
        IrInstruction *new_stack = call_instruction->new_stack->child;
        if (type_is_invalid(new_stack->value.type))
            return ira->codegen->invalid_instruction;

        casted_new_stack = ir_implicit_cast(ira, new_stack, u8_slice);
        if (type_is_invalid(casted_new_stack->value.type))
            return ira->codegen->invalid_instruction;
    }

    if (fn_type->data.fn.is_generic) {
        if (!fn_entry) {
            ir_add_error(ira, call_instruction->fn_ref,
                buf_sprintf("calling a generic function requires compile-time known function value"));
            return ira->codegen->invalid_instruction;
        }

        // Count the arguments of the function type id we are creating
        size_t new_fn_arg_count = first_arg_1_or_0;
        for (size_t call_i = 0; call_i < call_instruction->arg_count; call_i += 1) {
            IrInstruction *arg = call_instruction->args[call_i]->child;
            if (type_is_invalid(arg->value.type))
                return ira->codegen->invalid_instruction;

            if (arg->value.type->id == ZigTypeIdArgTuple) {
                new_fn_arg_count += arg->value.data.x_arg_tuple.end_index - arg->value.data.x_arg_tuple.start_index;
            } else {
                new_fn_arg_count += 1;
            }
        }

        IrInstruction **casted_args = allocate<IrInstruction *>(new_fn_arg_count);

        // Fork a scope of the function with known values for the parameters.
        Scope *parent_scope = fn_entry->fndef_scope->base.parent;
        ZigFn *impl_fn = create_fn(ira->codegen, fn_proto_node);
        impl_fn->param_source_nodes = allocate<AstNode *>(new_fn_arg_count);
        buf_init_from_buf(&impl_fn->symbol_name, &fn_entry->symbol_name);
        impl_fn->fndef_scope = create_fndef_scope(ira->codegen, impl_fn->body_node, parent_scope, impl_fn);
        impl_fn->child_scope = &impl_fn->fndef_scope->base;
        FnTypeId inst_fn_type_id = {0};
        init_fn_type_id(&inst_fn_type_id, fn_proto_node, new_fn_arg_count);
        inst_fn_type_id.param_count = 0;
        inst_fn_type_id.is_var_args = false;

        // TODO maybe GenericFnTypeId can be replaced with using the child_scope directly
        // as the key in generic_table
        GenericFnTypeId *generic_id = allocate<GenericFnTypeId>(1);
        generic_id->fn_entry = fn_entry;
        generic_id->param_count = 0;
        generic_id->params = create_const_vals(new_fn_arg_count);
        size_t next_proto_i = 0;

        if (first_arg_ptr) {
            assert(first_arg_ptr->value.type->id == ZigTypeIdPointer);

            bool first_arg_known_bare = false;
            if (fn_type_id->next_param_index >= 1) {
                ZigType *param_type = fn_type_id->param_info[next_proto_i].type;
                if (type_is_invalid(param_type))
                    return ira->codegen->invalid_instruction;
                first_arg_known_bare = param_type->id != ZigTypeIdPointer;
            }

            IrInstruction *first_arg;
            if (!first_arg_known_bare && handle_is_ptr(first_arg_ptr->value.type->data.pointer.child_type)) {
                first_arg = first_arg_ptr;
            } else {
                first_arg = ir_get_deref(ira, first_arg_ptr, first_arg_ptr);
                if (type_is_invalid(first_arg->value.type))
                    return ira->codegen->invalid_instruction;
            }

            if (!ir_analyze_fn_call_generic_arg(ira, fn_proto_node, first_arg, &impl_fn->child_scope,
                &next_proto_i, generic_id, &inst_fn_type_id, casted_args, impl_fn))
            {
                return ira->codegen->invalid_instruction;
            }
        }

        bool found_first_var_arg = false;
        size_t first_var_arg;

        ZigFn *parent_fn_entry = exec_fn_entry(ira->new_irb.exec);
        assert(parent_fn_entry);
        for (size_t call_i = 0; call_i < call_instruction->arg_count; call_i += 1) {
            IrInstruction *arg = call_instruction->args[call_i]->child;
            if (type_is_invalid(arg->value.type))
                return ira->codegen->invalid_instruction;

            if (arg->value.type->id == ZigTypeIdArgTuple) {
                for (size_t arg_tuple_i = arg->value.data.x_arg_tuple.start_index;
                    arg_tuple_i < arg->value.data.x_arg_tuple.end_index; arg_tuple_i += 1)
                {
                    AstNode *param_decl_node = fn_proto_node->data.fn_proto.params.at(next_proto_i);
                    assert(param_decl_node->type == NodeTypeParamDecl);
                    bool is_var_args = param_decl_node->data.param_decl.is_var_args;
                    if (is_var_args && !found_first_var_arg) {
                        first_var_arg = inst_fn_type_id.param_count;
                        found_first_var_arg = true;
                    }

                    ZigVar *arg_var = get_fn_var_by_index(parent_fn_entry, arg_tuple_i);
                    if (arg_var == nullptr) {
                        ir_add_error(ira, arg,
                            buf_sprintf("compiler bug: var args can't handle void. https://github.com/ziglang/zig/issues/557"));
                        return ira->codegen->invalid_instruction;
                    }
                    IrInstruction *arg_var_ptr_inst = ir_get_var_ptr(ira, arg, arg_var);
                    if (type_is_invalid(arg_var_ptr_inst->value.type))
                        return ira->codegen->invalid_instruction;

                    IrInstruction *arg_tuple_arg = ir_get_deref(ira, arg, arg_var_ptr_inst);
                    if (type_is_invalid(arg_tuple_arg->value.type))
                        return ira->codegen->invalid_instruction;

                    if (!ir_analyze_fn_call_generic_arg(ira, fn_proto_node, arg_tuple_arg, &impl_fn->child_scope,
                        &next_proto_i, generic_id, &inst_fn_type_id, casted_args, impl_fn))
                    {
                        return ira->codegen->invalid_instruction;
                    }
                }
            } else {
                AstNode *param_decl_node = fn_proto_node->data.fn_proto.params.at(next_proto_i);
                assert(param_decl_node->type == NodeTypeParamDecl);
                bool is_var_args = param_decl_node->data.param_decl.is_var_args;
                if (is_var_args && !found_first_var_arg) {
                    first_var_arg = inst_fn_type_id.param_count;
                    found_first_var_arg = true;
                }

                if (!ir_analyze_fn_call_generic_arg(ira, fn_proto_node, arg, &impl_fn->child_scope,
                    &next_proto_i, generic_id, &inst_fn_type_id, casted_args, impl_fn))
                {
                    return ira->codegen->invalid_instruction;
                }
            }
        }

        if (fn_proto_node->data.fn_proto.is_var_args) {
            AstNode *param_decl_node = fn_proto_node->data.fn_proto.params.at(next_proto_i);
            Buf *param_name = param_decl_node->data.param_decl.name;

            if (!found_first_var_arg) {
                first_var_arg = inst_fn_type_id.param_count;
            }

            ConstExprValue *var_args_val = create_const_arg_tuple(ira->codegen,
                    first_var_arg, inst_fn_type_id.param_count);
            ZigVar *var = add_variable(ira->codegen, param_decl_node,
                impl_fn->child_scope, param_name, true, var_args_val, nullptr, var_args_val->type);
            impl_fn->child_scope = var->child_scope;
        }

        if (fn_proto_node->data.fn_proto.align_expr != nullptr) {
            ConstExprValue *align_result = ir_eval_const_value(ira->codegen, impl_fn->child_scope,
                    fn_proto_node->data.fn_proto.align_expr, get_align_amt_type(ira->codegen),
                    ira->new_irb.exec->backward_branch_count, ira->new_irb.exec->backward_branch_quota,
                    nullptr, nullptr, fn_proto_node->data.fn_proto.align_expr, nullptr, ira->new_irb.exec, nullptr);
            IrInstructionConst *const_instruction = ir_create_instruction<IrInstructionConst>(&ira->new_irb,
                    impl_fn->child_scope, fn_proto_node->data.fn_proto.align_expr);
            const_instruction->base.value = *align_result;

            uint32_t align_bytes = 0;
            ir_resolve_align(ira, &const_instruction->base, &align_bytes);
            impl_fn->align_bytes = align_bytes;
            inst_fn_type_id.alignment = align_bytes;
        }

        if (fn_proto_node->data.fn_proto.return_var_token == nullptr) {
            AstNode *return_type_node = fn_proto_node->data.fn_proto.return_type;
            ZigType *specified_return_type = analyze_type_expr(ira->codegen, impl_fn->child_scope, return_type_node);
            if (type_is_invalid(specified_return_type))
                return ira->codegen->invalid_instruction;
            if (fn_proto_node->data.fn_proto.auto_err_set) {
                ZigType *inferred_err_set_type = get_auto_err_set_type(ira->codegen, impl_fn);
                if ((err = type_resolve(ira->codegen, specified_return_type, ResolveStatusSizeKnown)))
                    return ira->codegen->invalid_instruction;
                inst_fn_type_id.return_type = get_error_union_type(ira->codegen, inferred_err_set_type, specified_return_type);
            } else {
                inst_fn_type_id.return_type = specified_return_type;
            }

            switch (type_requires_comptime(ira->codegen, specified_return_type)) {
            case ReqCompTimeYes:
                // Throw out our work and call the function as if it were comptime.
                return ir_analyze_fn_call(ira, call_instruction, fn_entry, fn_type, fn_ref, first_arg_ptr,
                        true, FnInlineAuto);
            case ReqCompTimeInvalid:
                return ira->codegen->invalid_instruction;
            case ReqCompTimeNo:
                break;
            }
        }
        IrInstruction *async_allocator_inst = nullptr;
        if (call_instruction->is_async) {
            AstNode *async_allocator_type_node = fn_proto_node->data.fn_proto.async_allocator_type;
            if (async_allocator_type_node != nullptr) {
                ZigType *async_allocator_type = analyze_type_expr(ira->codegen, impl_fn->child_scope, async_allocator_type_node);
                if (type_is_invalid(async_allocator_type))
                    return ira->codegen->invalid_instruction;
                inst_fn_type_id.async_allocator_type = async_allocator_type;
            }
            IrInstruction *uncasted_async_allocator_inst;
            if (call_instruction->async_allocator == nullptr) {
                uncasted_async_allocator_inst = ir_get_implicit_allocator(ira, &call_instruction->base,
                        ImplicitAllocatorIdLocalVar);
                if (type_is_invalid(uncasted_async_allocator_inst->value.type))
                    return ira->codegen->invalid_instruction;
            } else {
                uncasted_async_allocator_inst = call_instruction->async_allocator->child;
                if (type_is_invalid(uncasted_async_allocator_inst->value.type))
                    return ira->codegen->invalid_instruction;
            }
            if (inst_fn_type_id.async_allocator_type == nullptr) {
                inst_fn_type_id.async_allocator_type = uncasted_async_allocator_inst->value.type;
            }
            async_allocator_inst = ir_implicit_cast(ira, uncasted_async_allocator_inst, inst_fn_type_id.async_allocator_type);
            if (type_is_invalid(async_allocator_inst->value.type))
                return ira->codegen->invalid_instruction;
        }

        auto existing_entry = ira->codegen->generic_table.put_unique(generic_id, impl_fn);
        if (existing_entry) {
            // throw away all our work and use the existing function
            impl_fn = existing_entry->value;
        } else {
            // finish instantiating the function
            impl_fn->type_entry = get_fn_type(ira->codegen, &inst_fn_type_id);
            if (type_is_invalid(impl_fn->type_entry))
                return ira->codegen->invalid_instruction;

            impl_fn->ir_executable.source_node = call_instruction->base.source_node;
            impl_fn->ir_executable.parent_exec = ira->new_irb.exec;
            impl_fn->analyzed_executable.source_node = call_instruction->base.source_node;
            impl_fn->analyzed_executable.parent_exec = ira->new_irb.exec;
            impl_fn->analyzed_executable.backward_branch_quota = ira->new_irb.exec->backward_branch_quota;
            impl_fn->analyzed_executable.is_generic_instantiation = true;

            ira->codegen->fn_defs.append(impl_fn);
        }

        FnTypeId *impl_fn_type_id = &impl_fn->type_entry->data.fn.fn_type_id;
        if (fn_type_can_fail(impl_fn_type_id)) {
            parent_fn_entry->calls_or_awaits_errorable_fn = true;
        }

        size_t impl_param_count = impl_fn_type_id->param_count;
        if (call_instruction->is_async) {
            IrInstruction *result = ir_analyze_async_call(ira, call_instruction, impl_fn, impl_fn->type_entry,
                    fn_ref, casted_args, impl_param_count, async_allocator_inst);
            ir_add_alloca(ira, result, result->value.type);
            return ir_finish_anal(ira, result);
        }

        assert(async_allocator_inst == nullptr);
        IrInstruction *new_call_instruction = ir_build_call(&ira->new_irb,
                call_instruction->base.scope, call_instruction->base.source_node,
                impl_fn, nullptr, impl_param_count, casted_args, false, fn_inline,
                call_instruction->is_async, nullptr, casted_new_stack);
        new_call_instruction->value.type = impl_fn_type_id->return_type;

        ir_add_alloca(ira, new_call_instruction, impl_fn_type_id->return_type);

        return ir_finish_anal(ira, new_call_instruction);
    }

    ZigFn *parent_fn_entry = exec_fn_entry(ira->new_irb.exec);
    assert(fn_type_id->return_type != nullptr);
    assert(parent_fn_entry != nullptr);
    if (fn_type_can_fail(fn_type_id)) {
        parent_fn_entry->calls_or_awaits_errorable_fn = true;
    }


    IrInstruction **casted_args = allocate<IrInstruction *>(call_param_count);
    size_t next_arg_index = 0;
    if (first_arg_ptr) {
        assert(first_arg_ptr->value.type->id == ZigTypeIdPointer);

        ZigType *param_type = fn_type_id->param_info[next_arg_index].type;
        if (type_is_invalid(param_type))
            return ira->codegen->invalid_instruction;

        IrInstruction *first_arg;
        if (param_type->id == ZigTypeIdPointer &&
            handle_is_ptr(first_arg_ptr->value.type->data.pointer.child_type))
        {
            first_arg = first_arg_ptr;
        } else {
            first_arg = ir_get_deref(ira, first_arg_ptr, first_arg_ptr);
            if (type_is_invalid(first_arg->value.type))
                return ira->codegen->invalid_instruction;
        }

        IrInstruction *casted_arg = ir_implicit_cast(ira, first_arg, param_type);
        if (type_is_invalid(casted_arg->value.type))
            return ira->codegen->invalid_instruction;

        casted_args[next_arg_index] = casted_arg;
        next_arg_index += 1;
    }
    for (size_t call_i = 0; call_i < call_instruction->arg_count; call_i += 1) {
        IrInstruction *old_arg = call_instruction->args[call_i]->child;
        if (type_is_invalid(old_arg->value.type))
            return ira->codegen->invalid_instruction;
        IrInstruction *casted_arg;
        if (next_arg_index < src_param_count) {
            ZigType *param_type = fn_type_id->param_info[next_arg_index].type;
            if (type_is_invalid(param_type))
                return ira->codegen->invalid_instruction;
            casted_arg = ir_implicit_cast(ira, old_arg, param_type);
            if (type_is_invalid(casted_arg->value.type))
                return ira->codegen->invalid_instruction;
        } else {
            casted_arg = old_arg;
        }

        casted_args[next_arg_index] = casted_arg;
        next_arg_index += 1;
    }

    assert(next_arg_index == call_param_count);

    ZigType *return_type = fn_type_id->return_type;
    if (type_is_invalid(return_type))
        return ira->codegen->invalid_instruction;

    if (call_instruction->is_async) {
        IrInstruction *uncasted_async_allocator_inst;
        if (call_instruction->async_allocator == nullptr) {
            uncasted_async_allocator_inst = ir_get_implicit_allocator(ira, &call_instruction->base,
                    ImplicitAllocatorIdLocalVar);
            if (type_is_invalid(uncasted_async_allocator_inst->value.type))
                return ira->codegen->invalid_instruction;
        } else {
            uncasted_async_allocator_inst = call_instruction->async_allocator->child;
            if (type_is_invalid(uncasted_async_allocator_inst->value.type))
                return ira->codegen->invalid_instruction;

        }
        IrInstruction *async_allocator_inst = ir_implicit_cast(ira, uncasted_async_allocator_inst, fn_type_id->async_allocator_type);
        if (type_is_invalid(async_allocator_inst->value.type))
            return ira->codegen->invalid_instruction;

        IrInstruction *result = ir_analyze_async_call(ira, call_instruction, fn_entry, fn_type, fn_ref,
                casted_args, call_param_count, async_allocator_inst);
        ir_add_alloca(ira, result, result->value.type);
        return ir_finish_anal(ira, result);
    }

    if (fn_entry != nullptr && fn_entry->fn_inline == FnInlineAlways && fn_inline == FnInlineNever) {
        ir_add_error(ira, &call_instruction->base,
            buf_sprintf("no-inline call of inline function"));
        return ira->codegen->invalid_instruction;
    }

    IrInstruction *new_call_instruction = ir_build_call(&ira->new_irb,
            call_instruction->base.scope, call_instruction->base.source_node,
            fn_entry, fn_ref, call_param_count, casted_args, false, fn_inline, false, nullptr, casted_new_stack);
    new_call_instruction->value.type = return_type;
    ir_add_alloca(ira, new_call_instruction, return_type);
    return ir_finish_anal(ira, new_call_instruction);
}

static IrInstruction *ir_analyze_instruction_call(IrAnalyze *ira, IrInstructionCall *call_instruction) {
    IrInstruction *fn_ref = call_instruction->fn_ref->child;
    if (type_is_invalid(fn_ref->value.type))
        return ira->codegen->invalid_instruction;

    bool is_comptime = call_instruction->is_comptime ||
        ir_should_inline(ira->new_irb.exec, call_instruction->base.scope);

    if (is_comptime || instr_is_comptime(fn_ref)) {
        if (fn_ref->value.type->id == ZigTypeIdMetaType) {
            ZigType *dest_type = ir_resolve_type(ira, fn_ref);
            if (type_is_invalid(dest_type))
                return ira->codegen->invalid_instruction;

            size_t actual_param_count = call_instruction->arg_count;

            if (actual_param_count != 1) {
                ir_add_error_node(ira, call_instruction->base.source_node,
                        buf_sprintf("cast expression expects exactly one parameter"));
                return ira->codegen->invalid_instruction;
            }

            IrInstruction *arg = call_instruction->args[0]->child;

            IrInstruction *cast_instruction = ir_analyze_cast(ira, &call_instruction->base, dest_type, arg);
            if (type_is_invalid(cast_instruction->value.type))
                return ira->codegen->invalid_instruction;
            return ir_finish_anal(ira, cast_instruction);
        } else if (fn_ref->value.type->id == ZigTypeIdFn) {
            ZigFn *fn_table_entry = ir_resolve_fn(ira, fn_ref);
            if (fn_table_entry == nullptr)
                return ira->codegen->invalid_instruction;
            return ir_analyze_fn_call(ira, call_instruction, fn_table_entry, fn_table_entry->type_entry,
                fn_ref, nullptr, is_comptime, call_instruction->fn_inline);
        } else if (fn_ref->value.type->id == ZigTypeIdBoundFn) {
            assert(fn_ref->value.special == ConstValSpecialStatic);
            ZigFn *fn_table_entry = fn_ref->value.data.x_bound_fn.fn;
            IrInstruction *first_arg_ptr = fn_ref->value.data.x_bound_fn.first_arg;
            return ir_analyze_fn_call(ira, call_instruction, fn_table_entry, fn_table_entry->type_entry,
                fn_ref, first_arg_ptr, is_comptime, call_instruction->fn_inline);
        } else {
            ir_add_error_node(ira, fn_ref->source_node,
                buf_sprintf("type '%s' not a function", buf_ptr(&fn_ref->value.type->name)));
            return ira->codegen->invalid_instruction;
        }
    }

    if (fn_ref->value.type->id == ZigTypeIdFn) {
        return ir_analyze_fn_call(ira, call_instruction, nullptr, fn_ref->value.type,
            fn_ref, nullptr, false, FnInlineAuto);
    } else {
        ir_add_error_node(ira, fn_ref->source_node,
            buf_sprintf("type '%s' not a function", buf_ptr(&fn_ref->value.type->name)));
        return ira->codegen->invalid_instruction;
    }
}

// out_val->type must be the type to read the pointer as
// if the type is different than the actual type then it does a comptime byte reinterpretation
static Error ir_read_const_ptr(IrAnalyze *ira, CodeGen *codegen, AstNode *source_node,
        ConstExprValue *out_val, ConstExprValue *ptr_val)
{
    Error err;
    assert(out_val->type != nullptr);

    ConstExprValue *pointee = const_ptr_pointee_unchecked(codegen, ptr_val);

    if ((err = type_resolve(codegen, pointee->type, ResolveStatusSizeKnown)))
        return ErrorSemanticAnalyzeFail;
    if ((err = type_resolve(codegen, out_val->type, ResolveStatusSizeKnown)))
        return ErrorSemanticAnalyzeFail;

    size_t src_size = type_size(codegen, pointee->type);
    size_t dst_size = type_size(codegen, out_val->type);

    if (dst_size <= src_size) {
        if (src_size == dst_size && types_have_same_zig_comptime_repr(pointee->type, out_val->type)) {
            copy_const_val(out_val, pointee, ptr_val->data.x_ptr.mut == ConstPtrMutComptimeConst);
            return ErrorNone;
        }
        Buf buf = BUF_INIT;
        buf_resize(&buf, src_size);
        buf_write_value_bytes(codegen, (uint8_t*)buf_ptr(&buf), pointee);
        if ((err = buf_read_value_bytes(ira, codegen, source_node, (uint8_t*)buf_ptr(&buf), out_val)))
            return err;
        return ErrorNone;
    }

    switch (ptr_val->data.x_ptr.special) {
        case ConstPtrSpecialInvalid:
            zig_unreachable();
        case ConstPtrSpecialNull:
            if (dst_size == 0)
                return ErrorNone;
            opt_ir_add_error_node(ira, codegen, source_node,
                buf_sprintf("attempt to read %" ZIG_PRI_usize " bytes from null pointer",
                dst_size));
            return ErrorSemanticAnalyzeFail;
        case ConstPtrSpecialRef: {
            opt_ir_add_error_node(ira, codegen, source_node,
                buf_sprintf("attempt to read %" ZIG_PRI_usize " bytes from pointer to %s which is %" ZIG_PRI_usize " bytes",
                dst_size, buf_ptr(&pointee->type->name), src_size));
            return ErrorSemanticAnalyzeFail;
        }
        case ConstPtrSpecialBaseArray: {
            ConstExprValue *array_val = ptr_val->data.x_ptr.data.base_array.array_val;
            assert(array_val->type->id == ZigTypeIdArray);
            if (array_val->data.x_array.special != ConstArraySpecialNone)
                zig_panic("TODO");
            size_t elem_size = src_size;
            size_t elem_index = ptr_val->data.x_ptr.data.base_array.elem_index;
            src_size = elem_size * (array_val->type->data.array.len - elem_index);
            if (dst_size > src_size) {
                opt_ir_add_error_node(ira, codegen, source_node,
                    buf_sprintf("attempt to read %" ZIG_PRI_usize " bytes from %s at index %" ZIG_PRI_usize " which is %" ZIG_PRI_usize " bytes",
                        dst_size, buf_ptr(&array_val->type->name), elem_index, src_size));
                return ErrorSemanticAnalyzeFail;
            }
            size_t elem_count = (dst_size % elem_size == 0) ? (dst_size / elem_size) : (dst_size / elem_size + 1);
            Buf buf = BUF_INIT;
            buf_resize(&buf, elem_count * elem_size);
            for (size_t i = 0; i < elem_count; i += 1) {
                ConstExprValue *elem_val = &array_val->data.x_array.data.s_none.elements[elem_index + i];
                buf_write_value_bytes(codegen, (uint8_t*)buf_ptr(&buf) + (i * elem_size), elem_val);
            }
            if ((err = buf_read_value_bytes(ira, codegen, source_node, (uint8_t*)buf_ptr(&buf), out_val)))
                return err;
            return ErrorNone;
        }
        case ConstPtrSpecialBaseStruct:
        case ConstPtrSpecialBaseErrorUnionCode:
        case ConstPtrSpecialBaseErrorUnionPayload:
        case ConstPtrSpecialBaseOptionalPayload:
        case ConstPtrSpecialDiscard:
        case ConstPtrSpecialHardCodedAddr:
        case ConstPtrSpecialFunction:
            zig_panic("TODO");
    }
    zig_unreachable();
}

static IrInstruction *ir_analyze_maybe(IrAnalyze *ira, IrInstructionUnOp *un_op_instruction) {
    Error err;
    IrInstruction *value = un_op_instruction->value->child;
    ZigType *type_entry = ir_resolve_type(ira, value);
    if (type_is_invalid(type_entry))
        return ira->codegen->invalid_instruction;
    if ((err = ensure_complete_type(ira->codegen, type_entry)))
        return ira->codegen->invalid_instruction;

    switch (type_entry->id) {
        case ZigTypeIdInvalid:
            zig_unreachable();
        case ZigTypeIdMetaType:
        case ZigTypeIdVoid:
        case ZigTypeIdBool:
        case ZigTypeIdInt:
        case ZigTypeIdVector:
        case ZigTypeIdFloat:
        case ZigTypeIdPointer:
        case ZigTypeIdArray:
        case ZigTypeIdStruct:
        case ZigTypeIdComptimeFloat:
        case ZigTypeIdComptimeInt:
        case ZigTypeIdUndefined:
        case ZigTypeIdNull:
        case ZigTypeIdOptional:
        case ZigTypeIdErrorUnion:
        case ZigTypeIdErrorSet:
        case ZigTypeIdEnum:
        case ZigTypeIdUnion:
        case ZigTypeIdFn:
        case ZigTypeIdNamespace:
        case ZigTypeIdBoundFn:
        case ZigTypeIdArgTuple:
        case ZigTypeIdPromise:
            return ir_const_type(ira, &un_op_instruction->base, get_optional_type(ira->codegen, type_entry));
        case ZigTypeIdUnreachable:
        case ZigTypeIdOpaque:
            ir_add_error_node(ira, un_op_instruction->base.source_node,
                    buf_sprintf("type '%s' not optional", buf_ptr(&type_entry->name)));
            return ira->codegen->invalid_instruction;
    }
    zig_unreachable();
}

static ErrorMsg *ir_eval_negation_scalar(IrAnalyze *ira, IrInstruction *source_instr, ZigType *scalar_type,
        ConstExprValue *operand_val, ConstExprValue *scalar_out_val, bool is_wrap_op)
{
    bool is_float = (scalar_type->id == ZigTypeIdFloat || scalar_type->id == ZigTypeIdComptimeFloat);

    bool ok_type = ((scalar_type->id == ZigTypeIdInt && scalar_type->data.integral.is_signed) ||
        scalar_type->id == ZigTypeIdComptimeInt || (is_float && !is_wrap_op));

    if (!ok_type) {
        const char *fmt = is_wrap_op ? "invalid wrapping negation type: '%s'" : "invalid negation type: '%s'";
        return ir_add_error(ira, source_instr, buf_sprintf(fmt, buf_ptr(&scalar_type->name)));
    }

    if (is_float) {
        float_negate(scalar_out_val, operand_val);
    } else if (is_wrap_op) {
        bigint_negate_wrap(&scalar_out_val->data.x_bigint, &operand_val->data.x_bigint,
                scalar_type->data.integral.bit_count);
    } else {
        bigint_negate(&scalar_out_val->data.x_bigint, &operand_val->data.x_bigint);
    }

    scalar_out_val->type = scalar_type;
    scalar_out_val->special = ConstValSpecialStatic;

    if (is_wrap_op || is_float || scalar_type->id == ZigTypeIdComptimeInt) {
        return nullptr;
    }

    if (!bigint_fits_in_bits(&scalar_out_val->data.x_bigint, scalar_type->data.integral.bit_count, true)) {
        return ir_add_error(ira, source_instr, buf_sprintf("negation caused overflow"));
    }
    return nullptr;
}

static IrInstruction *ir_analyze_negation(IrAnalyze *ira, IrInstructionUnOp *instruction) {
    IrInstruction *value = instruction->value->child;
    ZigType *expr_type = value->value.type;
    if (type_is_invalid(expr_type))
        return ira->codegen->invalid_instruction;

    bool is_wrap_op = (instruction->op_id == IrUnOpNegationWrap);

    ZigType *scalar_type = (expr_type->id == ZigTypeIdVector) ? expr_type->data.vector.elem_type : expr_type;

    if (instr_is_comptime(value)) {
        ConstExprValue *operand_val = ir_resolve_const(ira, value, UndefBad);
        if (!operand_val)
            return ira->codegen->invalid_instruction;

        IrInstruction *result_instruction = ir_const(ira, &instruction->base, expr_type);
        ConstExprValue *out_val = &result_instruction->value;
        if (expr_type->id == ZigTypeIdVector) {
            expand_undef_array(ira->codegen, operand_val);
            out_val->special = ConstValSpecialUndef;
            expand_undef_array(ira->codegen, out_val);
            size_t len = expr_type->data.vector.len;
            for (size_t i = 0; i < len; i += 1) {
                ConstExprValue *scalar_operand_val = &operand_val->data.x_array.data.s_none.elements[i];
                ConstExprValue *scalar_out_val = &out_val->data.x_array.data.s_none.elements[i];
                assert(scalar_operand_val->type == scalar_type);
                assert(scalar_out_val->type == scalar_type);
                ErrorMsg *msg = ir_eval_negation_scalar(ira, &instruction->base, scalar_type,
                        scalar_operand_val, scalar_out_val, is_wrap_op);
                if (msg != nullptr) {
                    add_error_note(ira->codegen, msg, instruction->base.source_node,
                        buf_sprintf("when computing vector element at index %" ZIG_PRI_usize, i));
                    return ira->codegen->invalid_instruction;
                }
            }
            out_val->type = expr_type;
            out_val->special = ConstValSpecialStatic;
        } else {
            if (ir_eval_negation_scalar(ira, &instruction->base, scalar_type, operand_val, out_val,
                        is_wrap_op) != nullptr)
            {
                return ira->codegen->invalid_instruction;
            }
        }
        return result_instruction;
    }

    IrInstruction *result = ir_build_un_op(&ira->new_irb,
            instruction->base.scope, instruction->base.source_node,
            instruction->op_id, value);
    result->value.type = expr_type;
    return result;
}

static IrInstruction *ir_analyze_bin_not(IrAnalyze *ira, IrInstructionUnOp *instruction) {
    IrInstruction *value = instruction->value->child;
    ZigType *expr_type = value->value.type;
    if (type_is_invalid(expr_type))
        return ira->codegen->invalid_instruction;

    if (expr_type->id == ZigTypeIdInt) {
        if (instr_is_comptime(value)) {
            ConstExprValue *target_const_val = ir_resolve_const(ira, value, UndefBad);
            if (target_const_val == nullptr)
                return ira->codegen->invalid_instruction;

            IrInstruction *result = ir_const(ira, &instruction->base, expr_type);
            bigint_not(&result->value.data.x_bigint, &target_const_val->data.x_bigint,
                    expr_type->data.integral.bit_count, expr_type->data.integral.is_signed);
            return result;
        }

        IrInstruction *result = ir_build_un_op(&ira->new_irb, instruction->base.scope,
                instruction->base.source_node, IrUnOpBinNot, value);
        result->value.type = expr_type;
        return result;
    }

    ir_add_error(ira, &instruction->base,
            buf_sprintf("unable to perform binary not operation on type '%s'", buf_ptr(&expr_type->name)));
    return ira->codegen->invalid_instruction;
}

static IrInstruction *ir_analyze_instruction_un_op(IrAnalyze *ira, IrInstructionUnOp *instruction) {
    IrUnOp op_id = instruction->op_id;
    switch (op_id) {
        case IrUnOpInvalid:
            zig_unreachable();
        case IrUnOpBinNot:
            return ir_analyze_bin_not(ira, instruction);
        case IrUnOpNegation:
        case IrUnOpNegationWrap:
            return ir_analyze_negation(ira, instruction);
        case IrUnOpDereference: {
            IrInstruction *ptr = instruction->value->child;
            if (type_is_invalid(ptr->value.type))
                return ira->codegen->invalid_instruction;
            ZigType *ptr_type = ptr->value.type;
            if (ptr_type->id == ZigTypeIdPointer && ptr_type->data.pointer.ptr_len == PtrLenUnknown) {
                ir_add_error_node(ira, instruction->base.source_node,
                    buf_sprintf("index syntax required for unknown-length pointer type '%s'",
                        buf_ptr(&ptr_type->name)));
                return ira->codegen->invalid_instruction;
            }

            IrInstruction *result = ir_get_deref(ira, &instruction->base, ptr);
            if (result == ira->codegen->invalid_instruction)
                return ira->codegen->invalid_instruction;

            // If the result needs to be an lvalue, type check it
            if (instruction->lval == LValPtr && result->value.type->id != ZigTypeIdPointer) {
                ir_add_error(ira, &instruction->base,
                    buf_sprintf("attempt to dereference non-pointer type '%s'", buf_ptr(&result->value.type->name)));
                return ira->codegen->invalid_instruction;
            }

            return result;
        }
        case IrUnOpOptional:
            return ir_analyze_maybe(ira, instruction);
    }
    zig_unreachable();
}

static IrInstruction *ir_analyze_instruction_br(IrAnalyze *ira, IrInstructionBr *br_instruction) {
    IrBasicBlock *old_dest_block = br_instruction->dest_block;

    bool is_comptime;
    if (!ir_resolve_comptime(ira, br_instruction->is_comptime->child, &is_comptime))
        return ir_unreach_error(ira);

    if (is_comptime || old_dest_block->ref_count == 1)
        return ir_inline_bb(ira, &br_instruction->base, old_dest_block);

    IrBasicBlock *new_bb = ir_get_new_bb_runtime(ira, old_dest_block, &br_instruction->base);
    if (new_bb == nullptr)
        return ir_unreach_error(ira);

    IrInstruction *result = ir_build_br(&ira->new_irb,
            br_instruction->base.scope, br_instruction->base.source_node, new_bb, nullptr);
    result->value.type = ira->codegen->builtin_types.entry_unreachable;
    return ir_finish_anal(ira, result);
}

static IrInstruction *ir_analyze_instruction_cond_br(IrAnalyze *ira, IrInstructionCondBr *cond_br_instruction) {
    IrInstruction *condition = cond_br_instruction->condition->child;
    if (type_is_invalid(condition->value.type))
        return ir_unreach_error(ira);

    bool is_comptime;
    if (!ir_resolve_comptime(ira, cond_br_instruction->is_comptime->child, &is_comptime))
        return ir_unreach_error(ira);

    ZigType *bool_type = ira->codegen->builtin_types.entry_bool;
    IrInstruction *casted_condition = ir_implicit_cast(ira, condition, bool_type);
    if (type_is_invalid(casted_condition->value.type))
        return ir_unreach_error(ira);

    if (is_comptime || instr_is_comptime(casted_condition)) {
        bool cond_is_true;
        if (!ir_resolve_bool(ira, casted_condition, &cond_is_true))
            return ir_unreach_error(ira);

        IrBasicBlock *old_dest_block = cond_is_true ?
            cond_br_instruction->then_block : cond_br_instruction->else_block;

        if (is_comptime || old_dest_block->ref_count == 1)
            return ir_inline_bb(ira, &cond_br_instruction->base, old_dest_block);

        IrBasicBlock *new_dest_block = ir_get_new_bb_runtime(ira, old_dest_block, &cond_br_instruction->base);
        if (new_dest_block == nullptr)
            return ir_unreach_error(ira);

        IrInstruction *result = ir_build_br(&ira->new_irb,
            cond_br_instruction->base.scope, cond_br_instruction->base.source_node, new_dest_block, nullptr);
        result->value.type = ira->codegen->builtin_types.entry_unreachable;
        return ir_finish_anal(ira, result);
    }

    assert(cond_br_instruction->then_block != cond_br_instruction->else_block);
    IrBasicBlock *new_then_block = ir_get_new_bb_runtime(ira, cond_br_instruction->then_block, &cond_br_instruction->base);
    if (new_then_block == nullptr)
        return ir_unreach_error(ira);

    IrBasicBlock *new_else_block = ir_get_new_bb_runtime(ira, cond_br_instruction->else_block, &cond_br_instruction->base);
    if (new_else_block == nullptr)
        return ir_unreach_error(ira);

    IrInstruction *result = ir_build_cond_br(&ira->new_irb,
            cond_br_instruction->base.scope, cond_br_instruction->base.source_node,
            casted_condition, new_then_block, new_else_block, nullptr);
    result->value.type = ira->codegen->builtin_types.entry_unreachable;
    return ir_finish_anal(ira, result);
}

static IrInstruction *ir_analyze_instruction_unreachable(IrAnalyze *ira,
        IrInstructionUnreachable *unreachable_instruction)
{
    IrInstruction *result = ir_build_unreachable(&ira->new_irb,
            unreachable_instruction->base.scope, unreachable_instruction->base.source_node);
    result->value.type = ira->codegen->builtin_types.entry_unreachable;
    return ir_finish_anal(ira, result);
}

static IrInstruction *ir_analyze_instruction_phi(IrAnalyze *ira, IrInstructionPhi *phi_instruction) {
    if (ira->const_predecessor_bb) {
        for (size_t i = 0; i < phi_instruction->incoming_count; i += 1) {
            IrBasicBlock *predecessor = phi_instruction->incoming_blocks[i];
            if (predecessor != ira->const_predecessor_bb)
                continue;
            IrInstruction *value = phi_instruction->incoming_values[i]->child;
            assert(value->value.type);
            if (type_is_invalid(value->value.type))
                return ira->codegen->invalid_instruction;

            if (value->value.special != ConstValSpecialRuntime) {
                IrInstruction *result = ir_const(ira, &phi_instruction->base, nullptr);
                copy_const_val(&result->value, &value->value, true);
                return result;
            } else {
                return value;
            }
        }
        zig_unreachable();
    }

    ZigList<IrBasicBlock*> new_incoming_blocks = {0};
    ZigList<IrInstruction*> new_incoming_values = {0};

    for (size_t i = 0; i < phi_instruction->incoming_count; i += 1) {
        IrBasicBlock *predecessor = phi_instruction->incoming_blocks[i];
        if (predecessor->ref_count == 0)
            continue;


        IrInstruction *old_value = phi_instruction->incoming_values[i];
        assert(old_value);
        IrInstruction *new_value = old_value->child;
        if (!new_value || new_value->value.type->id == ZigTypeIdUnreachable || predecessor->other == nullptr)
            continue;

        if (type_is_invalid(new_value->value.type))
            return ira->codegen->invalid_instruction;


        assert(predecessor->other);
        new_incoming_blocks.append(predecessor->other);
        new_incoming_values.append(new_value);
    }

    if (new_incoming_blocks.length == 0) {
        IrInstruction *result = ir_build_unreachable(&ira->new_irb,
            phi_instruction->base.scope, phi_instruction->base.source_node);
        result->value.type = ira->codegen->builtin_types.entry_unreachable;
        return ir_finish_anal(ira, result);
    }

    if (new_incoming_blocks.length == 1) {
        return new_incoming_values.at(0);
    }

    ZigType *resolved_type = ir_resolve_peer_types(ira, phi_instruction->base.source_node, nullptr,
            new_incoming_values.items, new_incoming_values.length);
    if (type_is_invalid(resolved_type))
        return ira->codegen->invalid_instruction;

    switch (type_has_one_possible_value(ira->codegen, resolved_type)) {
    case OnePossibleValueInvalid:
        return ira->codegen->invalid_instruction;
    case OnePossibleValueYes:
        return ir_const(ira, &phi_instruction->base, resolved_type);
    case OnePossibleValueNo:
        break;
    }

    switch (type_requires_comptime(ira->codegen, resolved_type)) {
    case ReqCompTimeInvalid:
        return ira->codegen->invalid_instruction;
    case ReqCompTimeYes:
        ir_add_error_node(ira, phi_instruction->base.source_node,
                buf_sprintf("values of type '%s' must be comptime known", buf_ptr(&resolved_type->name)));
        return ira->codegen->invalid_instruction;
    case ReqCompTimeNo:
        break;
    }

    bool all_stack_ptrs = (resolved_type->id == ZigTypeIdPointer);

    // cast all values to the resolved type. however we can't put cast instructions in front of the phi instruction.
    // so we go back and insert the casts as the last instruction in the corresponding predecessor blocks, and
    // then make sure the branch instruction is preserved.
    IrBasicBlock *cur_bb = ira->new_irb.current_basic_block;
    for (size_t i = 0; i < new_incoming_values.length; i += 1) {
        IrInstruction *new_value = new_incoming_values.at(i);
        IrBasicBlock *predecessor = new_incoming_blocks.at(i);
        IrInstruction *branch_instruction = predecessor->instruction_list.pop();
        ir_set_cursor_at_end(&ira->new_irb, predecessor);
        IrInstruction *casted_value = ir_implicit_cast(ira, new_value, resolved_type);
        if (casted_value == ira->codegen->invalid_instruction) {
            return ira->codegen->invalid_instruction;
        }
        new_incoming_values.items[i] = casted_value;
        predecessor->instruction_list.append(branch_instruction);

        if (all_stack_ptrs && (casted_value->value.special != ConstValSpecialRuntime ||
            casted_value->value.data.rh_ptr != RuntimeHintPtrStack))
        {
            all_stack_ptrs = false;
        }
    }
    ir_set_cursor_at_end(&ira->new_irb, cur_bb);

    IrInstruction *result = ir_build_phi(&ira->new_irb,
        phi_instruction->base.scope, phi_instruction->base.source_node,
        new_incoming_blocks.length, new_incoming_blocks.items, new_incoming_values.items);
    result->value.type = resolved_type;

    if (all_stack_ptrs) {
        assert(result->value.special == ConstValSpecialRuntime);
        result->value.data.rh_ptr = RuntimeHintPtrStack;
    }

    return result;
}

static IrInstruction *ir_analyze_instruction_var_ptr(IrAnalyze *ira, IrInstructionVarPtr *instruction) {
    ZigVar *var = instruction->var;
    IrInstruction *result = ir_get_var_ptr(ira, &instruction->base, var);
    if (instruction->crossed_fndef_scope != nullptr && !instr_is_comptime(result)) {
        ErrorMsg *msg = ir_add_error(ira, &instruction->base,
            buf_sprintf("'%s' not accessible from inner function", buf_ptr(&var->name)));
        add_error_note(ira->codegen, msg, instruction->crossed_fndef_scope->base.source_node,
                buf_sprintf("crossed function definition here"));
        add_error_note(ira->codegen, msg, var->decl_node,
                buf_sprintf("declared here"));
        return ira->codegen->invalid_instruction;
    }
    return result;
}

static ZigType *adjust_ptr_align(CodeGen *g, ZigType *ptr_type, uint32_t new_align) {
    assert(ptr_type->id == ZigTypeIdPointer);
    return get_pointer_to_type_extra(g,
            ptr_type->data.pointer.child_type,
            ptr_type->data.pointer.is_const, ptr_type->data.pointer.is_volatile,
            ptr_type->data.pointer.ptr_len,
            new_align,
            ptr_type->data.pointer.bit_offset_in_host, ptr_type->data.pointer.host_int_bytes);
}

static ZigType *adjust_slice_align(CodeGen *g, ZigType *slice_type, uint32_t new_align) {
    assert(is_slice(slice_type));
    ZigType *ptr_type = adjust_ptr_align(g, slice_type->data.structure.fields[slice_ptr_index].type_entry,
        new_align);
    return get_slice_type(g, ptr_type);
}

static ZigType *adjust_ptr_len(CodeGen *g, ZigType *ptr_type, PtrLen ptr_len) {
    assert(ptr_type->id == ZigTypeIdPointer);
    return get_pointer_to_type_extra(g,
            ptr_type->data.pointer.child_type,
            ptr_type->data.pointer.is_const, ptr_type->data.pointer.is_volatile,
            ptr_len,
            ptr_type->data.pointer.explicit_alignment,
            ptr_type->data.pointer.bit_offset_in_host, ptr_type->data.pointer.host_int_bytes);
}

static IrInstruction *ir_analyze_instruction_elem_ptr(IrAnalyze *ira, IrInstructionElemPtr *elem_ptr_instruction) {
    Error err;
    IrInstruction *array_ptr = elem_ptr_instruction->array_ptr->child;
    if (type_is_invalid(array_ptr->value.type))
        return ira->codegen->invalid_instruction;

    ConstExprValue *orig_array_ptr_val = &array_ptr->value;

    IrInstruction *elem_index = elem_ptr_instruction->elem_index->child;
    if (type_is_invalid(elem_index->value.type))
        return ira->codegen->invalid_instruction;

    ZigType *ptr_type = orig_array_ptr_val->type;
    assert(ptr_type->id == ZigTypeIdPointer);

    ZigType *array_type = ptr_type->data.pointer.child_type;

    // At first return_type will be the pointer type we want to return, except with an optimistic alignment.
    // We will adjust return_type's alignment before returning it.
    ZigType *return_type;

    if (type_is_invalid(array_type)) {
        return ira->codegen->invalid_instruction;
    } else if (array_type->id == ZigTypeIdArray ||
        (array_type->id == ZigTypeIdPointer &&
         array_type->data.pointer.ptr_len == PtrLenSingle &&
         array_type->data.pointer.child_type->id == ZigTypeIdArray))
    {
        if (array_type->id == ZigTypeIdPointer) {
            array_type = array_type->data.pointer.child_type;
            ptr_type = ptr_type->data.pointer.child_type;
            if (orig_array_ptr_val->special != ConstValSpecialRuntime) {
                orig_array_ptr_val = const_ptr_pointee(ira, ira->codegen, orig_array_ptr_val,
                        elem_ptr_instruction->base.source_node);
                if (orig_array_ptr_val == nullptr)
                    return ira->codegen->invalid_instruction;
            }
        }
        if (array_type->data.array.len == 0) {
            ir_add_error_node(ira, elem_ptr_instruction->base.source_node,
                    buf_sprintf("index 0 outside array of size 0"));
            return ira->codegen->invalid_instruction;
        }
        ZigType *child_type = array_type->data.array.child_type;
        if (ptr_type->data.pointer.host_int_bytes == 0) {
            return_type = get_pointer_to_type_extra(ira->codegen, child_type,
                    ptr_type->data.pointer.is_const, ptr_type->data.pointer.is_volatile,
                    elem_ptr_instruction->ptr_len,
                    ptr_type->data.pointer.explicit_alignment, 0, 0);
        } else {
            uint64_t elem_val_scalar;
            if (!ir_resolve_usize(ira, elem_index, &elem_val_scalar))
                return ira->codegen->invalid_instruction;

            size_t bit_width = type_size_bits(ira->codegen, child_type);
            size_t bit_offset = bit_width * elem_val_scalar;

            return_type = get_pointer_to_type_extra(ira->codegen, child_type,
                    ptr_type->data.pointer.is_const, ptr_type->data.pointer.is_volatile,
                    elem_ptr_instruction->ptr_len,
                    1, (uint32_t)bit_offset, ptr_type->data.pointer.host_int_bytes);
        }
    } else if (array_type->id == ZigTypeIdPointer) {
        if (array_type->data.pointer.ptr_len == PtrLenSingle) {
            ir_add_error_node(ira, elem_ptr_instruction->base.source_node,
                    buf_sprintf("index of single-item pointer"));
            return ira->codegen->invalid_instruction;
        }
        return_type = adjust_ptr_len(ira->codegen, array_type, elem_ptr_instruction->ptr_len);
    } else if (is_slice(array_type)) {
        return_type = adjust_ptr_len(ira->codegen, array_type->data.structure.fields[slice_ptr_index].type_entry,
                elem_ptr_instruction->ptr_len);
    } else if (array_type->id == ZigTypeIdArgTuple) {
        ConstExprValue *ptr_val = ir_resolve_const(ira, array_ptr, UndefBad);
        if (!ptr_val)
            return ira->codegen->invalid_instruction;
        ConstExprValue *args_val = const_ptr_pointee(ira, ira->codegen, ptr_val, elem_ptr_instruction->base.source_node);
        if (args_val == nullptr)
            return ira->codegen->invalid_instruction;
        size_t start = args_val->data.x_arg_tuple.start_index;
        size_t end = args_val->data.x_arg_tuple.end_index;
        uint64_t elem_index_val;
        if (!ir_resolve_usize(ira, elem_index, &elem_index_val))
            return ira->codegen->invalid_instruction;
        size_t index = elem_index_val;
        size_t len = end - start;
        if (index >= len) {
            ir_add_error(ira, &elem_ptr_instruction->base,
                buf_sprintf("index %" ZIG_PRI_usize " outside argument list of size %" ZIG_PRI_usize "", index, len));
            return ira->codegen->invalid_instruction;
        }
        size_t abs_index = start + index;
        ZigFn *fn_entry = exec_fn_entry(ira->new_irb.exec);
        assert(fn_entry);
        ZigVar *var = get_fn_var_by_index(fn_entry, abs_index);
        bool is_const = true;
        bool is_volatile = false;
        if (var) {
            return ir_get_var_ptr(ira, &elem_ptr_instruction->base, var);
        } else {
            return ir_get_const_ptr(ira, &elem_ptr_instruction->base, &ira->codegen->const_void_val,
                    ira->codegen->builtin_types.entry_void, ConstPtrMutComptimeConst, is_const, is_volatile, 0);
        }
    } else {
        ir_add_error_node(ira, elem_ptr_instruction->base.source_node,
                buf_sprintf("array access of non-array type '%s'", buf_ptr(&array_type->name)));
        return ira->codegen->invalid_instruction;
    }

    ZigType *usize = ira->codegen->builtin_types.entry_usize;
    IrInstruction *casted_elem_index = ir_implicit_cast(ira, elem_index, usize);
    if (casted_elem_index == ira->codegen->invalid_instruction)
        return ira->codegen->invalid_instruction;

    bool safety_check_on = elem_ptr_instruction->safety_check_on;
    if ((err = ensure_complete_type(ira->codegen, return_type->data.pointer.child_type)))
        return ira->codegen->invalid_instruction;

    uint64_t elem_size = type_size(ira->codegen, return_type->data.pointer.child_type);
    uint64_t abi_align = get_abi_alignment(ira->codegen, return_type->data.pointer.child_type);
    uint64_t ptr_align = get_ptr_align(ira->codegen, return_type);
    if (instr_is_comptime(casted_elem_index)) {
        uint64_t index = bigint_as_unsigned(&casted_elem_index->value.data.x_bigint);
        if (array_type->id == ZigTypeIdArray) {
            uint64_t array_len = array_type->data.array.len;
            if (index >= array_len) {
                ir_add_error_node(ira, elem_ptr_instruction->base.source_node,
                    buf_sprintf("index %" ZIG_PRI_u64 " outside array of size %" ZIG_PRI_u64,
                            index, array_len));
                return ira->codegen->invalid_instruction;
            }
            safety_check_on = false;
        }

        {
            // figure out the largest alignment possible
            uint64_t chosen_align = abi_align;
            if (ptr_align >= abi_align) {
                while (ptr_align > abi_align) {
                    if ((index * elem_size) % ptr_align == 0) {
                        chosen_align = ptr_align;
                        break;
                    }
                    ptr_align >>= 1;
                }
            } else if (elem_size >= ptr_align && elem_size % ptr_align == 0) {
                chosen_align = ptr_align;
            } else {
                // can't get here because guaranteed elem_size >= abi_align
                zig_unreachable();
            }
            return_type = adjust_ptr_align(ira->codegen, return_type, chosen_align);
        }

        if (orig_array_ptr_val->special != ConstValSpecialRuntime &&
            (orig_array_ptr_val->data.x_ptr.mut != ConstPtrMutRuntimeVar ||
                array_type->id == ZigTypeIdArray))
        {
            ConstExprValue *array_ptr_val = const_ptr_pointee(ira, ira->codegen, orig_array_ptr_val,
                                        elem_ptr_instruction->base.source_node);
            if (array_ptr_val == nullptr)
                return ira->codegen->invalid_instruction;

            if (array_ptr_val->special != ConstValSpecialRuntime &&
                (array_type->id != ZigTypeIdPointer ||
                    array_ptr_val->data.x_ptr.special != ConstPtrSpecialHardCodedAddr))
            {
                if (array_type->id == ZigTypeIdPointer) {
                    IrInstruction *result = ir_const(ira, &elem_ptr_instruction->base, return_type);
                    ConstExprValue *out_val = &result->value;
                    out_val->data.x_ptr.mut = array_ptr_val->data.x_ptr.mut;
                    size_t new_index;
                    size_t mem_size;
                    size_t old_size;
                    switch (array_ptr_val->data.x_ptr.special) {
                        case ConstPtrSpecialInvalid:
                        case ConstPtrSpecialDiscard:
                            zig_unreachable();
                        case ConstPtrSpecialRef:
                            mem_size = 1;
                            old_size = 1;
                            new_index = index;

                            out_val->data.x_ptr.special = ConstPtrSpecialRef;
                            out_val->data.x_ptr.data.ref.pointee = array_ptr_val->data.x_ptr.data.ref.pointee;
                            break;
                        case ConstPtrSpecialBaseArray:
                            {
                                size_t offset = array_ptr_val->data.x_ptr.data.base_array.elem_index;
                                new_index = offset + index;
                                mem_size = array_ptr_val->data.x_ptr.data.base_array.array_val->type->data.array.len;
                                old_size = mem_size - offset;

                                assert(array_ptr_val->data.x_ptr.data.base_array.array_val);

                                out_val->data.x_ptr.special = ConstPtrSpecialBaseArray;
                                out_val->data.x_ptr.data.base_array.array_val =
                                    array_ptr_val->data.x_ptr.data.base_array.array_val;
                                out_val->data.x_ptr.data.base_array.elem_index = new_index;
                                out_val->data.x_ptr.data.base_array.is_cstr =
                                    array_ptr_val->data.x_ptr.data.base_array.is_cstr;

                                break;
                            }
                        case ConstPtrSpecialBaseStruct:
                            zig_panic("TODO elem ptr on a const inner struct");
                        case ConstPtrSpecialBaseErrorUnionCode:
                            zig_panic("TODO elem ptr on a const inner error union code");
                        case ConstPtrSpecialBaseErrorUnionPayload:
                            zig_panic("TODO elem ptr on a const inner error union payload");
                        case ConstPtrSpecialBaseOptionalPayload:
                            zig_panic("TODO elem ptr on a const inner optional payload");
                        case ConstPtrSpecialHardCodedAddr:
                            zig_unreachable();
                        case ConstPtrSpecialFunction:
                            zig_panic("TODO element ptr of a function casted to a ptr");
                        case ConstPtrSpecialNull:
                            zig_panic("TODO elem ptr on a null pointer");
                    }
                    if (new_index >= mem_size) {
                        ir_add_error_node(ira, elem_ptr_instruction->base.source_node,
                            buf_sprintf("index %" ZIG_PRI_u64 " outside pointer of size %" ZIG_PRI_usize "", index, old_size));
                        return ira->codegen->invalid_instruction;
                    }
                    return result;
                } else if (is_slice(array_type)) {
                    ConstExprValue *ptr_field = &array_ptr_val->data.x_struct.fields[slice_ptr_index];
                    if (ptr_field->data.x_ptr.special == ConstPtrSpecialHardCodedAddr) {
                        IrInstruction *result = ir_build_elem_ptr(&ira->new_irb, elem_ptr_instruction->base.scope, elem_ptr_instruction->base.source_node,
                                array_ptr, casted_elem_index, false, elem_ptr_instruction->ptr_len);
                        result->value.type = return_type;
                        return result;
                    }
                    ConstExprValue *len_field = &array_ptr_val->data.x_struct.fields[slice_len_index];
                    IrInstruction *result = ir_const(ira, &elem_ptr_instruction->base, return_type);
                    ConstExprValue *out_val = &result->value;
                    uint64_t slice_len = bigint_as_unsigned(&len_field->data.x_bigint);
                    if (index >= slice_len) {
                        ir_add_error_node(ira, elem_ptr_instruction->base.source_node,
                            buf_sprintf("index %" ZIG_PRI_u64 " outside slice of size %" ZIG_PRI_u64,
                                index, slice_len));
                        return ira->codegen->invalid_instruction;
                    }
                    out_val->data.x_ptr.mut = ptr_field->data.x_ptr.mut;
                    switch (ptr_field->data.x_ptr.special) {
                        case ConstPtrSpecialInvalid:
                        case ConstPtrSpecialDiscard:
                            zig_unreachable();
                        case ConstPtrSpecialRef:
                            out_val->data.x_ptr.special = ConstPtrSpecialRef;
                            out_val->data.x_ptr.data.ref.pointee = ptr_field->data.x_ptr.data.ref.pointee;
                            break;
                        case ConstPtrSpecialBaseArray:
                            {
                                size_t offset = ptr_field->data.x_ptr.data.base_array.elem_index;
                                uint64_t new_index = offset + index;
                                assert(new_index < ptr_field->data.x_ptr.data.base_array.array_val->type->data.array.len);
                                out_val->data.x_ptr.special = ConstPtrSpecialBaseArray;
                                out_val->data.x_ptr.data.base_array.array_val =
                                    ptr_field->data.x_ptr.data.base_array.array_val;
                                out_val->data.x_ptr.data.base_array.elem_index = new_index;
                                out_val->data.x_ptr.data.base_array.is_cstr =
                                    ptr_field->data.x_ptr.data.base_array.is_cstr;
                                break;
                            }
                        case ConstPtrSpecialBaseStruct:
                            zig_panic("TODO elem ptr on a slice backed by const inner struct");
                        case ConstPtrSpecialBaseErrorUnionCode:
                            zig_panic("TODO elem ptr on a slice backed by const inner error union code");
                        case ConstPtrSpecialBaseErrorUnionPayload:
                            zig_panic("TODO elem ptr on a slice backed by const inner error union payload");
                        case ConstPtrSpecialBaseOptionalPayload:
                            zig_panic("TODO elem ptr on a slice backed by const optional payload");
                        case ConstPtrSpecialHardCodedAddr:
                            zig_unreachable();
                        case ConstPtrSpecialFunction:
                            zig_panic("TODO elem ptr on a slice that was ptrcast from a function");
                        case ConstPtrSpecialNull:
                            zig_panic("TODO elem ptr on a slice has a null pointer");
                    }
                    return result;
                } else if (array_type->id == ZigTypeIdArray) {
                    IrInstruction *result = ir_const(ira, &elem_ptr_instruction->base, return_type);
                    ConstExprValue *out_val = &result->value;
                    out_val->data.x_ptr.special = ConstPtrSpecialBaseArray;
                    out_val->data.x_ptr.mut = orig_array_ptr_val->data.x_ptr.mut;
                    out_val->data.x_ptr.data.base_array.array_val = array_ptr_val;
                    out_val->data.x_ptr.data.base_array.elem_index = index;
                    return result;
                } else {
                    zig_unreachable();
                }
            }
        }

    } else {
        // runtime known element index
        switch (type_requires_comptime(ira->codegen, return_type)) {
        case ReqCompTimeYes:
            ir_add_error(ira, elem_index,
                buf_sprintf("values of type '%s' must be comptime known, but index value is runtime known",
                    buf_ptr(&return_type->data.pointer.child_type->name)));
            return ira->codegen->invalid_instruction;
        case ReqCompTimeInvalid:
            return ira->codegen->invalid_instruction;
        case ReqCompTimeNo:
            break;
        }
        if (ptr_align < abi_align) {
            if (elem_size >= ptr_align && elem_size % ptr_align == 0) {
                return_type = adjust_ptr_align(ira->codegen, return_type, ptr_align);
            } else {
                // can't get here because guaranteed elem_size >= abi_align
                zig_unreachable();
            }
        } else {
            return_type = adjust_ptr_align(ira->codegen, return_type, abi_align);
        }
    }

    IrInstruction *result = ir_build_elem_ptr(&ira->new_irb, elem_ptr_instruction->base.scope, elem_ptr_instruction->base.source_node,
            array_ptr, casted_elem_index, safety_check_on, elem_ptr_instruction->ptr_len);
    result->value.type = return_type;
    return result;
}

static IrInstruction *ir_analyze_container_member_access_inner(IrAnalyze *ira,
    ZigType *bare_struct_type, Buf *field_name, IrInstruction *source_instr,
    IrInstruction *container_ptr, ZigType *container_type)
{
    if (!is_slice(bare_struct_type)) {
        ScopeDecls *container_scope = get_container_scope(bare_struct_type);
        assert(container_scope != nullptr);
        auto entry = container_scope->decl_table.maybe_get(field_name);
        Tld *tld = entry ? entry->value : nullptr;
        if (tld && tld->id == TldIdFn) {
            resolve_top_level_decl(ira->codegen, tld, false, source_instr->source_node);
            if (tld->resolution == TldResolutionInvalid)
                return ira->codegen->invalid_instruction;
            TldFn *tld_fn = (TldFn *)tld;
            ZigFn *fn_entry = tld_fn->fn_entry;
            if (type_is_invalid(fn_entry->type_entry))
                return ira->codegen->invalid_instruction;

            IrInstruction *bound_fn_value = ir_build_const_bound_fn(&ira->new_irb, source_instr->scope,
                source_instr->source_node, fn_entry, container_ptr);
            return ir_get_ref(ira, source_instr, bound_fn_value, true, false);
        }
    }
    const char *prefix_name;
    if (is_slice(bare_struct_type)) {
        prefix_name = "";
    } else if (bare_struct_type->id == ZigTypeIdStruct) {
        prefix_name = "struct ";
    } else if (bare_struct_type->id == ZigTypeIdEnum) {
        prefix_name = "enum ";
    } else if (bare_struct_type->id == ZigTypeIdUnion) {
        prefix_name = "union ";
    } else {
        prefix_name = "";
    }
    ir_add_error_node(ira, source_instr->source_node,
        buf_sprintf("no member named '%s' in %s'%s'", buf_ptr(field_name), prefix_name, buf_ptr(&bare_struct_type->name)));
    return ira->codegen->invalid_instruction;
}

static IrInstruction *ir_analyze_container_field_ptr(IrAnalyze *ira, Buf *field_name,
    IrInstruction *source_instr, IrInstruction *container_ptr, ZigType *container_type)
{
    Error err;

    ZigType *bare_type = container_ref_type(container_type);
    if ((err = ensure_complete_type(ira->codegen, bare_type)))
        return ira->codegen->invalid_instruction;

    assert(container_ptr->value.type->id == ZigTypeIdPointer);
    bool is_const = container_ptr->value.type->data.pointer.is_const;
    bool is_volatile = container_ptr->value.type->data.pointer.is_volatile;
    if (bare_type->id == ZigTypeIdStruct) {
        TypeStructField *field = find_struct_type_field(bare_type, field_name);
        if (field) {
            switch (type_has_one_possible_value(ira->codegen, field->type_entry)) {
                case OnePossibleValueInvalid:
                    return ira->codegen->invalid_instruction;
                case OnePossibleValueYes: {
                    IrInstruction *elem = ir_const(ira, source_instr, field->type_entry);
                    return ir_get_ref(ira, source_instr, elem, false, false);
                }
                case OnePossibleValueNo:
                    break;
            }
            bool is_packed = (bare_type->data.structure.layout == ContainerLayoutPacked);
            uint32_t align_bytes = is_packed ? 1 : get_abi_alignment(ira->codegen, field->type_entry);
            uint32_t ptr_bit_offset = container_ptr->value.type->data.pointer.bit_offset_in_host;
            uint32_t ptr_host_int_bytes = container_ptr->value.type->data.pointer.host_int_bytes;
            uint32_t host_int_bytes_for_result_type = (ptr_host_int_bytes == 0) ?
                get_host_int_bytes(ira->codegen, bare_type, field) : ptr_host_int_bytes;
            if (instr_is_comptime(container_ptr)) {
                ConstExprValue *ptr_val = ir_resolve_const(ira, container_ptr, UndefBad);
                if (!ptr_val)
                    return ira->codegen->invalid_instruction;

                if (ptr_val->data.x_ptr.special != ConstPtrSpecialHardCodedAddr) {
                    ConstExprValue *struct_val = const_ptr_pointee(ira, ira->codegen, ptr_val, source_instr->source_node);
                    if (struct_val == nullptr)
                        return ira->codegen->invalid_instruction;
                    if (type_is_invalid(struct_val->type))
                        return ira->codegen->invalid_instruction;
                    ZigType *ptr_type = get_pointer_to_type_extra(ira->codegen, field->type_entry,
                            is_const, is_volatile, PtrLenSingle, align_bytes,
                            (uint32_t)(ptr_bit_offset + field->bit_offset_in_host),
                            (uint32_t)host_int_bytes_for_result_type);
                    IrInstruction *result = ir_const(ira, source_instr, ptr_type);
                    ConstExprValue *const_val = &result->value;
                    const_val->data.x_ptr.special = ConstPtrSpecialBaseStruct;
                    const_val->data.x_ptr.mut = container_ptr->value.data.x_ptr.mut;
                    const_val->data.x_ptr.data.base_struct.struct_val = struct_val;
                    const_val->data.x_ptr.data.base_struct.field_index = field->src_index;
                    return result;
                }
            }
            IrInstruction *result = ir_build_struct_field_ptr(&ira->new_irb, source_instr->scope, source_instr->source_node,
                    container_ptr, field);
            result->value.type = get_pointer_to_type_extra(ira->codegen, field->type_entry, is_const, is_volatile,
                    PtrLenSingle,
                    align_bytes,
                    (uint32_t)(ptr_bit_offset + field->bit_offset_in_host),
                    host_int_bytes_for_result_type);
            return result;
        } else {
            return ir_analyze_container_member_access_inner(ira, bare_type, field_name,
                source_instr, container_ptr, container_type);
        }
    } else if (bare_type->id == ZigTypeIdEnum) {
        return ir_analyze_container_member_access_inner(ira, bare_type, field_name,
            source_instr, container_ptr, container_type);
    } else if (bare_type->id == ZigTypeIdUnion) {
        TypeUnionField *field = find_union_type_field(bare_type, field_name);
        if (field) {
            if (instr_is_comptime(container_ptr)) {
                ConstExprValue *ptr_val = ir_resolve_const(ira, container_ptr, UndefBad);
                if (!ptr_val)
                    return ira->codegen->invalid_instruction;

                if (ptr_val->data.x_ptr.special != ConstPtrSpecialHardCodedAddr) {
                    ConstExprValue *union_val = const_ptr_pointee(ira, ira->codegen, ptr_val, source_instr->source_node);
                    if (union_val == nullptr)
                        return ira->codegen->invalid_instruction;
                    if (type_is_invalid(union_val->type))
                        return ira->codegen->invalid_instruction;

                    TypeUnionField *actual_field = find_union_field_by_tag(bare_type, &union_val->data.x_union.tag);
                    if (actual_field == nullptr)
                        zig_unreachable();

                    if (field != actual_field) {
                        ir_add_error_node(ira, source_instr->source_node,
                            buf_sprintf("accessing union field '%s' while field '%s' is set", buf_ptr(field_name),
                                buf_ptr(actual_field->name)));
                        return ira->codegen->invalid_instruction;
                    }

                    ConstExprValue *payload_val = union_val->data.x_union.payload;

                    ZigType *field_type = field->type_entry;
                    if (field_type->id == ZigTypeIdVoid) {
                        assert(payload_val == nullptr);
                        payload_val = create_const_vals(1);
                        payload_val->special = ConstValSpecialStatic;
                        payload_val->type = field_type;
                    }

                    ZigType *ptr_type = get_pointer_to_type_extra(ira->codegen, field_type,
                            is_const, is_volatile, PtrLenSingle, 0, 0, 0);

                    IrInstruction *result = ir_const(ira, source_instr, ptr_type);
                    ConstExprValue *const_val = &result->value;
                    const_val->data.x_ptr.special = ConstPtrSpecialRef;
                    const_val->data.x_ptr.mut = container_ptr->value.data.x_ptr.mut;
                    const_val->data.x_ptr.data.ref.pointee = payload_val;
                    return result;
                }
            }

            IrInstruction *result = ir_build_union_field_ptr(&ira->new_irb, source_instr->scope, source_instr->source_node, container_ptr, field);
            result->value.type = get_pointer_to_type_extra(ira->codegen, field->type_entry, is_const, is_volatile,
                    PtrLenSingle, 0, 0, 0);
            return result;
        } else {
            return ir_analyze_container_member_access_inner(ira, bare_type, field_name,
                source_instr, container_ptr, container_type);
        }
    } else {
        zig_unreachable();
    }
}

static void add_link_lib_symbol(IrAnalyze *ira, Buf *lib_name, Buf *symbol_name, AstNode *source_node) {
    if (buf_eql_str(lib_name, "c") && ira->codegen->libc_link_lib == nullptr &&
        !ira->codegen->reported_bad_link_libc_error)
    {
        ir_add_error_node(ira, source_node,
            buf_sprintf("dependency on library c must be explicitly specified in the build command"));
        ira->codegen->reported_bad_link_libc_error = true;
    }

    LinkLib *link_lib = add_link_lib(ira->codegen, lib_name);
    for (size_t i = 0; i < link_lib->symbols.length; i += 1) {
        Buf *existing_symbol_name = link_lib->symbols.at(i);
        if (buf_eql_buf(existing_symbol_name, symbol_name)) {
            return;
        }
    }
    for (size_t i = 0; i < ira->codegen->forbidden_libs.length; i += 1) {
        Buf *forbidden_lib_name = ira->codegen->forbidden_libs.at(i);
        if (buf_eql_buf(lib_name, forbidden_lib_name)) {
            ir_add_error_node(ira, source_node,
                buf_sprintf("linking against forbidden library '%s'", buf_ptr(symbol_name)));
        }
    }
    link_lib->symbols.append(symbol_name);
}


static IrInstruction *ir_analyze_decl_ref(IrAnalyze *ira, IrInstruction *source_instruction, Tld *tld) {
    bool pointer_only = false;
    resolve_top_level_decl(ira->codegen, tld, pointer_only, source_instruction->source_node);
    if (tld->resolution == TldResolutionInvalid)
        return ira->codegen->invalid_instruction;

    switch (tld->id) {
        case TldIdContainer:
        case TldIdCompTime:
            zig_unreachable();
        case TldIdVar:
        {
            TldVar *tld_var = (TldVar *)tld;
            ZigVar *var = tld_var->var;
            if (tld_var->extern_lib_name != nullptr) {
                add_link_lib_symbol(ira, tld_var->extern_lib_name, &var->name, source_instruction->source_node);
            }

            return ir_get_var_ptr(ira, source_instruction, var);
        }
        case TldIdFn:
        {
            TldFn *tld_fn = (TldFn *)tld;
            ZigFn *fn_entry = tld_fn->fn_entry;
            assert(fn_entry->type_entry);

            if (type_is_invalid(fn_entry->type_entry))
                return ira->codegen->invalid_instruction;

            // TODO instead of allocating this every time, put it in the tld value and we can reference
            // the same one every time
            ConstExprValue *const_val = create_const_vals(1);
            const_val->special = ConstValSpecialStatic;
            const_val->type = fn_entry->type_entry;
            const_val->data.x_ptr.data.fn.fn_entry = fn_entry;
            const_val->data.x_ptr.special = ConstPtrSpecialFunction;
            const_val->data.x_ptr.mut = ConstPtrMutComptimeConst;

            if (tld_fn->extern_lib_name != nullptr) {
                add_link_lib_symbol(ira, tld_fn->extern_lib_name, &fn_entry->symbol_name, source_instruction->source_node);
            }

            bool ptr_is_const = true;
            bool ptr_is_volatile = false;
            return ir_get_const_ptr(ira, source_instruction, const_val, fn_entry->type_entry,
                    ConstPtrMutComptimeConst, ptr_is_const, ptr_is_volatile, 0);
        }
    }
    zig_unreachable();
}

static ErrorTableEntry *find_err_table_entry(ZigType *err_set_type, Buf *field_name) {
    assert(err_set_type->id == ZigTypeIdErrorSet);
    for (uint32_t i = 0; i < err_set_type->data.error_set.err_count; i += 1) {
        ErrorTableEntry *err_table_entry = err_set_type->data.error_set.errors[i];
        if (buf_eql_buf(&err_table_entry->name, field_name)) {
            return err_table_entry;
        }
    }
    return nullptr;
}

static IrInstruction *ir_analyze_instruction_field_ptr(IrAnalyze *ira, IrInstructionFieldPtr *field_ptr_instruction) {
    Error err;
    IrInstruction *container_ptr = field_ptr_instruction->container_ptr->child;
    if (type_is_invalid(container_ptr->value.type))
        return ira->codegen->invalid_instruction;

    ZigType *container_type = container_ptr->value.type->data.pointer.child_type;

    Buf *field_name = field_ptr_instruction->field_name_buffer;
    if (!field_name) {
        IrInstruction *field_name_expr = field_ptr_instruction->field_name_expr->child;
        field_name = ir_resolve_str(ira, field_name_expr);
        if (!field_name)
            return ira->codegen->invalid_instruction;
    }


    AstNode *source_node = field_ptr_instruction->base.source_node;

    if (type_is_invalid(container_type)) {
        return ira->codegen->invalid_instruction;
    } else if (is_container_ref(container_type)) {
        assert(container_ptr->value.type->id == ZigTypeIdPointer);
        if (container_type->id == ZigTypeIdPointer) {
            ZigType *bare_type = container_ref_type(container_type);
            IrInstruction *container_child = ir_get_deref(ira, &field_ptr_instruction->base, container_ptr);
            IrInstruction *result = ir_analyze_container_field_ptr(ira, field_name, &field_ptr_instruction->base, container_child, bare_type);
            return result;
        } else {
            IrInstruction *result = ir_analyze_container_field_ptr(ira, field_name, &field_ptr_instruction->base, container_ptr, container_type);
            return result;
        }
    } else if (is_array_ref(container_type)) {
        if (buf_eql_str(field_name, "len")) {
            ConstExprValue *len_val = create_const_vals(1);
            if (container_type->id == ZigTypeIdPointer) {
                init_const_usize(ira->codegen, len_val, container_type->data.pointer.child_type->data.array.len);
            } else {
                init_const_usize(ira->codegen, len_val, container_type->data.array.len);
            }

            ZigType *usize = ira->codegen->builtin_types.entry_usize;
            bool ptr_is_const = true;
            bool ptr_is_volatile = false;
            return ir_get_const_ptr(ira, &field_ptr_instruction->base, len_val,
                    usize, ConstPtrMutComptimeConst, ptr_is_const, ptr_is_volatile, 0);
        } else {
            ir_add_error_node(ira, source_node,
                buf_sprintf("no member named '%s' in '%s'", buf_ptr(field_name),
                    buf_ptr(&container_type->name)));
            return ira->codegen->invalid_instruction;
        }
    } else if (container_type->id == ZigTypeIdArgTuple) {
        ConstExprValue *container_ptr_val = ir_resolve_const(ira, container_ptr, UndefBad);
        if (!container_ptr_val)
            return ira->codegen->invalid_instruction;

        assert(container_ptr->value.type->id == ZigTypeIdPointer);
        ConstExprValue *child_val = const_ptr_pointee(ira, ira->codegen, container_ptr_val, source_node);
        if (child_val == nullptr)
            return ira->codegen->invalid_instruction;

        if (buf_eql_str(field_name, "len")) {
            ConstExprValue *len_val = create_const_vals(1);
            size_t len = child_val->data.x_arg_tuple.end_index - child_val->data.x_arg_tuple.start_index;
            init_const_usize(ira->codegen, len_val, len);

            ZigType *usize = ira->codegen->builtin_types.entry_usize;
            bool ptr_is_const = true;
            bool ptr_is_volatile = false;
            return ir_get_const_ptr(ira, &field_ptr_instruction->base, len_val,
                    usize, ConstPtrMutComptimeConst, ptr_is_const, ptr_is_volatile, 0);
        } else {
            ir_add_error_node(ira, source_node,
                buf_sprintf("no member named '%s' in '%s'", buf_ptr(field_name),
                    buf_ptr(&container_type->name)));
            return ira->codegen->invalid_instruction;
        }
    } else if (container_type->id == ZigTypeIdMetaType) {
        ConstExprValue *container_ptr_val = ir_resolve_const(ira, container_ptr, UndefBad);
        if (!container_ptr_val)
            return ira->codegen->invalid_instruction;

        assert(container_ptr->value.type->id == ZigTypeIdPointer);
        ConstExprValue *child_val = const_ptr_pointee(ira, ira->codegen, container_ptr_val, source_node);
        if (child_val == nullptr)
            return ira->codegen->invalid_instruction;
        ZigType *child_type = child_val->data.x_type;

        if (type_is_invalid(child_type)) {
            return ira->codegen->invalid_instruction;
        } else if (is_container(child_type)) {
            if (is_slice(child_type) && buf_eql_str(field_name, "Child")) {
                bool ptr_is_const = true;
                bool ptr_is_volatile = false;
                TypeStructField *ptr_field = &child_type->data.structure.fields[slice_ptr_index];
                assert(ptr_field->type_entry->id == ZigTypeIdPointer);
                ZigType *child_type = ptr_field->type_entry->data.pointer.child_type;
                return ir_get_const_ptr(ira, &field_ptr_instruction->base,
                    create_const_type(ira->codegen, child_type),
                    ira->codegen->builtin_types.entry_type,
                    ConstPtrMutComptimeConst, ptr_is_const, ptr_is_volatile, 0);
            }
            if (child_type->id == ZigTypeIdEnum) {
                if ((err = ensure_complete_type(ira->codegen, child_type)))
                    return ira->codegen->invalid_instruction;

                TypeEnumField *field = find_enum_type_field(child_type, field_name);
                if (field) {
                    bool ptr_is_const = true;
                    bool ptr_is_volatile = false;
                    return ir_get_const_ptr(ira, &field_ptr_instruction->base,
                            create_const_enum(child_type, &field->value), child_type,
                            ConstPtrMutComptimeConst, ptr_is_const, ptr_is_volatile, 0);
                }
            }
            ScopeDecls *container_scope = get_container_scope(child_type);
            if (container_scope != nullptr) {
                auto entry = container_scope->decl_table.maybe_get(field_name);
                Tld *tld = entry ? entry->value : nullptr;
                if (tld) {
                    return ir_analyze_decl_ref(ira, &field_ptr_instruction->base, tld);
                }
            }
            if (child_type->id == ZigTypeIdUnion &&
                    (child_type->data.unionation.decl_node->data.container_decl.init_arg_expr != nullptr ||
                    child_type->data.unionation.decl_node->data.container_decl.auto_enum))
            {
                if ((err = ensure_complete_type(ira->codegen, child_type)))
                    return ira->codegen->invalid_instruction;
                TypeUnionField *field = find_union_type_field(child_type, field_name);
                if (field) {
                    ZigType *enum_type = child_type->data.unionation.tag_type;
                    bool ptr_is_const = true;
                    bool ptr_is_volatile = false;
                    return ir_get_const_ptr(ira, &field_ptr_instruction->base,
                            create_const_enum(enum_type, &field->enum_field->value), enum_type,
                            ConstPtrMutComptimeConst, ptr_is_const, ptr_is_volatile, 0);
                }
            }
            ir_add_error(ira, &field_ptr_instruction->base,
                buf_sprintf("container '%s' has no member called '%s'",
                    buf_ptr(&child_type->name), buf_ptr(field_name)));
            return ira->codegen->invalid_instruction;
        } else if (child_type->id == ZigTypeIdErrorSet) {
            ErrorTableEntry *err_entry;
            ZigType *err_set_type;
            if (type_is_global_error_set(child_type)) {
                auto existing_entry = ira->codegen->error_table.maybe_get(field_name);
                if (existing_entry) {
                    err_entry = existing_entry->value;
                } else {
                    err_entry = allocate<ErrorTableEntry>(1);
                    err_entry->decl_node = field_ptr_instruction->base.source_node;
                    buf_init_from_buf(&err_entry->name, field_name);
                    size_t error_value_count = ira->codegen->errors_by_index.length;
                    assert((uint32_t)error_value_count < (((uint32_t)1) << (uint32_t)ira->codegen->err_tag_type->data.integral.bit_count));
                    err_entry->value = error_value_count;
                    ira->codegen->errors_by_index.append(err_entry);
                    ira->codegen->err_enumerators.append(ZigLLVMCreateDebugEnumerator(ira->codegen->dbuilder,
                        buf_ptr(field_name), error_value_count));
                    ira->codegen->error_table.put(field_name, err_entry);
                }
                if (err_entry->set_with_only_this_in_it == nullptr) {
                    err_entry->set_with_only_this_in_it = make_err_set_with_one_item(ira->codegen,
                            field_ptr_instruction->base.scope, field_ptr_instruction->base.source_node,
                            err_entry);
                }
                err_set_type = err_entry->set_with_only_this_in_it;
            } else {
                if (!resolve_inferred_error_set(ira->codegen, child_type, field_ptr_instruction->base.source_node)) {
                    return ira->codegen->invalid_instruction;
                }
                err_entry = find_err_table_entry(child_type, field_name);
                if (err_entry == nullptr) {
                    ir_add_error(ira, &field_ptr_instruction->base,
                        buf_sprintf("no error named '%s' in '%s'", buf_ptr(field_name), buf_ptr(&child_type->name)));
                    return ira->codegen->invalid_instruction;
                }
                err_set_type = child_type;
            }
            ConstExprValue *const_val = create_const_vals(1);
            const_val->special = ConstValSpecialStatic;
            const_val->type = err_set_type;
            const_val->data.x_err_set = err_entry;

            bool ptr_is_const = true;
            bool ptr_is_volatile = false;
            return ir_get_const_ptr(ira, &field_ptr_instruction->base, const_val,
                    err_set_type, ConstPtrMutComptimeConst, ptr_is_const, ptr_is_volatile, 0);
        } else if (child_type->id == ZigTypeIdInt) {
            if (buf_eql_str(field_name, "bit_count")) {
                bool ptr_is_const = true;
                bool ptr_is_volatile = false;
                return ir_get_const_ptr(ira, &field_ptr_instruction->base,
                    create_const_unsigned_negative(ira->codegen->builtin_types.entry_num_lit_int,
                        child_type->data.integral.bit_count, false),
                    ira->codegen->builtin_types.entry_num_lit_int,
                    ConstPtrMutComptimeConst, ptr_is_const, ptr_is_volatile, 0);
            } else if (buf_eql_str(field_name, "is_signed")) {
                bool ptr_is_const = true;
                bool ptr_is_volatile = false;
                return ir_get_const_ptr(ira, &field_ptr_instruction->base,
                    create_const_bool(ira->codegen, child_type->data.integral.is_signed),
                    ira->codegen->builtin_types.entry_bool,
                    ConstPtrMutComptimeConst, ptr_is_const, ptr_is_volatile, 0);
            } else {
                ir_add_error(ira, &field_ptr_instruction->base,
                    buf_sprintf("type '%s' has no member called '%s'",
                        buf_ptr(&child_type->name), buf_ptr(field_name)));
                return ira->codegen->invalid_instruction;
            }
        } else if (child_type->id == ZigTypeIdFloat) {
            if (buf_eql_str(field_name, "bit_count")) {
                bool ptr_is_const = true;
                bool ptr_is_volatile = false;
                return ir_get_const_ptr(ira, &field_ptr_instruction->base,
                    create_const_unsigned_negative(ira->codegen->builtin_types.entry_num_lit_int,
                        child_type->data.floating.bit_count, false),
                    ira->codegen->builtin_types.entry_num_lit_int,
                    ConstPtrMutComptimeConst, ptr_is_const, ptr_is_volatile, 0);
            } else {
                ir_add_error(ira, &field_ptr_instruction->base,
                    buf_sprintf("type '%s' has no member called '%s'",
                        buf_ptr(&child_type->name), buf_ptr(field_name)));
                return ira->codegen->invalid_instruction;
            }
        } else if (child_type->id == ZigTypeIdPointer) {
            if (buf_eql_str(field_name, "Child")) {
                bool ptr_is_const = true;
                bool ptr_is_volatile = false;
                return ir_get_const_ptr(ira, &field_ptr_instruction->base,
                    create_const_type(ira->codegen, child_type->data.pointer.child_type),
                    ira->codegen->builtin_types.entry_type,
                    ConstPtrMutComptimeConst, ptr_is_const, ptr_is_volatile, 0);
            } else if (buf_eql_str(field_name, "alignment")) {
                bool ptr_is_const = true;
                bool ptr_is_volatile = false;
                if ((err = type_resolve(ira->codegen, child_type->data.pointer.child_type,
                                ResolveStatusAlignmentKnown)))
                {
                    return ira->codegen->invalid_instruction;
                }
                return ir_get_const_ptr(ira, &field_ptr_instruction->base,
                    create_const_unsigned_negative(ira->codegen->builtin_types.entry_num_lit_int,
                        get_ptr_align(ira->codegen, child_type), false),
                    ira->codegen->builtin_types.entry_num_lit_int,
                    ConstPtrMutComptimeConst, ptr_is_const, ptr_is_volatile, 0);
            } else {
                ir_add_error(ira, &field_ptr_instruction->base,
                    buf_sprintf("type '%s' has no member called '%s'",
                        buf_ptr(&child_type->name), buf_ptr(field_name)));
                return ira->codegen->invalid_instruction;
            }
        } else if (child_type->id == ZigTypeIdArray) {
            if (buf_eql_str(field_name, "Child")) {
                bool ptr_is_const = true;
                bool ptr_is_volatile = false;
                return ir_get_const_ptr(ira, &field_ptr_instruction->base,
                    create_const_type(ira->codegen, child_type->data.array.child_type),
                    ira->codegen->builtin_types.entry_type,
                    ConstPtrMutComptimeConst, ptr_is_const, ptr_is_volatile, 0);
            } else if (buf_eql_str(field_name, "len")) {
                bool ptr_is_const = true;
                bool ptr_is_volatile = false;
                return ir_get_const_ptr(ira, &field_ptr_instruction->base,
                    create_const_unsigned_negative(ira->codegen->builtin_types.entry_num_lit_int,
                        child_type->data.array.len, false),
                    ira->codegen->builtin_types.entry_num_lit_int,
                    ConstPtrMutComptimeConst, ptr_is_const, ptr_is_volatile, 0);
            } else {
                ir_add_error(ira, &field_ptr_instruction->base,
                    buf_sprintf("type '%s' has no member called '%s'",
                        buf_ptr(&child_type->name), buf_ptr(field_name)));
                return ira->codegen->invalid_instruction;
            }
        } else if (child_type->id == ZigTypeIdErrorUnion) {
            if (buf_eql_str(field_name, "Payload")) {
                bool ptr_is_const = true;
                bool ptr_is_volatile = false;
                return ir_get_const_ptr(ira, &field_ptr_instruction->base,
                    create_const_type(ira->codegen, child_type->data.error_union.payload_type),
                    ira->codegen->builtin_types.entry_type,
                    ConstPtrMutComptimeConst, ptr_is_const, ptr_is_volatile, 0);
            } else if (buf_eql_str(field_name, "ErrorSet")) {
                bool ptr_is_const = true;
                bool ptr_is_volatile = false;
                return ir_get_const_ptr(ira, &field_ptr_instruction->base,
                    create_const_type(ira->codegen, child_type->data.error_union.err_set_type),
                    ira->codegen->builtin_types.entry_type,
                    ConstPtrMutComptimeConst, ptr_is_const, ptr_is_volatile, 0);
            } else {
                ir_add_error(ira, &field_ptr_instruction->base,
                    buf_sprintf("type '%s' has no member called '%s'",
                        buf_ptr(&child_type->name), buf_ptr(field_name)));
                return ira->codegen->invalid_instruction;
            }
        } else if (child_type->id == ZigTypeIdOptional) {
            if (buf_eql_str(field_name, "Child")) {
                bool ptr_is_const = true;
                bool ptr_is_volatile = false;
                return ir_get_const_ptr(ira, &field_ptr_instruction->base,
                    create_const_type(ira->codegen, child_type->data.maybe.child_type),
                    ira->codegen->builtin_types.entry_type,
                    ConstPtrMutComptimeConst, ptr_is_const, ptr_is_volatile, 0);
            } else {
                ir_add_error(ira, &field_ptr_instruction->base,
                    buf_sprintf("type '%s' has no member called '%s'",
                        buf_ptr(&child_type->name), buf_ptr(field_name)));
                return ira->codegen->invalid_instruction;
            }
        } else if (child_type->id == ZigTypeIdFn) {
            if (buf_eql_str(field_name, "ReturnType")) {
                if (child_type->data.fn.fn_type_id.return_type == nullptr) {
                    // Return type can only ever be null, if the function is generic
                    assert(child_type->data.fn.is_generic);

                    ir_add_error(ira, &field_ptr_instruction->base,
                        buf_sprintf("ReturnType has not been resolved because '%s' is generic", buf_ptr(&child_type->name)));
                    return ira->codegen->invalid_instruction;
                }

                bool ptr_is_const = true;
                bool ptr_is_volatile = false;
                return ir_get_const_ptr(ira, &field_ptr_instruction->base,
                    create_const_type(ira->codegen, child_type->data.fn.fn_type_id.return_type),
                    ira->codegen->builtin_types.entry_type,
                    ConstPtrMutComptimeConst, ptr_is_const, ptr_is_volatile, 0);
            } else if (buf_eql_str(field_name, "is_var_args")) {
                bool ptr_is_const = true;
                bool ptr_is_volatile = false;
                return ir_get_const_ptr(ira, &field_ptr_instruction->base,
                    create_const_bool(ira->codegen, child_type->data.fn.fn_type_id.is_var_args),
                    ira->codegen->builtin_types.entry_bool,
                    ConstPtrMutComptimeConst, ptr_is_const, ptr_is_volatile, 0);
            } else if (buf_eql_str(field_name, "arg_count")) {
                bool ptr_is_const = true;
                bool ptr_is_volatile = false;
                return ir_get_const_ptr(ira, &field_ptr_instruction->base,
                    create_const_usize(ira->codegen, child_type->data.fn.fn_type_id.param_count),
                    ira->codegen->builtin_types.entry_usize,
                    ConstPtrMutComptimeConst, ptr_is_const, ptr_is_volatile, 0);
            } else {
                ir_add_error(ira, &field_ptr_instruction->base,
                    buf_sprintf("type '%s' has no member called '%s'",
                        buf_ptr(&child_type->name), buf_ptr(field_name)));
                return ira->codegen->invalid_instruction;
            }
        } else {
            ir_add_error(ira, &field_ptr_instruction->base,
                buf_sprintf("type '%s' does not support field access", buf_ptr(&child_type->name)));
            return ira->codegen->invalid_instruction;
        }
    } else if (container_type->id == ZigTypeIdNamespace) {
        assert(container_ptr->value.type->id == ZigTypeIdPointer);
        ConstExprValue *container_ptr_val = ir_resolve_const(ira, container_ptr, UndefBad);
        if (!container_ptr_val)
            return ira->codegen->invalid_instruction;

        ConstExprValue *namespace_val = const_ptr_pointee(ira, ira->codegen, container_ptr_val,
                field_ptr_instruction->base.source_node);
        if (namespace_val == nullptr)
            return ira->codegen->invalid_instruction;
        assert(namespace_val->special == ConstValSpecialStatic);

        ImportTableEntry *namespace_import = namespace_val->data.x_import;

        Tld *tld = find_decl(ira->codegen, &namespace_import->decls_scope->base, field_name);
        if (tld) {
            if (tld->visib_mod == VisibModPrivate &&
                tld->import != source_node->owner)
            {
                ErrorMsg *msg = ir_add_error_node(ira, source_node,
                    buf_sprintf("'%s' is private", buf_ptr(field_name)));
                add_error_note(ira->codegen, msg, tld->source_node, buf_sprintf("declared here"));
                return ira->codegen->invalid_instruction;
            }
            return ir_analyze_decl_ref(ira, &field_ptr_instruction->base, tld);
        } else {
            const char *import_name = namespace_import->path ? buf_ptr(namespace_import->path) : "(C import)";
            ir_add_error_node(ira, source_node,
                buf_sprintf("no member named '%s' in '%s'", buf_ptr(field_name), import_name));
            return ira->codegen->invalid_instruction;
        }
    } else {
        ir_add_error_node(ira, field_ptr_instruction->base.source_node,
            buf_sprintf("type '%s' does not support field access", buf_ptr(&container_type->name)));
        return ira->codegen->invalid_instruction;
    }
}

static IrInstruction *ir_analyze_instruction_store_ptr(IrAnalyze *ira, IrInstructionStorePtr *instruction) {
    IrInstruction *ptr = instruction->ptr->child;
    if (type_is_invalid(ptr->value.type))
        return ira->codegen->invalid_instruction;

    IrInstruction *value = instruction->value->child;
    if (type_is_invalid(value->value.type))
        return ira->codegen->invalid_instruction;

    return ir_analyze_store_ptr(ira, &instruction->base, ptr, value);
}

static IrInstruction *ir_analyze_instruction_load_ptr(IrAnalyze *ira, IrInstructionLoadPtr *instruction) {
    IrInstruction *ptr = instruction->ptr->child;
    if (type_is_invalid(ptr->value.type))
        return ira->codegen->invalid_instruction;
    return ir_get_deref(ira, &instruction->base, ptr);
}

static IrInstruction *ir_analyze_instruction_typeof(IrAnalyze *ira, IrInstructionTypeOf *typeof_instruction) {
    IrInstruction *expr_value = typeof_instruction->value->child;
    ZigType *type_entry = expr_value->value.type;
    if (type_is_invalid(type_entry))
        return ira->codegen->invalid_instruction;
    return ir_const_type(ira, &typeof_instruction->base, type_entry);
}

static IrInstruction *ir_analyze_instruction_to_ptr_type(IrAnalyze *ira,
        IrInstructionToPtrType *to_ptr_type_instruction)
{
    Error err;

    IrInstruction *value = to_ptr_type_instruction->value->child;
    ZigType *type_entry = value->value.type;
    if (type_is_invalid(type_entry))
        return ira->codegen->invalid_instruction;

    ZigType *ptr_type;
    if (type_entry->id == ZigTypeIdArray) {
        // TODO: Allow capturing pointer to const array.
        // const a = "123"; for (a) |*c| continue;
        // error: expected type '*u8', found '*const u8'
        ptr_type = get_pointer_to_type(ira->codegen, type_entry->data.array.child_type, false);
    } else if (is_array_ref(type_entry)) {
        ptr_type = get_pointer_to_type(ira->codegen,
            type_entry->data.pointer.child_type->data.array.child_type, type_entry->data.pointer.is_const);
    }  else if (is_slice(type_entry)) {
        ZigType *slice_ptr_type = type_entry->data.structure.fields[0].type_entry;
        ptr_type = adjust_ptr_len(ira->codegen, slice_ptr_type, PtrLenSingle);
        // If the pointer is over-aligned, we may have to reduce it based on the alignment of the element type.
        if (slice_ptr_type->data.pointer.explicit_alignment != 0) {
            ZigType *elem_type = slice_ptr_type->data.pointer.child_type;
            if ((err = type_resolve(ira->codegen, elem_type, ResolveStatusAlignmentKnown)))
                return ira->codegen->invalid_instruction;
            uint32_t elem_align = get_abi_alignment(ira->codegen, elem_type);
            uint32_t reduced_align = min(elem_align, slice_ptr_type->data.pointer.explicit_alignment);
            ptr_type = adjust_ptr_align(ira->codegen, ptr_type, reduced_align);
        }
    } else if (type_entry->id == ZigTypeIdArgTuple) {
        ConstExprValue *arg_tuple_val = ir_resolve_const(ira, value, UndefBad);
        if (!arg_tuple_val)
            return ira->codegen->invalid_instruction;
        zig_panic("TODO for loop on var args");
    } else {
        ir_add_error_node(ira, to_ptr_type_instruction->base.source_node,
                buf_sprintf("expected array type, found '%s'", buf_ptr(&type_entry->name)));
        return ira->codegen->invalid_instruction;
    }

    return ir_const_type(ira, &to_ptr_type_instruction->base, ptr_type);
}

static IrInstruction *ir_analyze_instruction_ptr_type_child(IrAnalyze *ira,
        IrInstructionPtrTypeChild *ptr_type_child_instruction)
{
    IrInstruction *type_value = ptr_type_child_instruction->value->child;
    ZigType *type_entry = ir_resolve_type(ira, type_value);
    if (type_is_invalid(type_entry))
        return ira->codegen->invalid_instruction;

    if (type_entry->id != ZigTypeIdPointer) {
        ir_add_error_node(ira, ptr_type_child_instruction->base.source_node,
                buf_sprintf("expected pointer type, found '%s'", buf_ptr(&type_entry->name)));
        return ira->codegen->invalid_instruction;
    }

    return ir_const_type(ira, &ptr_type_child_instruction->base, type_entry->data.pointer.child_type);
}

static IrInstruction *ir_analyze_instruction_set_cold(IrAnalyze *ira, IrInstructionSetCold *instruction) {
    if (ira->new_irb.exec->is_inline) {
        // ignore setCold when running functions at compile time
        return ir_const_void(ira, &instruction->base);
    }

    IrInstruction *is_cold_value = instruction->is_cold->child;
    bool want_cold;
    if (!ir_resolve_bool(ira, is_cold_value, &want_cold))
        return ira->codegen->invalid_instruction;

    ZigFn *fn_entry = scope_fn_entry(instruction->base.scope);
    if (fn_entry == nullptr) {
        ir_add_error(ira, &instruction->base, buf_sprintf("@setCold outside function"));
        return ira->codegen->invalid_instruction;
    }

    if (fn_entry->set_cold_node != nullptr) {
        ErrorMsg *msg = ir_add_error(ira, &instruction->base, buf_sprintf("cold set twice in same function"));
        add_error_note(ira->codegen, msg, fn_entry->set_cold_node, buf_sprintf("first set here"));
        return ira->codegen->invalid_instruction;
    }

    fn_entry->set_cold_node = instruction->base.source_node;
    fn_entry->is_cold = want_cold;

    return ir_const_void(ira, &instruction->base);
}

static IrInstruction *ir_analyze_instruction_set_runtime_safety(IrAnalyze *ira,
        IrInstructionSetRuntimeSafety *set_runtime_safety_instruction)
{
    if (ira->new_irb.exec->is_inline) {
        // ignore setRuntimeSafety when running functions at compile time
        return ir_const_void(ira, &set_runtime_safety_instruction->base);
    }

    bool *safety_off_ptr;
    AstNode **safety_set_node_ptr;

    Scope *scope = set_runtime_safety_instruction->base.scope;
    while (scope != nullptr) {
        if (scope->id == ScopeIdBlock) {
            ScopeBlock *block_scope = (ScopeBlock *)scope;
            safety_off_ptr = &block_scope->safety_off;
            safety_set_node_ptr = &block_scope->safety_set_node;
            break;
        } else if (scope->id == ScopeIdFnDef) {
            ScopeFnDef *def_scope = (ScopeFnDef *)scope;
            ZigFn *target_fn = def_scope->fn_entry;
            assert(target_fn->def_scope != nullptr);
            safety_off_ptr = &target_fn->def_scope->safety_off;
            safety_set_node_ptr = &target_fn->def_scope->safety_set_node;
            break;
        } else if (scope->id == ScopeIdDecls) {
            ScopeDecls *decls_scope = (ScopeDecls *)scope;
            safety_off_ptr = &decls_scope->safety_off;
            safety_set_node_ptr = &decls_scope->safety_set_node;
            break;
        } else {
            scope = scope->parent;
            continue;
        }
    }
    assert(scope != nullptr);

    IrInstruction *safety_on_value = set_runtime_safety_instruction->safety_on->child;
    bool want_runtime_safety;
    if (!ir_resolve_bool(ira, safety_on_value, &want_runtime_safety))
        return ira->codegen->invalid_instruction;

    AstNode *source_node = set_runtime_safety_instruction->base.source_node;
    if (*safety_set_node_ptr) {
        ErrorMsg *msg = ir_add_error_node(ira, source_node,
                buf_sprintf("runtime safety set twice for same scope"));
        add_error_note(ira->codegen, msg, *safety_set_node_ptr, buf_sprintf("first set here"));
        return ira->codegen->invalid_instruction;
    }
    *safety_set_node_ptr = source_node;
    *safety_off_ptr = !want_runtime_safety;

    return ir_const_void(ira, &set_runtime_safety_instruction->base);
}

static IrInstruction *ir_analyze_instruction_set_float_mode(IrAnalyze *ira,
        IrInstructionSetFloatMode *instruction)
{
    if (ira->new_irb.exec->is_inline) {
        // ignore setFloatMode when running functions at compile time
        return ir_const_void(ira, &instruction->base);
    }

    bool *fast_math_on_ptr;
    AstNode **fast_math_set_node_ptr;

    Scope *scope = instruction->base.scope;
    while (scope != nullptr) {
        if (scope->id == ScopeIdBlock) {
            ScopeBlock *block_scope = (ScopeBlock *)scope;
            fast_math_on_ptr = &block_scope->fast_math_on;
            fast_math_set_node_ptr = &block_scope->fast_math_set_node;
            break;
        } else if (scope->id == ScopeIdFnDef) {
            ScopeFnDef *def_scope = (ScopeFnDef *)scope;
            ZigFn *target_fn = def_scope->fn_entry;
            assert(target_fn->def_scope != nullptr);
            fast_math_on_ptr = &target_fn->def_scope->fast_math_on;
            fast_math_set_node_ptr = &target_fn->def_scope->fast_math_set_node;
            break;
        } else if (scope->id == ScopeIdDecls) {
            ScopeDecls *decls_scope = (ScopeDecls *)scope;
            fast_math_on_ptr = &decls_scope->fast_math_on;
            fast_math_set_node_ptr = &decls_scope->fast_math_set_node;
            break;
        } else {
            scope = scope->parent;
            continue;
        }
    }
    assert(scope != nullptr);

    IrInstruction *float_mode_value = instruction->mode_value->child;
    FloatMode float_mode_scalar;
    if (!ir_resolve_float_mode(ira, float_mode_value, &float_mode_scalar))
        return ira->codegen->invalid_instruction;

    AstNode *source_node = instruction->base.source_node;
    if (*fast_math_set_node_ptr) {
        ErrorMsg *msg = ir_add_error_node(ira, source_node,
                buf_sprintf("float mode set twice for same scope"));
        add_error_note(ira->codegen, msg, *fast_math_set_node_ptr, buf_sprintf("first set here"));
        return ira->codegen->invalid_instruction;
    }
    *fast_math_set_node_ptr = source_node;
    *fast_math_on_ptr = (float_mode_scalar == FloatModeOptimized);

    return ir_const_void(ira, &instruction->base);
}

static IrInstruction *ir_analyze_instruction_slice_type(IrAnalyze *ira,
        IrInstructionSliceType *slice_type_instruction)
{
    Error err;
    uint32_t align_bytes = 0;
    if (slice_type_instruction->align_value != nullptr) {
        if (!ir_resolve_align(ira, slice_type_instruction->align_value->child, &align_bytes))
            return ira->codegen->invalid_instruction;
    }

    ZigType *child_type = ir_resolve_type(ira, slice_type_instruction->child_type->child);
    if (type_is_invalid(child_type))
        return ira->codegen->invalid_instruction;

    bool is_const = slice_type_instruction->is_const;
    bool is_volatile = slice_type_instruction->is_volatile;

    switch (child_type->id) {
        case ZigTypeIdInvalid: // handled above
            zig_unreachable();
        case ZigTypeIdUnreachable:
        case ZigTypeIdUndefined:
        case ZigTypeIdNull:
        case ZigTypeIdArgTuple:
        case ZigTypeIdOpaque:
            ir_add_error_node(ira, slice_type_instruction->base.source_node,
                    buf_sprintf("slice of type '%s' not allowed", buf_ptr(&child_type->name)));
            return ira->codegen->invalid_instruction;
        case ZigTypeIdMetaType:
        case ZigTypeIdVoid:
        case ZigTypeIdBool:
        case ZigTypeIdInt:
        case ZigTypeIdFloat:
        case ZigTypeIdPointer:
        case ZigTypeIdArray:
        case ZigTypeIdStruct:
        case ZigTypeIdComptimeFloat:
        case ZigTypeIdComptimeInt:
        case ZigTypeIdOptional:
        case ZigTypeIdErrorUnion:
        case ZigTypeIdErrorSet:
        case ZigTypeIdEnum:
        case ZigTypeIdUnion:
        case ZigTypeIdFn:
        case ZigTypeIdNamespace:
        case ZigTypeIdBoundFn:
        case ZigTypeIdPromise:
        case ZigTypeIdVector:
            {
                if ((err = type_resolve(ira->codegen, child_type, ResolveStatusZeroBitsKnown)))
                    return ira->codegen->invalid_instruction;
                ZigType *slice_ptr_type = get_pointer_to_type_extra(ira->codegen, child_type,
                        is_const, is_volatile, PtrLenUnknown, align_bytes, 0, 0);
                ZigType *result_type = get_slice_type(ira->codegen, slice_ptr_type);
                return ir_const_type(ira, &slice_type_instruction->base, result_type);
            }
    }
    zig_unreachable();
}

static IrInstruction *ir_analyze_instruction_asm(IrAnalyze *ira, IrInstructionAsm *asm_instruction) {
    assert(asm_instruction->base.source_node->type == NodeTypeAsmExpr);

    AstNodeAsmExpr *asm_expr = &asm_instruction->base.source_node->data.asm_expr;

    bool global_scope = (scope_fn_entry(asm_instruction->base.scope) == nullptr);
    if (global_scope) {
        if (asm_expr->output_list.length != 0 || asm_expr->input_list.length != 0 ||
            asm_expr->clobber_list.length != 0)
        {
            ir_add_error(ira, &asm_instruction->base,
                buf_sprintf("global assembly cannot have inputs, outputs, or clobbers"));
            return ira->codegen->invalid_instruction;
        }

        buf_append_char(&ira->codegen->global_asm, '\n');
        buf_append_buf(&ira->codegen->global_asm, asm_expr->asm_template);

        return ir_const_void(ira, &asm_instruction->base);
    }

    if (!ir_emit_global_runtime_side_effect(ira, &asm_instruction->base))
        return ira->codegen->invalid_instruction;

    // TODO validate the output types and variable types

    IrInstruction **input_list = allocate<IrInstruction *>(asm_expr->input_list.length);
    IrInstruction **output_types = allocate<IrInstruction *>(asm_expr->output_list.length);

    ZigType *return_type = ira->codegen->builtin_types.entry_void;
    for (size_t i = 0; i < asm_expr->output_list.length; i += 1) {
        AsmOutput *asm_output = asm_expr->output_list.at(i);
        if (asm_output->return_type) {
            output_types[i] = asm_instruction->output_types[i]->child;
            return_type = ir_resolve_type(ira, output_types[i]);
            if (type_is_invalid(return_type))
                return ira->codegen->invalid_instruction;
        }
    }

    for (size_t i = 0; i < asm_expr->input_list.length; i += 1) {
        IrInstruction *const input_value = asm_instruction->input_list[i]->child;
        if (type_is_invalid(input_value->value.type))
            return ira->codegen->invalid_instruction;

        if (instr_is_comptime(input_value) &&
            (input_value->value.type->id == ZigTypeIdComptimeInt ||
            input_value->value.type->id == ZigTypeIdComptimeFloat)) {
            ir_add_error_node(ira, input_value->source_node,
                buf_sprintf("expected sized integer or sized float, found %s", buf_ptr(&input_value->value.type->name)));
            return ira->codegen->invalid_instruction;
        }

        input_list[i] = input_value;
    }

    IrInstruction *result = ir_build_asm(&ira->new_irb,
        asm_instruction->base.scope, asm_instruction->base.source_node,
        input_list, output_types, asm_instruction->output_vars, asm_instruction->return_count,
        asm_instruction->has_side_effects);
    result->value.type = return_type;
    return result;
}

static IrInstruction *ir_analyze_instruction_array_type(IrAnalyze *ira,
        IrInstructionArrayType *array_type_instruction)
{
    Error err;

    IrInstruction *size_value = array_type_instruction->size->child;
    uint64_t size;
    if (!ir_resolve_usize(ira, size_value, &size))
        return ira->codegen->invalid_instruction;

    IrInstruction *child_type_value = array_type_instruction->child_type->child;
    ZigType *child_type = ir_resolve_type(ira, child_type_value);
    if (type_is_invalid(child_type))
        return ira->codegen->invalid_instruction;
    switch (child_type->id) {
        case ZigTypeIdInvalid: // handled above
            zig_unreachable();
        case ZigTypeIdUnreachable:
        case ZigTypeIdUndefined:
        case ZigTypeIdNull:
        case ZigTypeIdArgTuple:
        case ZigTypeIdOpaque:
            ir_add_error_node(ira, array_type_instruction->base.source_node,
                    buf_sprintf("array of type '%s' not allowed", buf_ptr(&child_type->name)));
            return ira->codegen->invalid_instruction;
        case ZigTypeIdMetaType:
        case ZigTypeIdVoid:
        case ZigTypeIdBool:
        case ZigTypeIdInt:
        case ZigTypeIdFloat:
        case ZigTypeIdPointer:
        case ZigTypeIdArray:
        case ZigTypeIdStruct:
        case ZigTypeIdComptimeFloat:
        case ZigTypeIdComptimeInt:
        case ZigTypeIdOptional:
        case ZigTypeIdErrorUnion:
        case ZigTypeIdErrorSet:
        case ZigTypeIdEnum:
        case ZigTypeIdUnion:
        case ZigTypeIdFn:
        case ZigTypeIdNamespace:
        case ZigTypeIdBoundFn:
        case ZigTypeIdPromise:
        case ZigTypeIdVector:
            {
                if ((err = ensure_complete_type(ira->codegen, child_type)))
                    return ira->codegen->invalid_instruction;
                ZigType *result_type = get_array_type(ira->codegen, child_type, size);
                return ir_const_type(ira, &array_type_instruction->base, result_type);
            }
    }
    zig_unreachable();
}

static IrInstruction *ir_analyze_instruction_promise_type(IrAnalyze *ira, IrInstructionPromiseType *instruction) {
    ZigType *promise_type;

    if (instruction->payload_type == nullptr) {
        promise_type = ira->codegen->builtin_types.entry_promise;
    } else {
        ZigType *payload_type = ir_resolve_type(ira, instruction->payload_type->child);
        if (type_is_invalid(payload_type))
            return ira->codegen->invalid_instruction;

        promise_type = get_promise_type(ira->codegen, payload_type);
    }

    return ir_const_type(ira, &instruction->base, promise_type);
}

static IrInstruction *ir_analyze_instruction_size_of(IrAnalyze *ira,
        IrInstructionSizeOf *size_of_instruction)
{
    Error err;
    IrInstruction *type_value = size_of_instruction->type_value->child;
    ZigType *type_entry = ir_resolve_type(ira, type_value);

    if ((err = ensure_complete_type(ira->codegen, type_entry)))
        return ira->codegen->invalid_instruction;

    switch (type_entry->id) {
        case ZigTypeIdInvalid: // handled above
            zig_unreachable();
        case ZigTypeIdUnreachable:
        case ZigTypeIdUndefined:
        case ZigTypeIdNull:
        case ZigTypeIdComptimeFloat:
        case ZigTypeIdComptimeInt:
        case ZigTypeIdBoundFn:
        case ZigTypeIdMetaType:
        case ZigTypeIdNamespace:
        case ZigTypeIdArgTuple:
        case ZigTypeIdOpaque:
            ir_add_error_node(ira, size_of_instruction->base.source_node,
                    buf_sprintf("no size available for type '%s'", buf_ptr(&type_entry->name)));
            return ira->codegen->invalid_instruction;
        case ZigTypeIdVoid:
        case ZigTypeIdBool:
        case ZigTypeIdInt:
        case ZigTypeIdFloat:
        case ZigTypeIdPointer:
        case ZigTypeIdArray:
        case ZigTypeIdStruct:
        case ZigTypeIdOptional:
        case ZigTypeIdErrorUnion:
        case ZigTypeIdErrorSet:
        case ZigTypeIdEnum:
        case ZigTypeIdUnion:
        case ZigTypeIdFn:
        case ZigTypeIdPromise:
        case ZigTypeIdVector:
            {
                uint64_t size_in_bytes = type_size(ira->codegen, type_entry);
                return ir_const_unsigned(ira, &size_of_instruction->base, size_in_bytes);
            }
    }
    zig_unreachable();
}

static IrInstruction *ir_analyze_test_non_null(IrAnalyze *ira, IrInstruction *source_inst, IrInstruction *value) {
    ZigType *type_entry = value->value.type;

    if (type_entry->id == ZigTypeIdOptional) {
        if (instr_is_comptime(value)) {
            ConstExprValue *maybe_val = ir_resolve_const(ira, value, UndefBad);
            if (!maybe_val)
                return ira->codegen->invalid_instruction;

            return ir_const_bool(ira, source_inst, !optional_value_is_null(maybe_val));
        }

        IrInstruction *result = ir_build_test_nonnull(&ira->new_irb,
            source_inst->scope, source_inst->source_node, value);
        result->value.type = ira->codegen->builtin_types.entry_bool;
        return result;
    } else if (type_entry->id == ZigTypeIdNull) {
        return ir_const_bool(ira, source_inst, false);
    } else {
        return ir_const_bool(ira, source_inst, true);
    }
}

static IrInstruction *ir_analyze_instruction_test_non_null(IrAnalyze *ira, IrInstructionTestNonNull *instruction) {
    IrInstruction *value = instruction->value->child;
    if (type_is_invalid(value->value.type))
        return ira->codegen->invalid_instruction;

    return ir_analyze_test_non_null(ira, &instruction->base, value);
}

static IrInstruction *ir_analyze_unwrap_optional_payload(IrAnalyze *ira, IrInstruction *source_instr,
        IrInstruction *base_ptr, bool safety_check_on)
{
    ZigType *ptr_type = base_ptr->value.type;
    assert(ptr_type->id == ZigTypeIdPointer);

    ZigType *type_entry = ptr_type->data.pointer.child_type;
    if (type_is_invalid(type_entry))
        return ira->codegen->invalid_instruction;

    if (type_entry->id != ZigTypeIdOptional) {
        ir_add_error_node(ira, base_ptr->source_node,
                buf_sprintf("expected optional type, found '%s'", buf_ptr(&type_entry->name)));
        return ira->codegen->invalid_instruction;
    }

    ZigType *child_type = type_entry->data.maybe.child_type;
    ZigType *result_type = get_pointer_to_type_extra(ira->codegen, child_type,
            ptr_type->data.pointer.is_const, ptr_type->data.pointer.is_volatile, PtrLenSingle, 0, 0, 0);

    if (instr_is_comptime(base_ptr)) {
        ConstExprValue *val = ir_resolve_const(ira, base_ptr, UndefBad);
        if (!val)
            return ira->codegen->invalid_instruction;
        ConstExprValue *maybe_val = const_ptr_pointee(ira, ira->codegen, val, source_instr->source_node);
        if (maybe_val == nullptr)
            return ira->codegen->invalid_instruction;

        if (val->data.x_ptr.mut != ConstPtrMutRuntimeVar) {
            if (optional_value_is_null(maybe_val)) {
                ir_add_error(ira, source_instr, buf_sprintf("unable to unwrap null"));
                return ira->codegen->invalid_instruction;
            }
            IrInstruction *result = ir_const(ira, source_instr, result_type);
            ConstExprValue *out_val = &result->value;
            out_val->data.x_ptr.special = ConstPtrSpecialRef;
            out_val->data.x_ptr.mut = val->data.x_ptr.mut;
            if (types_have_same_zig_comptime_repr(type_entry, child_type)) {
                out_val->data.x_ptr.data.ref.pointee = maybe_val;
            } else {
                out_val->data.x_ptr.data.ref.pointee = maybe_val->data.x_optional;
            }
            return result;
        }
    }

    IrInstruction *result = ir_build_optional_unwrap_ptr(&ira->new_irb, source_instr->scope,
            source_instr->source_node, base_ptr, safety_check_on);
    result->value.type = result_type;
    return result;
}

static IrInstruction *ir_analyze_instruction_optional_unwrap_ptr(IrAnalyze *ira,
        IrInstructionOptionalUnwrapPtr *instruction)
{
    IrInstruction *base_ptr = instruction->base_ptr->child;
    if (type_is_invalid(base_ptr->value.type))
        return ira->codegen->invalid_instruction;

    return ir_analyze_unwrap_optional_payload(ira, &instruction->base, base_ptr, instruction->safety_check_on);
}

static IrInstruction *ir_analyze_instruction_ctz(IrAnalyze *ira, IrInstructionCtz *ctz_instruction) {
    IrInstruction *value = ctz_instruction->value->child;
    if (type_is_invalid(value->value.type)) {
        return ira->codegen->invalid_instruction;
    } else if (value->value.type->id == ZigTypeIdInt) {
        ZigType *return_type = get_smallest_unsigned_int_type(ira->codegen,
                value->value.type->data.integral.bit_count);
        if (value->value.special != ConstValSpecialRuntime) {
            size_t result_usize = bigint_ctz(&value->value.data.x_bigint,
                    value->value.type->data.integral.bit_count);
            IrInstruction *result = ir_const(ira, &ctz_instruction->base, return_type);
            bigint_init_unsigned(&result->value.data.x_bigint, result_usize);
            return result;
        }

        IrInstruction *result = ir_build_ctz(&ira->new_irb,
            ctz_instruction->base.scope, ctz_instruction->base.source_node, value);
        result->value.type = return_type;
        return result;
    } else {
        ir_add_error_node(ira, ctz_instruction->base.source_node,
            buf_sprintf("expected integer type, found '%s'", buf_ptr(&value->value.type->name)));
        return ira->codegen->invalid_instruction;
    }
}

static IrInstruction *ir_analyze_instruction_clz(IrAnalyze *ira, IrInstructionClz *clz_instruction) {
    IrInstruction *value = clz_instruction->value->child;
    if (type_is_invalid(value->value.type)) {
        return ira->codegen->invalid_instruction;
    } else if (value->value.type->id == ZigTypeIdInt) {
        ZigType *return_type = get_smallest_unsigned_int_type(ira->codegen,
                value->value.type->data.integral.bit_count);
        if (value->value.special != ConstValSpecialRuntime) {
            size_t result_usize = bigint_clz(&value->value.data.x_bigint,
                    value->value.type->data.integral.bit_count);
            IrInstruction *result = ir_const(ira, &clz_instruction->base, return_type);
            bigint_init_unsigned(&result->value.data.x_bigint, result_usize);
            return result;
        }

        IrInstruction *result = ir_build_clz(&ira->new_irb,
            clz_instruction->base.scope, clz_instruction->base.source_node, value);
        result->value.type = return_type;
        return result;
    } else {
        ir_add_error_node(ira, clz_instruction->base.source_node,
            buf_sprintf("expected integer type, found '%s'", buf_ptr(&value->value.type->name)));
        return ira->codegen->invalid_instruction;
    }
}

static IrInstruction *ir_analyze_instruction_pop_count(IrAnalyze *ira, IrInstructionPopCount *instruction) {
    IrInstruction *value = instruction->value->child;
    if (type_is_invalid(value->value.type))
        return ira->codegen->invalid_instruction;

    if (value->value.type->id != ZigTypeIdInt && value->value.type->id != ZigTypeIdComptimeInt) {
        ir_add_error(ira, value,
            buf_sprintf("expected integer type, found '%s'", buf_ptr(&value->value.type->name)));
        return ira->codegen->invalid_instruction;
    }

    if (instr_is_comptime(value)) {
        ConstExprValue *val = ir_resolve_const(ira, value, UndefBad);
        if (!val)
            return ira->codegen->invalid_instruction;
        if (bigint_cmp_zero(&val->data.x_bigint) != CmpLT) {
            size_t result = bigint_popcount_unsigned(&val->data.x_bigint);
            return ir_const_unsigned(ira, &instruction->base, result);
        }
        if (value->value.type->id == ZigTypeIdComptimeInt) {
            Buf *val_buf = buf_alloc();
            bigint_append_buf(val_buf, &val->data.x_bigint, 10);
            ir_add_error(ira, &instruction->base,
                buf_sprintf("@popCount on negative %s value %s",
                    buf_ptr(&value->value.type->name), buf_ptr(val_buf)));
            return ira->codegen->invalid_instruction;
        }
        size_t result = bigint_popcount_signed(&val->data.x_bigint, value->value.type->data.integral.bit_count);
        return ir_const_unsigned(ira, &instruction->base, result);
    }

    IrInstruction *result = ir_build_pop_count(&ira->new_irb, instruction->base.scope,
            instruction->base.source_node, value);
    result->value.type = get_smallest_unsigned_int_type(ira->codegen, value->value.type->data.integral.bit_count);
    return result;
}

static IrInstruction *ir_analyze_union_tag(IrAnalyze *ira, IrInstruction *source_instr, IrInstruction *value) {
    if (type_is_invalid(value->value.type))
        return ira->codegen->invalid_instruction;

    if (value->value.type->id == ZigTypeIdEnum) {
        return value;
    }

    if (value->value.type->id != ZigTypeIdUnion) {
        ir_add_error(ira, value,
            buf_sprintf("expected enum or union type, found '%s'", buf_ptr(&value->value.type->name)));
        return ira->codegen->invalid_instruction;
    }
    if (!value->value.type->data.unionation.have_explicit_tag_type && !source_instr->is_gen) {
        ErrorMsg *msg = ir_add_error(ira, source_instr, buf_sprintf("union has no associated enum"));
        if (value->value.type->data.unionation.decl_node != nullptr) {
            add_error_note(ira->codegen, msg, value->value.type->data.unionation.decl_node,
                    buf_sprintf("declared here"));
        }
        return ira->codegen->invalid_instruction;
    }

    ZigType *tag_type = value->value.type->data.unionation.tag_type;
    assert(tag_type->id == ZigTypeIdEnum);

    if (instr_is_comptime(value)) {
        ConstExprValue *val = ir_resolve_const(ira, value, UndefBad);
        if (!val)
            return ira->codegen->invalid_instruction;

        IrInstructionConst *const_instruction = ir_create_instruction<IrInstructionConst>(&ira->new_irb,
                source_instr->scope, source_instr->source_node);
        const_instruction->base.value.type = tag_type;
        const_instruction->base.value.special = ConstValSpecialStatic;
        bigint_init_bigint(&const_instruction->base.value.data.x_enum_tag, &val->data.x_union.tag);
        return &const_instruction->base;
    }

    IrInstruction *result = ir_build_union_tag(&ira->new_irb, source_instr->scope, source_instr->source_node, value);
    result->value.type = tag_type;
    return result;
}

static IrInstruction *ir_analyze_instruction_switch_br(IrAnalyze *ira,
        IrInstructionSwitchBr *switch_br_instruction)
{
    IrInstruction *target_value = switch_br_instruction->target_value->child;
    if (type_is_invalid(target_value->value.type))
        return ir_unreach_error(ira);

    if (switch_br_instruction->switch_prongs_void != nullptr) {
        if (type_is_invalid(switch_br_instruction->switch_prongs_void->child->value.type)) {
            return ir_unreach_error(ira);
        }
    }


    size_t case_count = switch_br_instruction->case_count;

    bool is_comptime;
    if (!ir_resolve_comptime(ira, switch_br_instruction->is_comptime->child, &is_comptime))
        return ira->codegen->invalid_instruction;

    if (is_comptime || instr_is_comptime(target_value)) {
        ConstExprValue *target_val = ir_resolve_const(ira, target_value, UndefBad);
        if (!target_val)
            return ir_unreach_error(ira);

        IrBasicBlock *old_dest_block = switch_br_instruction->else_block;
        for (size_t i = 0; i < case_count; i += 1) {
            IrInstructionSwitchBrCase *old_case = &switch_br_instruction->cases[i];
            IrInstruction *case_value = old_case->value->child;
            if (type_is_invalid(case_value->value.type))
                return ir_unreach_error(ira);

            if (case_value->value.type->id == ZigTypeIdEnum) {
                case_value = ir_analyze_union_tag(ira, &switch_br_instruction->base, case_value);
                if (type_is_invalid(case_value->value.type))
                    return ir_unreach_error(ira);
            }

            IrInstruction *casted_case_value = ir_implicit_cast(ira, case_value, target_value->value.type);
            if (type_is_invalid(casted_case_value->value.type))
                return ir_unreach_error(ira);

            ConstExprValue *case_val = ir_resolve_const(ira, casted_case_value, UndefBad);
            if (!case_val)
                return ir_unreach_error(ira);

            if (const_values_equal(ira->codegen, target_val, case_val)) {
                old_dest_block = old_case->block;
                break;
            }
        }

        if (is_comptime || old_dest_block->ref_count == 1) {
            return ir_inline_bb(ira, &switch_br_instruction->base, old_dest_block);
        } else {
            IrBasicBlock *new_dest_block = ir_get_new_bb(ira, old_dest_block, &switch_br_instruction->base);
            IrInstruction *result = ir_build_br(&ira->new_irb,
                switch_br_instruction->base.scope, switch_br_instruction->base.source_node,
                new_dest_block, nullptr);
            result->value.type = ira->codegen->builtin_types.entry_unreachable;
            return ir_finish_anal(ira, result);
        }
    }

    IrInstructionSwitchBrCase *cases = allocate<IrInstructionSwitchBrCase>(case_count);
    for (size_t i = 0; i < case_count; i += 1) {
        IrInstructionSwitchBrCase *old_case = &switch_br_instruction->cases[i];
        IrInstructionSwitchBrCase *new_case = &cases[i];
        new_case->block = ir_get_new_bb(ira, old_case->block, &switch_br_instruction->base);
        new_case->value = ira->codegen->invalid_instruction;

        // Calling ir_get_new_bb set the ref_instruction on the new basic block.
        // However a switch br may branch to the same basic block which would trigger an
        // incorrect re-generation of the block. So we set it to null here and assign
        // it back after the loop.
        new_case->block->ref_instruction = nullptr;

        IrInstruction *old_value = old_case->value;
        IrInstruction *new_value = old_value->child;
        if (type_is_invalid(new_value->value.type))
            continue;

        if (new_value->value.type->id == ZigTypeIdEnum) {
            new_value = ir_analyze_union_tag(ira, &switch_br_instruction->base, new_value);
            if (type_is_invalid(new_value->value.type))
                continue;
        }

        IrInstruction *casted_new_value = ir_implicit_cast(ira, new_value, target_value->value.type);
        if (type_is_invalid(casted_new_value->value.type))
            continue;

        if (!ir_resolve_const(ira, casted_new_value, UndefBad))
            continue;

        new_case->value = casted_new_value;
    }

    for (size_t i = 0; i < case_count; i += 1) {
        IrInstructionSwitchBrCase *new_case = &cases[i];
        if (new_case->value == ira->codegen->invalid_instruction)
            return ir_unreach_error(ira);
        new_case->block->ref_instruction = &switch_br_instruction->base;
    }

    IrBasicBlock *new_else_block = ir_get_new_bb(ira, switch_br_instruction->else_block, &switch_br_instruction->base);
    IrInstruction *result = ir_build_switch_br(&ira->new_irb,
        switch_br_instruction->base.scope, switch_br_instruction->base.source_node,
        target_value, new_else_block, case_count, cases, nullptr, nullptr);
    result->value.type = ira->codegen->builtin_types.entry_unreachable;
    return ir_finish_anal(ira, result);
}

static IrInstruction *ir_analyze_instruction_switch_target(IrAnalyze *ira,
        IrInstructionSwitchTarget *switch_target_instruction)
{
    Error err;
    IrInstruction *target_value_ptr = switch_target_instruction->target_value_ptr->child;
    if (type_is_invalid(target_value_ptr->value.type))
        return ira->codegen->invalid_instruction;

    if (target_value_ptr->value.type->id == ZigTypeIdMetaType) {
        assert(instr_is_comptime(target_value_ptr));
        ZigType *ptr_type = target_value_ptr->value.data.x_type;
        assert(ptr_type->id == ZigTypeIdPointer);
        return ir_const_type(ira, &switch_target_instruction->base, ptr_type->data.pointer.child_type);
    }

    ZigType *target_type = target_value_ptr->value.type->data.pointer.child_type;
    ConstExprValue *pointee_val = nullptr;
    if (instr_is_comptime(target_value_ptr)) {
        pointee_val = const_ptr_pointee(ira, ira->codegen, &target_value_ptr->value, target_value_ptr->source_node);
        if (pointee_val == nullptr)
            return ira->codegen->invalid_instruction;

        if (pointee_val->special == ConstValSpecialRuntime)
            pointee_val = nullptr;
    }
    if ((err = ensure_complete_type(ira->codegen, target_type)))
        return ira->codegen->invalid_instruction;

    switch (target_type->id) {
        case ZigTypeIdInvalid:
            zig_unreachable();
        case ZigTypeIdMetaType:
        case ZigTypeIdVoid:
        case ZigTypeIdBool:
        case ZigTypeIdInt:
        case ZigTypeIdFloat:
        case ZigTypeIdComptimeFloat:
        case ZigTypeIdComptimeInt:
        case ZigTypeIdPointer:
        case ZigTypeIdPromise:
        case ZigTypeIdFn:
        case ZigTypeIdNamespace:
        case ZigTypeIdErrorSet: {
            if (pointee_val) {
                IrInstruction *result = ir_const(ira, &switch_target_instruction->base, nullptr);
                copy_const_val(&result->value, pointee_val, true);
                result->value.type = target_type;
                return result;
            }

            IrInstruction *result = ir_get_deref(ira, &switch_target_instruction->base, target_value_ptr);
            result->value.type = target_type;
            return result;
        }
        case ZigTypeIdUnion: {
            AstNode *decl_node = target_type->data.unionation.decl_node;
            if (!decl_node->data.container_decl.auto_enum &&
                decl_node->data.container_decl.init_arg_expr == nullptr)
            {
                ErrorMsg *msg = ir_add_error(ira, target_value_ptr,
                    buf_sprintf("switch on union which has no attached enum"));
                add_error_note(ira->codegen, msg, decl_node,
                        buf_sprintf("consider 'union(enum)' here"));
                return ira->codegen->invalid_instruction;
            }
            ZigType *tag_type = target_type->data.unionation.tag_type;
            assert(tag_type != nullptr);
            assert(tag_type->id == ZigTypeIdEnum);
            if (pointee_val) {
                IrInstruction *result = ir_const(ira, &switch_target_instruction->base, tag_type);
                bigint_init_bigint(&result->value.data.x_enum_tag, &pointee_val->data.x_union.tag);
                return result;
            }
            if (tag_type->data.enumeration.src_field_count == 1) {
                IrInstruction *result = ir_const(ira, &switch_target_instruction->base, tag_type);
                TypeEnumField *only_field = &tag_type->data.enumeration.fields[0];
                bigint_init_bigint(&result->value.data.x_enum_tag, &only_field->value);
                return result;
            }

            IrInstruction *union_value = ir_get_deref(ira, &switch_target_instruction->base, target_value_ptr);
            union_value->value.type = target_type;

            IrInstruction *union_tag_inst = ir_build_union_tag(&ira->new_irb, switch_target_instruction->base.scope,
                    switch_target_instruction->base.source_node, union_value);
            union_tag_inst->value.type = tag_type;
            return union_tag_inst;
        }
        case ZigTypeIdEnum: {
            if ((err = type_resolve(ira->codegen, target_type, ResolveStatusZeroBitsKnown)))
                return ira->codegen->invalid_instruction;
            if (target_type->data.enumeration.src_field_count < 2) {
                TypeEnumField *only_field = &target_type->data.enumeration.fields[0];
                IrInstruction *result = ir_const(ira, &switch_target_instruction->base, target_type);
                bigint_init_bigint(&result->value.data.x_enum_tag, &only_field->value);
                return result;
            }

            if (pointee_val) {
                IrInstruction *result = ir_const(ira, &switch_target_instruction->base, target_type);
                bigint_init_bigint(&result->value.data.x_enum_tag, &pointee_val->data.x_enum_tag);
                return result;
            }

            IrInstruction *enum_value = ir_get_deref(ira, &switch_target_instruction->base, target_value_ptr);
            enum_value->value.type = target_type;
            return enum_value;
        }
        case ZigTypeIdErrorUnion:
        case ZigTypeIdUnreachable:
        case ZigTypeIdArray:
        case ZigTypeIdStruct:
        case ZigTypeIdUndefined:
        case ZigTypeIdNull:
        case ZigTypeIdOptional:
        case ZigTypeIdBoundFn:
        case ZigTypeIdArgTuple:
        case ZigTypeIdOpaque:
        case ZigTypeIdVector:
            ir_add_error(ira, &switch_target_instruction->base,
                buf_sprintf("invalid switch target type '%s'", buf_ptr(&target_type->name)));
            return ira->codegen->invalid_instruction;
    }
    zig_unreachable();
}

static IrInstruction *ir_analyze_instruction_switch_var(IrAnalyze *ira, IrInstructionSwitchVar *instruction) {
    IrInstruction *target_value_ptr = instruction->target_value_ptr->child;
    if (type_is_invalid(target_value_ptr->value.type))
        return ira->codegen->invalid_instruction;

    IrInstruction *prong_value = instruction->prong_value->child;
    if (type_is_invalid(prong_value->value.type))
        return ira->codegen->invalid_instruction;

    assert(target_value_ptr->value.type->id == ZigTypeIdPointer);
    ZigType *target_type = target_value_ptr->value.type->data.pointer.child_type;
    if (target_type->id == ZigTypeIdUnion) {
        ConstExprValue *prong_val = ir_resolve_const(ira, prong_value, UndefBad);
        if (!prong_val)
            return ira->codegen->invalid_instruction;

        assert(prong_value->value.type->id == ZigTypeIdEnum);
        TypeUnionField *field = find_union_field_by_tag(target_type, &prong_val->data.x_enum_tag);

        if (instr_is_comptime(target_value_ptr)) {
            ConstExprValue *target_val_ptr = ir_resolve_const(ira, target_value_ptr, UndefBad);
            if (!target_value_ptr)
                return ira->codegen->invalid_instruction;

            ConstExprValue *pointee_val = const_ptr_pointee(ira, ira->codegen, target_val_ptr, instruction->base.source_node);
            if (pointee_val == nullptr)
                return ira->codegen->invalid_instruction;

            IrInstruction *result = ir_const(ira, &instruction->base,
                    get_pointer_to_type(ira->codegen, field->type_entry,
                    target_val_ptr->type->data.pointer.is_const));
            ConstExprValue *out_val = &result->value;
            out_val->data.x_ptr.special = ConstPtrSpecialRef;
            out_val->data.x_ptr.mut = target_val_ptr->data.x_ptr.mut;
            out_val->data.x_ptr.data.ref.pointee = pointee_val->data.x_union.payload;
            return result;
        }

        IrInstruction *result = ir_build_union_field_ptr(&ira->new_irb,
            instruction->base.scope, instruction->base.source_node, target_value_ptr, field);
        result->value.type = get_pointer_to_type(ira->codegen, field->type_entry,
                target_value_ptr->value.type->data.pointer.is_const);
        return result;
    } else {
        ir_add_error(ira, &instruction->base,
            buf_sprintf("switch on type '%s' provides no expression parameter", buf_ptr(&target_type->name)));
        return ira->codegen->invalid_instruction;
    }
}

static IrInstruction *ir_analyze_instruction_union_tag(IrAnalyze *ira, IrInstructionUnionTag *instruction) {
    IrInstruction *value = instruction->value->child;
    return ir_analyze_union_tag(ira, &instruction->base, value);
}

static IrInstruction *ir_analyze_instruction_import(IrAnalyze *ira, IrInstructionImport *import_instruction) {
    Error err;

    IrInstruction *name_value = import_instruction->name->child;
    Buf *import_target_str = ir_resolve_str(ira, name_value);
    if (!import_target_str)
        return ira->codegen->invalid_instruction;

    AstNode *source_node = import_instruction->base.source_node;
    ImportTableEntry *import = source_node->owner;

    Buf *import_target_path;
    Buf *search_dir;
    assert(import->package);
    PackageTableEntry *target_package;
    auto package_entry = import->package->package_table.maybe_get(import_target_str);
    if (package_entry) {
        target_package = package_entry->value;
        import_target_path = &target_package->root_src_path;
        search_dir = &target_package->root_src_dir;
    } else {
        // try it as a filename
        target_package = import->package;
        import_target_path = import_target_str;

        // search relative to importing file
        search_dir = buf_alloc();
        os_path_dirname(import->path, search_dir);
    }

    Buf full_path = BUF_INIT;
    os_path_join(search_dir, import_target_path, &full_path);

    Buf *import_code = buf_alloc();
    Buf *resolved_path = buf_alloc();

    Buf *resolve_paths[] = { &full_path, };
    *resolved_path = os_path_resolve(resolve_paths, 1);

    auto import_entry = ira->codegen->import_table.maybe_get(resolved_path);
    if (import_entry) {
        IrInstruction *result = ir_const(ira, &import_instruction->base,
                ira->codegen->builtin_types.entry_namespace);
        result->value.data.x_import = import_entry->value;
        return result;
    }

    if ((err = file_fetch(ira->codegen, resolved_path, import_code))) {
        if (err == ErrorFileNotFound) {
            ir_add_error_node(ira, source_node,
                    buf_sprintf("unable to find '%s'", buf_ptr(import_target_path)));
            return ira->codegen->invalid_instruction;
        } else {
            ir_add_error_node(ira, source_node,
                    buf_sprintf("unable to open '%s': %s", buf_ptr(&full_path), err_str(err)));
            return ira->codegen->invalid_instruction;
        }
    }

    ImportTableEntry *target_import = add_source_file(ira->codegen, target_package, resolved_path, import_code);

    scan_import(ira->codegen, target_import);

    IrInstruction *result = ir_const(ira, &import_instruction->base, ira->codegen->builtin_types.entry_namespace);
    result->value.data.x_import = target_import;
    return result;
}

static IrInstruction *ir_analyze_instruction_ref(IrAnalyze *ira, IrInstructionRef *ref_instruction) {
    IrInstruction *value = ref_instruction->value->child;
    if (type_is_invalid(value->value.type))
        return ira->codegen->invalid_instruction;
    return ir_get_ref(ira, &ref_instruction->base, value, ref_instruction->is_const, ref_instruction->is_volatile);
}

static IrInstruction *ir_analyze_container_init_fields_union(IrAnalyze *ira, IrInstruction *instruction,
    ZigType *container_type, size_t instr_field_count, IrInstructionContainerInitFieldsField *fields)
{
    Error err;
    assert(container_type->id == ZigTypeIdUnion);

    if ((err = type_resolve(ira->codegen, container_type, ResolveStatusSizeKnown)))
        return ira->codegen->invalid_instruction;

    if (instr_field_count != 1) {
        ir_add_error(ira, instruction,
            buf_sprintf("union initialization expects exactly one field"));
        return ira->codegen->invalid_instruction;
    }

    IrInstructionContainerInitFieldsField *field = &fields[0];
    IrInstruction *field_value = field->value->child;
    if (type_is_invalid(field_value->value.type))
        return ira->codegen->invalid_instruction;

    TypeUnionField *type_field = find_union_type_field(container_type, field->name);
    if (!type_field) {
        ir_add_error_node(ira, field->source_node,
            buf_sprintf("no member named '%s' in union '%s'",
                buf_ptr(field->name), buf_ptr(&container_type->name)));
        return ira->codegen->invalid_instruction;
    }

    if (type_is_invalid(type_field->type_entry))
        return ira->codegen->invalid_instruction;

    IrInstruction *casted_field_value = ir_implicit_cast(ira, field_value, type_field->type_entry);
    if (casted_field_value == ira->codegen->invalid_instruction)
        return ira->codegen->invalid_instruction;

    if ((err = type_resolve(ira->codegen, casted_field_value->value.type, ResolveStatusZeroBitsKnown)))
        return ira->codegen->invalid_instruction;

    bool is_comptime = ir_should_inline(ira->new_irb.exec, instruction->scope)
        || type_requires_comptime(ira->codegen, container_type) == ReqCompTimeYes;
    if (is_comptime || casted_field_value->value.special != ConstValSpecialRuntime ||
        !type_has_bits(casted_field_value->value.type))
    {
        ConstExprValue *field_val = ir_resolve_const(ira, casted_field_value, UndefOk);
        if (!field_val)
            return ira->codegen->invalid_instruction;

        IrInstruction *result = ir_const(ira, instruction, container_type);
        ConstExprValue *out_val = &result->value;
        out_val->data.x_union.payload = field_val;
        out_val->data.x_union.tag = type_field->enum_field->value;
        out_val->parent.id = ConstParentIdUnion;
        out_val->parent.data.p_union.union_val = out_val;

        return result;
    }

    IrInstruction *new_instruction = ir_build_union_init(&ira->new_irb,
        instruction->scope, instruction->source_node,
        container_type, type_field, casted_field_value);
    new_instruction->value.type = container_type;
    ir_add_alloca(ira, new_instruction, container_type);
    return new_instruction;
}

static IrInstruction *ir_analyze_container_init_fields(IrAnalyze *ira, IrInstruction *instruction,
    ZigType *container_type, size_t instr_field_count, IrInstructionContainerInitFieldsField *fields)
{
    Error err;
    if (container_type->id == ZigTypeIdUnion) {
        return ir_analyze_container_init_fields_union(ira, instruction, container_type, instr_field_count, fields);
    }
    if (container_type->id != ZigTypeIdStruct || is_slice(container_type)) {
        ir_add_error(ira, instruction,
            buf_sprintf("type '%s' does not support struct initialization syntax",
                buf_ptr(&container_type->name)));
        return ira->codegen->invalid_instruction;
    }

    if ((err = type_resolve(ira->codegen, container_type, ResolveStatusSizeKnown)))
        return ira->codegen->invalid_instruction;

    size_t actual_field_count = container_type->data.structure.src_field_count;

    IrInstruction *first_non_const_instruction = nullptr;

    AstNode **field_assign_nodes = allocate<AstNode *>(actual_field_count);

    IrInstructionStructInitField *new_fields = allocate<IrInstructionStructInitField>(actual_field_count);

    bool is_comptime = ir_should_inline(ira->new_irb.exec, instruction->scope)
        || type_requires_comptime(ira->codegen, container_type) == ReqCompTimeYes;

    ConstExprValue const_val = {};
    const_val.special = ConstValSpecialStatic;
    const_val.type = container_type;
    const_val.data.x_struct.fields = create_const_vals(actual_field_count);
    for (size_t i = 0; i < instr_field_count; i += 1) {
        IrInstructionContainerInitFieldsField *field = &fields[i];

        IrInstruction *field_value = field->value->child;
        if (type_is_invalid(field_value->value.type))
            return ira->codegen->invalid_instruction;

        TypeStructField *type_field = find_struct_type_field(container_type, field->name);
        if (!type_field) {
            ir_add_error_node(ira, field->source_node,
                buf_sprintf("no member named '%s' in struct '%s'",
                    buf_ptr(field->name), buf_ptr(&container_type->name)));
            return ira->codegen->invalid_instruction;
        }

        if (type_is_invalid(type_field->type_entry))
            return ira->codegen->invalid_instruction;

        IrInstruction *casted_field_value = ir_implicit_cast(ira, field_value, type_field->type_entry);
        if (casted_field_value == ira->codegen->invalid_instruction)
            return ira->codegen->invalid_instruction;

        size_t field_index = type_field->src_index;
        AstNode *existing_assign_node = field_assign_nodes[field_index];
        if (existing_assign_node) {
            ErrorMsg *msg = ir_add_error_node(ira, field->source_node, buf_sprintf("duplicate field"));
            add_error_note(ira->codegen, msg, existing_assign_node, buf_sprintf("other field here"));
            return ira->codegen->invalid_instruction;
        }
        field_assign_nodes[field_index] = field->source_node;

        new_fields[field_index].value = casted_field_value;
        new_fields[field_index].type_struct_field = type_field;

        if (const_val.special == ConstValSpecialStatic) {
            if (is_comptime || casted_field_value->value.special != ConstValSpecialRuntime) {
                ConstExprValue *field_val = ir_resolve_const(ira, casted_field_value, UndefOk);
                if (!field_val)
                    return ira->codegen->invalid_instruction;

                copy_const_val(&const_val.data.x_struct.fields[field_index], field_val, true);
            } else {
                first_non_const_instruction = casted_field_value;
                const_val.special = ConstValSpecialRuntime;
            }
        }
    }

    bool any_missing = false;
    for (size_t i = 0; i < actual_field_count; i += 1) {
        if (!field_assign_nodes[i]) {
            ir_add_error_node(ira, instruction->source_node,
                buf_sprintf("missing field: '%s'", buf_ptr(container_type->data.structure.fields[i].name)));
            any_missing = true;
        }
    }
    if (any_missing)
        return ira->codegen->invalid_instruction;

    if (const_val.special == ConstValSpecialStatic) {
        IrInstruction *result = ir_const(ira, instruction, nullptr);
        ConstExprValue *out_val = &result->value;
        copy_const_val(out_val, &const_val, true);
        out_val->type = container_type;

        for (size_t i = 0; i < instr_field_count; i += 1) {
            ConstExprValue *field_val = &out_val->data.x_struct.fields[i];
            ConstParent *parent = get_const_val_parent(ira->codegen, field_val);
            if (parent != nullptr) {
                parent->id = ConstParentIdStruct;
                parent->data.p_struct.field_index = i;
                parent->data.p_struct.struct_val = out_val;
            }
        }

        return result;
    }

    if (is_comptime) {
        ir_add_error_node(ira, first_non_const_instruction->source_node,
            buf_sprintf("unable to evaluate constant expression"));
        return ira->codegen->invalid_instruction;
    }

    IrInstruction *new_instruction = ir_build_struct_init(&ira->new_irb,
        instruction->scope, instruction->source_node,
        container_type, actual_field_count, new_fields);
    new_instruction->value.type = container_type;
    ir_add_alloca(ira, new_instruction, container_type);
    return new_instruction;
}

static IrInstruction *ir_analyze_instruction_container_init_list(IrAnalyze *ira,
        IrInstructionContainerInitList *instruction)
{
    ZigType *container_type = ir_resolve_type(ira, instruction->container_type->child);
    if (type_is_invalid(container_type))
        return ira->codegen->invalid_instruction;

    size_t elem_count = instruction->item_count;

    if (container_type->id == ZigTypeIdStruct && !is_slice(container_type) && elem_count == 0) {
        return ir_analyze_container_init_fields(ira, &instruction->base, container_type,
                0, nullptr);
    } else if (is_slice(container_type) || container_type->id == ZigTypeIdArray) {
        // array is same as slice init but we make a compile error if the length is wrong
        ZigType *child_type;
        if (container_type->id == ZigTypeIdArray) {
            child_type = container_type->data.array.child_type;
            if (container_type->data.array.len != elem_count) {
                ZigType *literal_type = get_array_type(ira->codegen, child_type, elem_count);

                ir_add_error(ira, &instruction->base,
                    buf_sprintf("expected %s literal, found %s literal",
                        buf_ptr(&container_type->name), buf_ptr(&literal_type->name)));
                return ira->codegen->invalid_instruction;
            }
        } else {
            ZigType *pointer_type = container_type->data.structure.fields[slice_ptr_index].type_entry;
            assert(pointer_type->id == ZigTypeIdPointer);
            child_type = pointer_type->data.pointer.child_type;
        }

        ZigType *fixed_size_array_type = get_array_type(ira->codegen, child_type, elem_count);

        ConstExprValue const_val = {};
        const_val.special = ConstValSpecialStatic;
        const_val.type = fixed_size_array_type;
        const_val.data.x_array.data.s_none.elements = create_const_vals(elem_count);

        bool is_comptime = ir_should_inline(ira->new_irb.exec, instruction->base.scope);

        IrInstruction **new_items = allocate<IrInstruction *>(elem_count);

        IrInstruction *first_non_const_instruction = nullptr;

        for (size_t i = 0; i < elem_count; i += 1) {
            IrInstruction *arg_value = instruction->items[i]->child;
            if (type_is_invalid(arg_value->value.type))
                return ira->codegen->invalid_instruction;

            IrInstruction *casted_arg = ir_implicit_cast(ira, arg_value, child_type);
            if (casted_arg == ira->codegen->invalid_instruction)
                return ira->codegen->invalid_instruction;

            new_items[i] = casted_arg;

            if (const_val.special == ConstValSpecialStatic) {
                if (is_comptime || casted_arg->value.special != ConstValSpecialRuntime) {
                    ConstExprValue *elem_val = ir_resolve_const(ira, casted_arg, UndefBad);
                    if (!elem_val)
                        return ira->codegen->invalid_instruction;

                    copy_const_val(&const_val.data.x_array.data.s_none.elements[i], elem_val, true);
                } else {
                    first_non_const_instruction = casted_arg;
                    const_val.special = ConstValSpecialRuntime;
                }
            }
        }

        if (const_val.special == ConstValSpecialStatic) {
            IrInstruction *result = ir_const(ira, &instruction->base, nullptr);
            ConstExprValue *out_val = &result->value;
            copy_const_val(out_val, &const_val, true);
            result->value.type = fixed_size_array_type;
            for (size_t i = 0; i < elem_count; i += 1) {
                ConstExprValue *elem_val = &out_val->data.x_array.data.s_none.elements[i];
                ConstParent *parent = get_const_val_parent(ira->codegen, elem_val);
                if (parent != nullptr) {
                    parent->id = ConstParentIdArray;
                    parent->data.p_array.array_val = out_val;
                    parent->data.p_array.elem_index = i;
                }
            }
            return result;
        }

        if (is_comptime) {
            ir_add_error_node(ira, first_non_const_instruction->source_node,
                buf_sprintf("unable to evaluate constant expression"));
            return ira->codegen->invalid_instruction;
        }

        IrInstruction *new_instruction = ir_build_container_init_list(&ira->new_irb,
            instruction->base.scope, instruction->base.source_node,
            nullptr, elem_count, new_items);
        new_instruction->value.type = fixed_size_array_type;
        ir_add_alloca(ira, new_instruction, fixed_size_array_type);
        return new_instruction;
    } else if (container_type->id == ZigTypeIdVoid) {
        if (elem_count != 0) {
            ir_add_error_node(ira, instruction->base.source_node,
                buf_sprintf("void expression expects no arguments"));
            return ira->codegen->invalid_instruction;
        }
        return ir_const_void(ira, &instruction->base);
    } else {
        ir_add_error_node(ira, instruction->base.source_node,
            buf_sprintf("type '%s' does not support array initialization",
                buf_ptr(&container_type->name)));
        return ira->codegen->invalid_instruction;
    }
}

static IrInstruction *ir_analyze_instruction_container_init_fields(IrAnalyze *ira,
        IrInstructionContainerInitFields *instruction)
{
    IrInstruction *container_type_value = instruction->container_type->child;
    ZigType *container_type = ir_resolve_type(ira, container_type_value);
    if (type_is_invalid(container_type))
        return ira->codegen->invalid_instruction;

    return ir_analyze_container_init_fields(ira, &instruction->base, container_type,
        instruction->field_count, instruction->fields);
}

static IrInstruction *ir_analyze_instruction_compile_err(IrAnalyze *ira,
        IrInstructionCompileErr *instruction)
{
    IrInstruction *msg_value = instruction->msg->child;
    Buf *msg_buf = ir_resolve_str(ira, msg_value);
    if (!msg_buf)
        return ira->codegen->invalid_instruction;

    ErrorMsg *msg = ir_add_error(ira, &instruction->base, msg_buf);
    size_t i = ira->codegen->tld_ref_source_node_stack.length;
    for (;;) {
        if (i == 0)
            break;
        i -= 1;
        AstNode *source_node = ira->codegen->tld_ref_source_node_stack.at(i);
        if (source_node) {
            add_error_note(ira->codegen, msg, source_node,
                buf_sprintf("referenced here"));
        }
    }

    return ira->codegen->invalid_instruction;
}

static IrInstruction *ir_analyze_instruction_compile_log(IrAnalyze *ira, IrInstructionCompileLog *instruction) {
    Buf buf = BUF_INIT;
    fprintf(stderr, "| ");
    for (size_t i = 0; i < instruction->msg_count; i += 1) {
        IrInstruction *msg = instruction->msg_list[i]->child;
        if (type_is_invalid(msg->value.type))
            return ira->codegen->invalid_instruction;
        buf_resize(&buf, 0);
        render_const_value(ira->codegen, &buf, &msg->value);
        const char *comma_str = (i != 0) ? ", " : "";
        fprintf(stderr, "%s%s", comma_str, buf_ptr(&buf));
    }
    fprintf(stderr, "\n");

    auto *expr = &instruction->base.source_node->data.fn_call_expr;
    if (!expr->seen) {
        // Here we bypass higher level functions such as ir_add_error because we do not want
        // invalidate_exec to be called.
        add_node_error(ira->codegen, instruction->base.source_node, buf_sprintf("found compile log statement"));
    }
    expr->seen = true;

    return ir_const_void(ira, &instruction->base);
}

static IrInstruction *ir_analyze_instruction_err_name(IrAnalyze *ira, IrInstructionErrName *instruction) {
    IrInstruction *value = instruction->value->child;
    if (type_is_invalid(value->value.type))
        return ira->codegen->invalid_instruction;

    IrInstruction *casted_value = ir_implicit_cast(ira, value, ira->codegen->builtin_types.entry_global_error_set);
    if (type_is_invalid(casted_value->value.type))
        return ira->codegen->invalid_instruction;

    ZigType *u8_ptr_type = get_pointer_to_type_extra(ira->codegen, ira->codegen->builtin_types.entry_u8,
            true, false, PtrLenUnknown, 0, 0, 0);
    ZigType *str_type = get_slice_type(ira->codegen, u8_ptr_type);
    if (casted_value->value.special == ConstValSpecialStatic) {
        ErrorTableEntry *err = casted_value->value.data.x_err_set;
        if (!err->cached_error_name_val) {
            ConstExprValue *array_val = create_const_str_lit(ira->codegen, &err->name);
            err->cached_error_name_val = create_const_slice(ira->codegen, array_val, 0, buf_len(&err->name), true);
        }
        IrInstruction *result = ir_const(ira, &instruction->base, nullptr);
        copy_const_val(&result->value, err->cached_error_name_val, true);
        result->value.type = str_type;
        return result;
    }

    ira->codegen->generate_error_name_table = true;

    IrInstruction *result = ir_build_err_name(&ira->new_irb,
        instruction->base.scope, instruction->base.source_node, value);
    result->value.type = str_type;
    return result;
}

static IrInstruction *ir_analyze_instruction_enum_tag_name(IrAnalyze *ira, IrInstructionTagName *instruction) {
    Error err;
    IrInstruction *target = instruction->target->child;
    if (type_is_invalid(target->value.type))
        return ira->codegen->invalid_instruction;

    assert(target->value.type->id == ZigTypeIdEnum);

    if (instr_is_comptime(target)) {
        if ((err = type_resolve(ira->codegen, target->value.type, ResolveStatusZeroBitsKnown)))
            return ira->codegen->invalid_instruction;
        TypeEnumField *field = find_enum_field_by_tag(target->value.type, &target->value.data.x_bigint);
        ConstExprValue *array_val = create_const_str_lit(ira->codegen, field->name);
        IrInstruction *result = ir_const(ira, &instruction->base, nullptr);
        init_const_slice(ira->codegen, &result->value, array_val, 0, buf_len(field->name), true);
        return result;
    }

    IrInstruction *result = ir_build_tag_name(&ira->new_irb, instruction->base.scope,
            instruction->base.source_node, target);
    ZigType *u8_ptr_type = get_pointer_to_type_extra(
            ira->codegen, ira->codegen->builtin_types.entry_u8,
            true, false, PtrLenUnknown,
            0, 0, 0);
    result->value.type = get_slice_type(ira->codegen, u8_ptr_type);
    return result;
}

static IrInstruction *ir_analyze_instruction_field_parent_ptr(IrAnalyze *ira,
        IrInstructionFieldParentPtr *instruction)
{
    Error err;
    IrInstruction *type_value = instruction->type_value->child;
    ZigType *container_type = ir_resolve_type(ira, type_value);
    if (type_is_invalid(container_type))
        return ira->codegen->invalid_instruction;

    IrInstruction *field_name_value = instruction->field_name->child;
    Buf *field_name = ir_resolve_str(ira, field_name_value);
    if (!field_name)
        return ira->codegen->invalid_instruction;

    IrInstruction *field_ptr = instruction->field_ptr->child;
    if (type_is_invalid(field_ptr->value.type))
        return ira->codegen->invalid_instruction;

    if (container_type->id != ZigTypeIdStruct) {
        ir_add_error(ira, type_value,
                buf_sprintf("expected struct type, found '%s'", buf_ptr(&container_type->name)));
        return ira->codegen->invalid_instruction;
    }

    if ((err = ensure_complete_type(ira->codegen, container_type)))
        return ira->codegen->invalid_instruction;

    TypeStructField *field = find_struct_type_field(container_type, field_name);
    if (field == nullptr) {
        ir_add_error(ira, field_name_value,
                buf_sprintf("struct '%s' has no field '%s'",
                    buf_ptr(&container_type->name), buf_ptr(field_name)));
        return ira->codegen->invalid_instruction;
    }

    if (field_ptr->value.type->id != ZigTypeIdPointer) {
        ir_add_error(ira, field_ptr,
                buf_sprintf("expected pointer, found '%s'", buf_ptr(&field_ptr->value.type->name)));
        return ira->codegen->invalid_instruction;
    }

    bool is_packed = (container_type->data.structure.layout == ContainerLayoutPacked);
    uint32_t field_ptr_align = is_packed ? 1 : get_abi_alignment(ira->codegen, field->type_entry);
    uint32_t parent_ptr_align = is_packed ? 1 : get_abi_alignment(ira->codegen, container_type);

    ZigType *field_ptr_type = get_pointer_to_type_extra(ira->codegen, field->type_entry,
            field_ptr->value.type->data.pointer.is_const,
            field_ptr->value.type->data.pointer.is_volatile,
            PtrLenSingle,
            field_ptr_align, 0, 0);
    IrInstruction *casted_field_ptr = ir_implicit_cast(ira, field_ptr, field_ptr_type);
    if (type_is_invalid(casted_field_ptr->value.type))
        return ira->codegen->invalid_instruction;

    ZigType *result_type = get_pointer_to_type_extra(ira->codegen, container_type,
            casted_field_ptr->value.type->data.pointer.is_const,
            casted_field_ptr->value.type->data.pointer.is_volatile,
            PtrLenSingle,
            parent_ptr_align, 0, 0);

    if (instr_is_comptime(casted_field_ptr)) {
        ConstExprValue *field_ptr_val = ir_resolve_const(ira, casted_field_ptr, UndefBad);
        if (!field_ptr_val)
            return ira->codegen->invalid_instruction;

        if (field_ptr_val->data.x_ptr.special != ConstPtrSpecialBaseStruct) {
            ir_add_error(ira, field_ptr, buf_sprintf("pointer value not based on parent struct"));
            return ira->codegen->invalid_instruction;
        }

        size_t ptr_field_index = field_ptr_val->data.x_ptr.data.base_struct.field_index;
        if (ptr_field_index != field->src_index) {
            ir_add_error(ira, &instruction->base,
                    buf_sprintf("field '%s' has index %" ZIG_PRI_usize " but pointer value is index %" ZIG_PRI_usize " of struct '%s'",
                        buf_ptr(field->name), field->src_index,
                        ptr_field_index, buf_ptr(&container_type->name)));
            return ira->codegen->invalid_instruction;
        }

        IrInstruction *result = ir_const(ira, &instruction->base, result_type);
        ConstExprValue *out_val = &result->value;
        out_val->data.x_ptr.special = ConstPtrSpecialRef;
        out_val->data.x_ptr.data.ref.pointee = field_ptr_val->data.x_ptr.data.base_struct.struct_val;
        out_val->data.x_ptr.mut = field_ptr_val->data.x_ptr.mut;
        return result;
    }

    IrInstruction *result = ir_build_field_parent_ptr(&ira->new_irb, instruction->base.scope,
            instruction->base.source_node, type_value, field_name_value, casted_field_ptr, field);
    result->value.type = result_type;
    return result;
}

static TypeStructField *validate_byte_offset(IrAnalyze *ira,
        IrInstruction *type_value,
        IrInstruction *field_name_value,
        size_t *byte_offset)
{
    ZigType *container_type = ir_resolve_type(ira, type_value);
    if (type_is_invalid(container_type))
        return nullptr;

    Error err;
    if ((err = ensure_complete_type(ira->codegen, container_type)))
        return nullptr;

    Buf *field_name = ir_resolve_str(ira, field_name_value);
    if (!field_name)
        return nullptr;

    if (container_type->id != ZigTypeIdStruct) {
        ir_add_error(ira, type_value,
                buf_sprintf("expected struct type, found '%s'", buf_ptr(&container_type->name)));
        return nullptr;
    }

    TypeStructField *field = find_struct_type_field(container_type, field_name);
    if (field == nullptr) {
        ir_add_error(ira, field_name_value,
                buf_sprintf("struct '%s' has no field '%s'",
                        buf_ptr(&container_type->name), buf_ptr(field_name)));
        return nullptr;
    }

    if (!type_has_bits(field->type_entry)) {
        ir_add_error(ira, field_name_value,
                buf_sprintf("zero-bit field '%s' in struct '%s' has no offset",
                        buf_ptr(field_name), buf_ptr(&container_type->name)));
        return nullptr;
    }

    *byte_offset = LLVMOffsetOfElement(ira->codegen->target_data_ref, container_type->type_ref, field->gen_index);
    return field;
}

static IrInstruction *ir_analyze_instruction_byte_offset_of(IrAnalyze *ira,
        IrInstructionByteOffsetOf *instruction)
{
    IrInstruction *type_value = instruction->type_value->child;
    if (type_is_invalid(type_value->value.type))
        return ira->codegen->invalid_instruction;

    IrInstruction *field_name_value = instruction->field_name->child;
    size_t byte_offset = 0;
    if (!validate_byte_offset(ira, type_value, field_name_value, &byte_offset))
        return ira->codegen->invalid_instruction;


    return ir_const_unsigned(ira, &instruction->base, byte_offset);
}

static IrInstruction *ir_analyze_instruction_bit_offset_of(IrAnalyze *ira,
        IrInstructionBitOffsetOf *instruction)
{
    IrInstruction *type_value = instruction->type_value->child;
    if (type_is_invalid(type_value->value.type))
        return ira->codegen->invalid_instruction;
    IrInstruction *field_name_value = instruction->field_name->child;
    size_t byte_offset = 0;
    TypeStructField *field = nullptr;
    if (!(field = validate_byte_offset(ira, type_value, field_name_value, &byte_offset)))
        return ira->codegen->invalid_instruction;

    size_t bit_offset = byte_offset * 8 + field->bit_offset_in_host;
    return ir_const_unsigned(ira, &instruction->base, bit_offset);
}

static void ensure_field_index(ZigType *type, const char *field_name, size_t index) {
    Buf *field_name_buf;

    assert(type != nullptr && !type_is_invalid(type));
    // Check for our field by creating a buffer in place then using the comma operator to free it so that we don't
    // leak memory in debug mode.
    assert(find_struct_type_field(type, field_name_buf = buf_create_from_str(field_name))->src_index == index &&
            (buf_deinit(field_name_buf), true));
}

static ZigType *ir_type_info_get_type(IrAnalyze *ira, const char *type_name, ZigType *root) {
    Error err;
    ConstExprValue *type_info_var = get_builtin_value(ira->codegen, "TypeInfo");
    assert(type_info_var->type->id == ZigTypeIdMetaType);
    assertNoError(ensure_complete_type(ira->codegen, type_info_var->data.x_type));

    ZigType *type_info_type = type_info_var->data.x_type;
    assert(type_info_type->id == ZigTypeIdUnion);

    if (type_name == nullptr && root == nullptr)
        return type_info_type;
    else if (type_name == nullptr)
        return root;

    ZigType *root_type = (root == nullptr) ? type_info_type : root;

    ScopeDecls *type_info_scope = get_container_scope(root_type);
    assert(type_info_scope != nullptr);

    Buf field_name = BUF_INIT;
    buf_init_from_str(&field_name, type_name);
    auto entry = type_info_scope->decl_table.get(&field_name);
    buf_deinit(&field_name);

    TldVar *tld = (TldVar *)entry;
    assert(tld->base.id == TldIdVar);

    ZigVar *var = tld->var;

    if ((err = ensure_complete_type(ira->codegen, var->const_value->type)))
        return ira->codegen->builtin_types.entry_invalid;

    assert(var->const_value->type->id == ZigTypeIdMetaType);
    return var->const_value->data.x_type;
}

static Error ir_make_type_info_defs(IrAnalyze *ira, ConstExprValue *out_val, ScopeDecls *decls_scope) {
    Error err;
    ZigType *type_info_definition_type = ir_type_info_get_type(ira, "Definition", nullptr);
    if ((err = ensure_complete_type(ira->codegen, type_info_definition_type)))
        return err;

    ensure_field_index(type_info_definition_type, "name", 0);
    ensure_field_index(type_info_definition_type, "is_pub", 1);
    ensure_field_index(type_info_definition_type, "data", 2);

    ZigType *type_info_definition_data_type = ir_type_info_get_type(ira, "Data", type_info_definition_type);
    if ((err = ensure_complete_type(ira->codegen, type_info_definition_data_type)))
        return err;

    ZigType *type_info_fn_def_type = ir_type_info_get_type(ira, "FnDef", type_info_definition_data_type);
    if ((err = ensure_complete_type(ira->codegen, type_info_fn_def_type)))
        return err;

    ZigType *type_info_fn_def_inline_type = ir_type_info_get_type(ira, "Inline", type_info_fn_def_type);
    if ((err = ensure_complete_type(ira->codegen, type_info_fn_def_inline_type)))
        return err;

    // Loop through our definitions once to figure out how many definitions we will generate info for.
    auto decl_it = decls_scope->decl_table.entry_iterator();
    decltype(decls_scope->decl_table)::Entry *curr_entry = nullptr;
    int definition_count = 0;

    while ((curr_entry = decl_it.next()) != nullptr) {
        // If the definition is unresolved, force it to be resolved again.
        if (curr_entry->value->resolution == TldResolutionUnresolved) {
            resolve_top_level_decl(ira->codegen, curr_entry->value, false, curr_entry->value->source_node);
            if (curr_entry->value->resolution != TldResolutionOk) {
                return ErrorSemanticAnalyzeFail;
            }
        }

        // Skip comptime blocks and test functions.
        if (curr_entry->value->id != TldIdCompTime) {
            if (curr_entry->value->id == TldIdFn) {
                ZigFn *fn_entry = ((TldFn *)curr_entry->value)->fn_entry;
                if (fn_entry->is_test)
                    continue;
            }

            definition_count += 1;
        }
    }

    ConstExprValue *definition_array = create_const_vals(1);
    definition_array->special = ConstValSpecialStatic;
    definition_array->type = get_array_type(ira->codegen, type_info_definition_type, definition_count);
    definition_array->data.x_array.special = ConstArraySpecialNone;
    definition_array->data.x_array.data.s_none.elements = create_const_vals(definition_count);
    init_const_slice(ira->codegen, out_val, definition_array, 0, definition_count, false);

    // Loop through the definitions and generate info.
    decl_it = decls_scope->decl_table.entry_iterator();
    curr_entry = nullptr;
    int definition_index = 0;
    while ((curr_entry = decl_it.next()) != nullptr) {
        // Skip comptime blocks and test functions.
        if (curr_entry->value->id == TldIdCompTime) {
            continue;
        } else if (curr_entry->value->id == TldIdFn) {
            ZigFn *fn_entry = ((TldFn *)curr_entry->value)->fn_entry;
            if (fn_entry->is_test)
                continue;
        }

        ConstExprValue *definition_val = &definition_array->data.x_array.data.s_none.elements[definition_index];

        definition_val->special = ConstValSpecialStatic;
        definition_val->type = type_info_definition_type;

        ConstExprValue *inner_fields = create_const_vals(3);
        ConstExprValue *name = create_const_str_lit(ira->codegen, curr_entry->key);
        init_const_slice(ira->codegen, &inner_fields[0], name, 0, buf_len(curr_entry->key), true);
        inner_fields[1].special = ConstValSpecialStatic;
        inner_fields[1].type = ira->codegen->builtin_types.entry_bool;
        inner_fields[1].data.x_bool = curr_entry->value->visib_mod == VisibModPub;
        inner_fields[2].special = ConstValSpecialStatic;
        inner_fields[2].type = type_info_definition_data_type;
        inner_fields[2].parent.id = ConstParentIdStruct;
        inner_fields[2].parent.data.p_struct.struct_val = definition_val;
        inner_fields[2].parent.data.p_struct.field_index = 1;

        switch (curr_entry->value->id) {
            case TldIdVar:
                {
                    ZigVar *var = ((TldVar *)curr_entry->value)->var;
                    if ((err = ensure_complete_type(ira->codegen, var->const_value->type)))
                        return ErrorSemanticAnalyzeFail;

                    if (var->const_value->type->id == ZigTypeIdMetaType) {
                        // We have a variable of type 'type', so it's actually a type definition.
                        // 0: Data.Type: type
                        bigint_init_unsigned(&inner_fields[2].data.x_union.tag, 0);
                        inner_fields[2].data.x_union.payload = var->const_value;
                    } else {
                        // We have a variable of another type, so we store the type of the variable.
                        // 1: Data.Var: type
                        bigint_init_unsigned(&inner_fields[2].data.x_union.tag, 1);

                        ConstExprValue *payload = create_const_vals(1);
                        payload->type = ira->codegen->builtin_types.entry_type;
                        payload->data.x_type = var->const_value->type;

                        inner_fields[2].data.x_union.payload = payload;
                    }

                    break;
                }
            case TldIdFn:
                {
                    // 2: Data.Fn: Data.FnDef
                    bigint_init_unsigned(&inner_fields[2].data.x_union.tag, 2);

                    ZigFn *fn_entry = ((TldFn *)curr_entry->value)->fn_entry;
                    assert(!fn_entry->is_test);

                    AstNodeFnProto *fn_node = (AstNodeFnProto *)(fn_entry->proto_node);

                    ConstExprValue *fn_def_val = create_const_vals(1);
                    fn_def_val->special = ConstValSpecialStatic;
                    fn_def_val->type = type_info_fn_def_type;
                    fn_def_val->parent.id = ConstParentIdUnion;
                    fn_def_val->parent.data.p_union.union_val = &inner_fields[2];

                    ConstExprValue *fn_def_fields = create_const_vals(9);
                    fn_def_val->data.x_struct.fields = fn_def_fields;

                    // fn_type: type
                    ensure_field_index(fn_def_val->type, "fn_type", 0);
                    fn_def_fields[0].special = ConstValSpecialStatic;
                    fn_def_fields[0].type = ira->codegen->builtin_types.entry_type;
                    fn_def_fields[0].data.x_type = fn_entry->type_entry;
                    // inline_type: Data.FnDef.Inline
                    ensure_field_index(fn_def_val->type, "inline_type", 1);
                    fn_def_fields[1].special = ConstValSpecialStatic;
                    fn_def_fields[1].type = type_info_fn_def_inline_type;
                    bigint_init_unsigned(&fn_def_fields[1].data.x_enum_tag, fn_entry->fn_inline);
                    // calling_convention: TypeInfo.CallingConvention
                    ensure_field_index(fn_def_val->type, "calling_convention", 2);
                    fn_def_fields[2].special = ConstValSpecialStatic;
                    fn_def_fields[2].type = ir_type_info_get_type(ira, "CallingConvention", nullptr);
                    bigint_init_unsigned(&fn_def_fields[2].data.x_enum_tag, fn_node->cc);
                    // is_var_args: bool
                    ensure_field_index(fn_def_val->type, "is_var_args", 3);
                    bool is_varargs = fn_node->is_var_args;
                    fn_def_fields[3].special = ConstValSpecialStatic;
                    fn_def_fields[3].type = ira->codegen->builtin_types.entry_bool;
                    fn_def_fields[3].data.x_bool = is_varargs;
                    // is_extern: bool
                    ensure_field_index(fn_def_val->type, "is_extern", 4);
                    fn_def_fields[4].special = ConstValSpecialStatic;
                    fn_def_fields[4].type = ira->codegen->builtin_types.entry_bool;
                    fn_def_fields[4].data.x_bool = fn_node->is_extern;
                    // is_export: bool
                    ensure_field_index(fn_def_val->type, "is_export", 5);
                    fn_def_fields[5].special = ConstValSpecialStatic;
                    fn_def_fields[5].type = ira->codegen->builtin_types.entry_bool;
                    fn_def_fields[5].data.x_bool = fn_node->is_export;
                    // lib_name: ?[]const u8
                    ensure_field_index(fn_def_val->type, "lib_name", 6);
                    fn_def_fields[6].special = ConstValSpecialStatic;
                    ZigType *u8_ptr = get_pointer_to_type_extra(
                        ira->codegen, ira->codegen->builtin_types.entry_u8,
                        true, false, PtrLenUnknown,
                        0, 0, 0);
                    fn_def_fields[6].type = get_optional_type(ira->codegen, get_slice_type(ira->codegen, u8_ptr));
                    if (fn_node->is_extern && buf_len(fn_node->lib_name) > 0) {
                        fn_def_fields[6].data.x_optional = create_const_vals(1);
                        ConstExprValue *lib_name = create_const_str_lit(ira->codegen, fn_node->lib_name);
                        init_const_slice(ira->codegen, fn_def_fields[6].data.x_optional, lib_name, 0, buf_len(fn_node->lib_name), true);
                    } else {
                        fn_def_fields[6].data.x_optional = nullptr;
                    }
                    // return_type: type
                    ensure_field_index(fn_def_val->type, "return_type", 7);
                    fn_def_fields[7].special = ConstValSpecialStatic;
                    fn_def_fields[7].type = ira->codegen->builtin_types.entry_type;
                    fn_def_fields[7].data.x_type = fn_entry->type_entry->data.fn.fn_type_id.return_type;
                    // arg_names: [][] const u8
                    ensure_field_index(fn_def_val->type, "arg_names", 8);
                    size_t fn_arg_count = fn_entry->variable_list.length;
                    ConstExprValue *fn_arg_name_array = create_const_vals(1);
                    fn_arg_name_array->special = ConstValSpecialStatic;
                    fn_arg_name_array->type = get_array_type(ira->codegen,
                            get_slice_type(ira->codegen, u8_ptr), fn_arg_count);
                    fn_arg_name_array->data.x_array.special = ConstArraySpecialNone;
                    fn_arg_name_array->data.x_array.data.s_none.elements = create_const_vals(fn_arg_count);

                    init_const_slice(ira->codegen, &fn_def_fields[8], fn_arg_name_array, 0, fn_arg_count, false);

                    for (size_t fn_arg_index = 0; fn_arg_index < fn_arg_count; fn_arg_index++) {
                        ZigVar *arg_var = fn_entry->variable_list.at(fn_arg_index);
                        ConstExprValue *fn_arg_name_val = &fn_arg_name_array->data.x_array.data.s_none.elements[fn_arg_index];
                        ConstExprValue *arg_name = create_const_str_lit(ira->codegen, &arg_var->name);
                        init_const_slice(ira->codegen, fn_arg_name_val, arg_name, 0, buf_len(&arg_var->name), true);
                        fn_arg_name_val->parent.id = ConstParentIdArray;
                        fn_arg_name_val->parent.data.p_array.array_val = fn_arg_name_array;
                        fn_arg_name_val->parent.data.p_array.elem_index = fn_arg_index;
                    }

                    inner_fields[2].data.x_union.payload = fn_def_val;
                    break;
                }
            case TldIdContainer:
                {
                    ZigType *type_entry = ((TldContainer *)curr_entry->value)->type_entry;
                    if ((err = ensure_complete_type(ira->codegen, type_entry)))
                        return ErrorSemanticAnalyzeFail;

                    // This is a type.
                    bigint_init_unsigned(&inner_fields[2].data.x_union.tag, 0);

                    ConstExprValue *payload = create_const_vals(1);
                    payload->type = ira->codegen->builtin_types.entry_type;
                    payload->data.x_type = type_entry;

                    inner_fields[2].data.x_union.payload = payload;

                    break;
                }
            default:
                zig_unreachable();
        }

        definition_val->data.x_struct.fields = inner_fields;
        definition_index++;
    }

    assert(definition_index == definition_count);
    return ErrorNone;
}

static uint32_t ptr_len_to_size_enum_index(PtrLen ptr_len) {
    switch (ptr_len) {
        case PtrLenSingle:
            return 0;
        case PtrLenUnknown:
            return 1;
        case PtrLenC:
            return 3;
    }
    zig_unreachable();
}

static ConstExprValue *create_ptr_like_type_info(IrAnalyze *ira, ZigType *ptr_type_entry) {
    Error err;
    ZigType *attrs_type;
    uint32_t size_enum_index;
    if (is_slice(ptr_type_entry)) {
        attrs_type = ptr_type_entry->data.structure.fields[slice_ptr_index].type_entry;
        size_enum_index = 2;
    } else if (ptr_type_entry->id == ZigTypeIdPointer) {
        attrs_type = ptr_type_entry;
        size_enum_index = ptr_len_to_size_enum_index(ptr_type_entry->data.pointer.ptr_len);
    } else {
        zig_unreachable();
    }

    if ((err = type_resolve(ira->codegen, attrs_type->data.pointer.child_type, ResolveStatusAlignmentKnown)))
        return nullptr;

    ZigType *type_info_pointer_type = ir_type_info_get_type(ira, "Pointer", nullptr);
    assertNoError(ensure_complete_type(ira->codegen, type_info_pointer_type));

    ConstExprValue *result = create_const_vals(1);
    result->special = ConstValSpecialStatic;
    result->type = type_info_pointer_type;

    ConstExprValue *fields = create_const_vals(5);
    result->data.x_struct.fields = fields;

    // size: Size
    ensure_field_index(result->type, "size", 0);
    ZigType *type_info_pointer_size_type = ir_type_info_get_type(ira, "Size", type_info_pointer_type);
    assertNoError(ensure_complete_type(ira->codegen, type_info_pointer_size_type));
    fields[0].special = ConstValSpecialStatic;
    fields[0].type = type_info_pointer_size_type;
    bigint_init_unsigned(&fields[0].data.x_enum_tag, size_enum_index);

    // is_const: bool
    ensure_field_index(result->type, "is_const", 1);
    fields[1].special = ConstValSpecialStatic;
    fields[1].type = ira->codegen->builtin_types.entry_bool;
    fields[1].data.x_bool = attrs_type->data.pointer.is_const;
    // is_volatile: bool
    ensure_field_index(result->type, "is_volatile", 2);
    fields[2].special = ConstValSpecialStatic;
    fields[2].type = ira->codegen->builtin_types.entry_bool;
    fields[2].data.x_bool = attrs_type->data.pointer.is_volatile;
    // alignment: u32
    ensure_field_index(result->type, "alignment", 3);
    fields[3].special = ConstValSpecialStatic;
    fields[3].type = get_int_type(ira->codegen, false, 29);
    bigint_init_unsigned(&fields[3].data.x_bigint, get_ptr_align(ira->codegen, attrs_type));
    // child: type
    ensure_field_index(result->type, "child", 4);
    fields[4].special = ConstValSpecialStatic;
    fields[4].type = ira->codegen->builtin_types.entry_type;
    fields[4].data.x_type = attrs_type->data.pointer.child_type;

    return result;
};

static void make_enum_field_val(IrAnalyze *ira, ConstExprValue *enum_field_val, TypeEnumField *enum_field,
        ZigType *type_info_enum_field_type)
{
    enum_field_val->special = ConstValSpecialStatic;
    enum_field_val->type = type_info_enum_field_type;

    ConstExprValue *inner_fields = create_const_vals(2);
    inner_fields[1].special = ConstValSpecialStatic;
    inner_fields[1].type = ira->codegen->builtin_types.entry_usize;

    ConstExprValue *name = create_const_str_lit(ira->codegen, enum_field->name);
    init_const_slice(ira->codegen, &inner_fields[0], name, 0, buf_len(enum_field->name), true);

    bigint_init_bigint(&inner_fields[1].data.x_bigint, &enum_field->value);

    enum_field_val->data.x_struct.fields = inner_fields;
}

static Error ir_make_type_info_value(IrAnalyze *ira, AstNode *source_node, ZigType *type_entry, ConstExprValue **out) {
    Error err;
    assert(type_entry != nullptr);
    assert(!type_is_invalid(type_entry));

    if ((err = type_resolve(ira->codegen, type_entry, ResolveStatusSizeKnown)))
        return err;

    ConstExprValue *result = nullptr;
    switch (type_entry->id) {
        case ZigTypeIdInvalid:
            zig_unreachable();
        case ZigTypeIdMetaType:
        case ZigTypeIdVoid:
        case ZigTypeIdBool:
        case ZigTypeIdUnreachable:
        case ZigTypeIdComptimeFloat:
        case ZigTypeIdComptimeInt:
        case ZigTypeIdUndefined:
        case ZigTypeIdNull:
        case ZigTypeIdNamespace:
        case ZigTypeIdArgTuple:
        case ZigTypeIdOpaque:
            *out = nullptr;
            return ErrorNone;
        default:
            {
                // Lookup an available value in our cache.
                auto entry = ira->codegen->type_info_cache.maybe_get(type_entry);
                if (entry != nullptr) {
                    *out = entry->value;
                    return ErrorNone;
                }

                // Fallthrough if we don't find one.
            }
        case ZigTypeIdInt:
            {
                result = create_const_vals(1);
                result->special = ConstValSpecialStatic;
                result->type = ir_type_info_get_type(ira, "Int", nullptr);

                ConstExprValue *fields = create_const_vals(2);
                result->data.x_struct.fields = fields;

                // is_signed: bool
                ensure_field_index(result->type, "is_signed", 0);
                fields[0].special = ConstValSpecialStatic;
                fields[0].type = ira->codegen->builtin_types.entry_bool;
                fields[0].data.x_bool = type_entry->data.integral.is_signed;
                // bits: u8
                ensure_field_index(result->type, "bits", 1);
                fields[1].special = ConstValSpecialStatic;
                fields[1].type = ira->codegen->builtin_types.entry_u8;
                bigint_init_unsigned(&fields[1].data.x_bigint, type_entry->data.integral.bit_count);

                break;
            }
        case ZigTypeIdFloat:
            {
                result = create_const_vals(1);
                result->special = ConstValSpecialStatic;
                result->type = ir_type_info_get_type(ira, "Float", nullptr);

                ConstExprValue *fields = create_const_vals(1);
                result->data.x_struct.fields = fields;

                // bits: u8
                ensure_field_index(result->type, "bits", 0);
                fields[0].special = ConstValSpecialStatic;
                fields[0].type = ira->codegen->builtin_types.entry_u8;
                bigint_init_unsigned(&fields->data.x_bigint, type_entry->data.floating.bit_count);

                break;
            }
        case ZigTypeIdPointer:
            {
                result = create_ptr_like_type_info(ira, type_entry);
                if (result == nullptr)
                    return ErrorSemanticAnalyzeFail;
                break;
            }
        case ZigTypeIdArray:
            {
                result = create_const_vals(1);
                result->special = ConstValSpecialStatic;
                result->type = ir_type_info_get_type(ira, "Array", nullptr);

                ConstExprValue *fields = create_const_vals(2);
                result->data.x_struct.fields = fields;

                // len: usize
                ensure_field_index(result->type, "len", 0);
                fields[0].special = ConstValSpecialStatic;
                fields[0].type = ira->codegen->builtin_types.entry_usize;
                bigint_init_unsigned(&fields[0].data.x_bigint, type_entry->data.array.len);
                // child: type
                ensure_field_index(result->type, "child", 1);
                fields[1].special = ConstValSpecialStatic;
                fields[1].type = ira->codegen->builtin_types.entry_type;
                fields[1].data.x_type = type_entry->data.array.child_type;

                break;
            }
        case ZigTypeIdVector: {
            result = create_const_vals(1);
            result->special = ConstValSpecialStatic;
            result->type = ir_type_info_get_type(ira, "Vector", nullptr);

            ConstExprValue *fields = create_const_vals(2);
            result->data.x_struct.fields = fields;

            // len: usize
            ensure_field_index(result->type, "len", 0);
            fields[0].special = ConstValSpecialStatic;
            fields[0].type = ira->codegen->builtin_types.entry_u32;
            bigint_init_unsigned(&fields[0].data.x_bigint, type_entry->data.vector.len);
            // child: type
            ensure_field_index(result->type, "child", 1);
            fields[1].special = ConstValSpecialStatic;
            fields[1].type = ira->codegen->builtin_types.entry_type;
            fields[1].data.x_type = type_entry->data.vector.elem_type;

            break;
        }
        case ZigTypeIdOptional:
            {
                result = create_const_vals(1);
                result->special = ConstValSpecialStatic;
                result->type = ir_type_info_get_type(ira, "Optional", nullptr);

                ConstExprValue *fields = create_const_vals(1);
                result->data.x_struct.fields = fields;

                // child: type
                ensure_field_index(result->type, "child", 0);
                fields[0].special = ConstValSpecialStatic;
                fields[0].type = ira->codegen->builtin_types.entry_type;
                fields[0].data.x_type = type_entry->data.maybe.child_type;

                break;
            }
        case ZigTypeIdPromise:
            {
                result = create_const_vals(1);
                result->special = ConstValSpecialStatic;
                result->type = ir_type_info_get_type(ira, "Promise", nullptr);

                ConstExprValue *fields = create_const_vals(1);
                result->data.x_struct.fields = fields;

                // child: ?type
                ensure_field_index(result->type, "child", 0);
                fields[0].special = ConstValSpecialStatic;
                fields[0].type = get_optional_type(ira->codegen, ira->codegen->builtin_types.entry_type);

                if (type_entry->data.promise.result_type == nullptr)
                    fields[0].data.x_optional = nullptr;
                else {
                    ConstExprValue *child_type = create_const_vals(1);
                    child_type->special = ConstValSpecialStatic;
                    child_type->type = ira->codegen->builtin_types.entry_type;
                    child_type->data.x_type = type_entry->data.promise.result_type;
                    fields[0].data.x_optional = child_type;
                }

                break;
            }
        case ZigTypeIdEnum:
            {
                result = create_const_vals(1);
                result->special = ConstValSpecialStatic;
                result->type = ir_type_info_get_type(ira, "Enum", nullptr);

                ConstExprValue *fields = create_const_vals(4);
                result->data.x_struct.fields = fields;

                // layout: ContainerLayout
                ensure_field_index(result->type, "layout", 0);
                fields[0].special = ConstValSpecialStatic;
                fields[0].type = ir_type_info_get_type(ira, "ContainerLayout", nullptr);
                bigint_init_unsigned(&fields[0].data.x_enum_tag, type_entry->data.enumeration.layout);
                // tag_type: type
                ensure_field_index(result->type, "tag_type", 1);
                fields[1].special = ConstValSpecialStatic;
                fields[1].type = ira->codegen->builtin_types.entry_type;
                fields[1].data.x_type = type_entry->data.enumeration.tag_int_type;
                // fields: []TypeInfo.EnumField
                ensure_field_index(result->type, "fields", 2);

                ZigType *type_info_enum_field_type = ir_type_info_get_type(ira, "EnumField", nullptr);
                uint32_t enum_field_count = type_entry->data.enumeration.src_field_count;

                ConstExprValue *enum_field_array = create_const_vals(1);
                enum_field_array->special = ConstValSpecialStatic;
                enum_field_array->type = get_array_type(ira->codegen, type_info_enum_field_type, enum_field_count);
                enum_field_array->data.x_array.special = ConstArraySpecialNone;
                enum_field_array->data.x_array.data.s_none.elements = create_const_vals(enum_field_count);

                init_const_slice(ira->codegen, &fields[2], enum_field_array, 0, enum_field_count, false);

                for (uint32_t enum_field_index = 0; enum_field_index < enum_field_count; enum_field_index++)
                {
                    TypeEnumField *enum_field = &type_entry->data.enumeration.fields[enum_field_index];
                    ConstExprValue *enum_field_val = &enum_field_array->data.x_array.data.s_none.elements[enum_field_index];
                    make_enum_field_val(ira, enum_field_val, enum_field, type_info_enum_field_type);
                    enum_field_val->parent.id = ConstParentIdArray;
                    enum_field_val->parent.data.p_array.array_val = enum_field_array;
                    enum_field_val->parent.data.p_array.elem_index = enum_field_index;
                }
                // defs: []TypeInfo.Definition
                ensure_field_index(result->type, "defs", 3);
                if ((err = ir_make_type_info_defs(ira, &fields[3], type_entry->data.enumeration.decls_scope)))
                    return err;

                break;
            }
        case ZigTypeIdErrorSet:
            {
                result = create_const_vals(1);
                result->special = ConstValSpecialStatic;
                result->type = ir_type_info_get_type(ira, "ErrorSet", nullptr);

                ConstExprValue *fields = create_const_vals(1);
                result->data.x_struct.fields = fields;

                // errors: []TypeInfo.Error
                ensure_field_index(result->type, "errors", 0);

                ZigType *type_info_error_type = ir_type_info_get_type(ira, "Error", nullptr);
                if (!resolve_inferred_error_set(ira->codegen, type_entry, source_node)) {
                    return ErrorSemanticAnalyzeFail;
                }
                if (type_is_global_error_set(type_entry)) {
                    ir_add_error_node(ira, source_node,
                        buf_sprintf("TODO: compiler bug: implement @typeInfo support for anyerror. https://github.com/ziglang/zig/issues/1936"));
                    return ErrorSemanticAnalyzeFail;
                }

                uint32_t error_count = type_entry->data.error_set.err_count;
                ConstExprValue *error_array = create_const_vals(1);
                error_array->special = ConstValSpecialStatic;
                error_array->type = get_array_type(ira->codegen, type_info_error_type, error_count);
                error_array->data.x_array.special = ConstArraySpecialNone;
                error_array->data.x_array.data.s_none.elements = create_const_vals(error_count);

                init_const_slice(ira->codegen, &fields[0], error_array, 0, error_count, false);
                for (uint32_t error_index = 0; error_index < error_count; error_index++) {
                    ErrorTableEntry *error = type_entry->data.error_set.errors[error_index];
                    ConstExprValue *error_val = &error_array->data.x_array.data.s_none.elements[error_index];

                    error_val->special = ConstValSpecialStatic;
                    error_val->type = type_info_error_type;

                    ConstExprValue *inner_fields = create_const_vals(2);
                    inner_fields[1].special = ConstValSpecialStatic;
                    inner_fields[1].type = ira->codegen->builtin_types.entry_usize;

                    ConstExprValue *name = nullptr;
                    if (error->cached_error_name_val != nullptr)
                        name = error->cached_error_name_val;
                    if (name == nullptr)
                        name = create_const_str_lit(ira->codegen, &error->name);
                    init_const_slice(ira->codegen, &inner_fields[0], name, 0, buf_len(&error->name), true);
                    bigint_init_unsigned(&inner_fields[1].data.x_bigint, error->value);

                    error_val->data.x_struct.fields = inner_fields;
                    error_val->parent.id = ConstParentIdArray;
                    error_val->parent.data.p_array.array_val = error_array;
                    error_val->parent.data.p_array.elem_index = error_index;
                }

                break;
            }
        case ZigTypeIdErrorUnion:
            {
                result = create_const_vals(1);
                result->special = ConstValSpecialStatic;
                result->type = ir_type_info_get_type(ira, "ErrorUnion", nullptr);

                ConstExprValue *fields = create_const_vals(2);
                result->data.x_struct.fields = fields;

                // error_set: type
                ensure_field_index(result->type, "error_set", 0);
                fields[0].special = ConstValSpecialStatic;
                fields[0].type = ira->codegen->builtin_types.entry_type;
                fields[0].data.x_type = type_entry->data.error_union.err_set_type;

                // payload: type
                ensure_field_index(result->type, "payload", 1);
                fields[1].special = ConstValSpecialStatic;
                fields[1].type = ira->codegen->builtin_types.entry_type;
                fields[1].data.x_type = type_entry->data.error_union.payload_type;

                break;
            }
        case ZigTypeIdUnion:
            {
                result = create_const_vals(1);
                result->special = ConstValSpecialStatic;
                result->type = ir_type_info_get_type(ira, "Union", nullptr);

                ConstExprValue *fields = create_const_vals(4);
                result->data.x_struct.fields = fields;

                // layout: ContainerLayout
                ensure_field_index(result->type, "layout", 0);
                fields[0].special = ConstValSpecialStatic;
                fields[0].type = ir_type_info_get_type(ira, "ContainerLayout", nullptr);
                bigint_init_unsigned(&fields[0].data.x_enum_tag, type_entry->data.unionation.layout);
                // tag_type: ?type
                ensure_field_index(result->type, "tag_type", 1);
                fields[1].special = ConstValSpecialStatic;
                fields[1].type = get_optional_type(ira->codegen, ira->codegen->builtin_types.entry_type);

                AstNode *union_decl_node = type_entry->data.unionation.decl_node;
                if (union_decl_node->data.container_decl.auto_enum ||
                    union_decl_node->data.container_decl.init_arg_expr != nullptr)
                {
                    ConstExprValue *tag_type = create_const_vals(1);
                    tag_type->special = ConstValSpecialStatic;
                    tag_type->type = ira->codegen->builtin_types.entry_type;
                    tag_type->data.x_type = type_entry->data.unionation.tag_type;
                    fields[1].data.x_optional = tag_type;
                } else {
                    fields[1].data.x_optional = nullptr;
                }
                // fields: []TypeInfo.UnionField
                ensure_field_index(result->type, "fields", 2);

                ZigType *type_info_union_field_type = ir_type_info_get_type(ira, "UnionField", nullptr);
                uint32_t union_field_count = type_entry->data.unionation.src_field_count;

                ConstExprValue *union_field_array = create_const_vals(1);
                union_field_array->special = ConstValSpecialStatic;
                union_field_array->type = get_array_type(ira->codegen, type_info_union_field_type, union_field_count);
                union_field_array->data.x_array.special = ConstArraySpecialNone;
                union_field_array->data.x_array.data.s_none.elements = create_const_vals(union_field_count);

                init_const_slice(ira->codegen, &fields[2], union_field_array, 0, union_field_count, false);

                ZigType *type_info_enum_field_type = ir_type_info_get_type(ira, "EnumField", nullptr);

                for (uint32_t union_field_index = 0; union_field_index < union_field_count; union_field_index++) {
                    TypeUnionField *union_field = &type_entry->data.unionation.fields[union_field_index];
                    ConstExprValue *union_field_val = &union_field_array->data.x_array.data.s_none.elements[union_field_index];

                    union_field_val->special = ConstValSpecialStatic;
                    union_field_val->type = type_info_union_field_type;

                    ConstExprValue *inner_fields = create_const_vals(3);
                    inner_fields[1].special = ConstValSpecialStatic;
                    inner_fields[1].type = get_optional_type(ira->codegen, type_info_enum_field_type);

                    if (fields[1].data.x_optional == nullptr) {
                        inner_fields[1].data.x_optional = nullptr;
                    } else {
                        inner_fields[1].data.x_optional = create_const_vals(1);
                        make_enum_field_val(ira, inner_fields[1].data.x_optional, union_field->enum_field, type_info_enum_field_type);
                    }

                    inner_fields[2].special = ConstValSpecialStatic;
                    inner_fields[2].type = ira->codegen->builtin_types.entry_type;
                    inner_fields[2].data.x_type = union_field->type_entry;

                    ConstExprValue *name = create_const_str_lit(ira->codegen, union_field->name);
                    init_const_slice(ira->codegen, &inner_fields[0], name, 0, buf_len(union_field->name), true);

                    union_field_val->data.x_struct.fields = inner_fields;
                    union_field_val->parent.id = ConstParentIdArray;
                    union_field_val->parent.data.p_array.array_val = union_field_array;
                    union_field_val->parent.data.p_array.elem_index = union_field_index;
                }
                // defs: []TypeInfo.Definition
                ensure_field_index(result->type, "defs", 3);
                if ((err = ir_make_type_info_defs(ira, &fields[3], type_entry->data.unionation.decls_scope)))
                    return err;

                break;
            }
        case ZigTypeIdStruct:
            {
                if (type_entry->data.structure.is_slice) {
                    result = create_ptr_like_type_info(ira, type_entry);
                    if (result == nullptr)
                        return ErrorSemanticAnalyzeFail;
                    break;
                }

                result = create_const_vals(1);
                result->special = ConstValSpecialStatic;
                result->type = ir_type_info_get_type(ira, "Struct", nullptr);

                ConstExprValue *fields = create_const_vals(3);
                result->data.x_struct.fields = fields;

                // layout: ContainerLayout
                ensure_field_index(result->type, "layout", 0);
                fields[0].special = ConstValSpecialStatic;
                fields[0].type = ir_type_info_get_type(ira, "ContainerLayout", nullptr);
                bigint_init_unsigned(&fields[0].data.x_enum_tag, type_entry->data.structure.layout);
                // fields: []TypeInfo.StructField
                ensure_field_index(result->type, "fields", 1);

                ZigType *type_info_struct_field_type = ir_type_info_get_type(ira, "StructField", nullptr);
                uint32_t struct_field_count = type_entry->data.structure.src_field_count;

                ConstExprValue *struct_field_array = create_const_vals(1);
                struct_field_array->special = ConstValSpecialStatic;
                struct_field_array->type = get_array_type(ira->codegen, type_info_struct_field_type, struct_field_count);
                struct_field_array->data.x_array.special = ConstArraySpecialNone;
                struct_field_array->data.x_array.data.s_none.elements = create_const_vals(struct_field_count);

                init_const_slice(ira->codegen, &fields[1], struct_field_array, 0, struct_field_count, false);

                for (uint32_t struct_field_index = 0; struct_field_index < struct_field_count; struct_field_index++) {
                    TypeStructField *struct_field = &type_entry->data.structure.fields[struct_field_index];
                    ConstExprValue *struct_field_val = &struct_field_array->data.x_array.data.s_none.elements[struct_field_index];

                    struct_field_val->special = ConstValSpecialStatic;
                    struct_field_val->type = type_info_struct_field_type;

                    ConstExprValue *inner_fields = create_const_vals(3);
                    inner_fields[1].special = ConstValSpecialStatic;
                    inner_fields[1].type = get_optional_type(ira->codegen, ira->codegen->builtin_types.entry_usize);

                    if (!type_has_bits(struct_field->type_entry)) {
                        inner_fields[1].data.x_optional = nullptr;
                    } else {
                        size_t byte_offset = LLVMOffsetOfElement(ira->codegen->target_data_ref, type_entry->type_ref, struct_field->gen_index);
                        inner_fields[1].data.x_optional = create_const_vals(1);
                        inner_fields[1].data.x_optional->special = ConstValSpecialStatic;
                        inner_fields[1].data.x_optional->type = ira->codegen->builtin_types.entry_usize;
                        bigint_init_unsigned(&inner_fields[1].data.x_optional->data.x_bigint, byte_offset);
                    }

                    inner_fields[2].special = ConstValSpecialStatic;
                    inner_fields[2].type = ira->codegen->builtin_types.entry_type;
                    inner_fields[2].data.x_type = struct_field->type_entry;

                    ConstExprValue *name = create_const_str_lit(ira->codegen, struct_field->name);
                    init_const_slice(ira->codegen, &inner_fields[0], name, 0, buf_len(struct_field->name), true);

                    struct_field_val->data.x_struct.fields = inner_fields;
                    struct_field_val->parent.id = ConstParentIdArray;
                    struct_field_val->parent.data.p_array.array_val = struct_field_array;
                    struct_field_val->parent.data.p_array.elem_index = struct_field_index;
                }
                // defs: []TypeInfo.Definition
                ensure_field_index(result->type, "defs", 2);
                if ((err = ir_make_type_info_defs(ira, &fields[2], type_entry->data.structure.decls_scope)))
                    return err;

                break;
            }
        case ZigTypeIdFn:
            {
                result = create_const_vals(1);
                result->special = ConstValSpecialStatic;
                result->type = ir_type_info_get_type(ira, "Fn", nullptr);

                ConstExprValue *fields = create_const_vals(6);
                result->data.x_struct.fields = fields;

                // calling_convention: TypeInfo.CallingConvention
                ensure_field_index(result->type, "calling_convention", 0);
                fields[0].special = ConstValSpecialStatic;
                fields[0].type = ir_type_info_get_type(ira, "CallingConvention", nullptr);
                bigint_init_unsigned(&fields[0].data.x_enum_tag, type_entry->data.fn.fn_type_id.cc);
                // is_generic: bool
                ensure_field_index(result->type, "is_generic", 1);
                bool is_generic = type_entry->data.fn.is_generic;
                fields[1].special = ConstValSpecialStatic;
                fields[1].type = ira->codegen->builtin_types.entry_bool;
                fields[1].data.x_bool = is_generic;
                // is_varargs: bool
                ensure_field_index(result->type, "is_var_args", 2);
                bool is_varargs = type_entry->data.fn.fn_type_id.is_var_args;
                fields[2].special = ConstValSpecialStatic;
                fields[2].type = ira->codegen->builtin_types.entry_bool;
                fields[2].data.x_bool = type_entry->data.fn.fn_type_id.is_var_args;
                // return_type: ?type
                ensure_field_index(result->type, "return_type", 3);
                fields[3].special = ConstValSpecialStatic;
                fields[3].type = get_optional_type(ira->codegen, ira->codegen->builtin_types.entry_type);
                if (type_entry->data.fn.fn_type_id.return_type == nullptr)
                    fields[3].data.x_optional = nullptr;
                else {
                    ConstExprValue *return_type = create_const_vals(1);
                    return_type->special = ConstValSpecialStatic;
                    return_type->type = ira->codegen->builtin_types.entry_type;
                    return_type->data.x_type = type_entry->data.fn.fn_type_id.return_type;
                    fields[3].data.x_optional = return_type;
                }
                // async_allocator_type: type
                ensure_field_index(result->type, "async_allocator_type", 4);
                fields[4].special = ConstValSpecialStatic;
                fields[4].type = get_optional_type(ira->codegen, ira->codegen->builtin_types.entry_type);
                if (type_entry->data.fn.fn_type_id.async_allocator_type == nullptr)
                    fields[4].data.x_optional = nullptr;
                else {
                    ConstExprValue *async_alloc_type = create_const_vals(1);
                    async_alloc_type->special = ConstValSpecialStatic;
                    async_alloc_type->type = ira->codegen->builtin_types.entry_type;
                    async_alloc_type->data.x_type = type_entry->data.fn.fn_type_id.async_allocator_type;
                    fields[4].data.x_optional = async_alloc_type;
                }
                // args: []TypeInfo.FnArg
                ZigType *type_info_fn_arg_type = ir_type_info_get_type(ira, "FnArg", nullptr);
                size_t fn_arg_count = type_entry->data.fn.fn_type_id.param_count -
                        (is_varargs && type_entry->data.fn.fn_type_id.cc != CallingConventionC);

                ConstExprValue *fn_arg_array = create_const_vals(1);
                fn_arg_array->special = ConstValSpecialStatic;
                fn_arg_array->type = get_array_type(ira->codegen, type_info_fn_arg_type, fn_arg_count);
                fn_arg_array->data.x_array.special = ConstArraySpecialNone;
                fn_arg_array->data.x_array.data.s_none.elements = create_const_vals(fn_arg_count);

                init_const_slice(ira->codegen, &fields[5], fn_arg_array, 0, fn_arg_count, false);

                for (size_t fn_arg_index = 0; fn_arg_index < fn_arg_count; fn_arg_index++)
                {
                    FnTypeParamInfo *fn_param_info = &type_entry->data.fn.fn_type_id.param_info[fn_arg_index];
                    ConstExprValue *fn_arg_val = &fn_arg_array->data.x_array.data.s_none.elements[fn_arg_index];

                    fn_arg_val->special = ConstValSpecialStatic;
                    fn_arg_val->type = type_info_fn_arg_type;

                    bool arg_is_generic = fn_param_info->type == nullptr;
                    if (arg_is_generic) assert(is_generic);

                    ConstExprValue *inner_fields = create_const_vals(3);
                    inner_fields[0].special = ConstValSpecialStatic;
                    inner_fields[0].type = ira->codegen->builtin_types.entry_bool;
                    inner_fields[0].data.x_bool = arg_is_generic;
                    inner_fields[1].special = ConstValSpecialStatic;
                    inner_fields[1].type = ira->codegen->builtin_types.entry_bool;
                    inner_fields[1].data.x_bool = fn_param_info->is_noalias;
                    inner_fields[2].special = ConstValSpecialStatic;
                    inner_fields[2].type = get_optional_type(ira->codegen, ira->codegen->builtin_types.entry_type);

                    if (arg_is_generic)
                        inner_fields[2].data.x_optional = nullptr;
                    else {
                        ConstExprValue *arg_type = create_const_vals(1);
                        arg_type->special = ConstValSpecialStatic;
                        arg_type->type = ira->codegen->builtin_types.entry_type;
                        arg_type->data.x_type = fn_param_info->type;
                        inner_fields[2].data.x_optional = arg_type;
                    }

                    fn_arg_val->data.x_struct.fields = inner_fields;
                    fn_arg_val->parent.id = ConstParentIdArray;
                    fn_arg_val->parent.data.p_array.array_val = fn_arg_array;
                    fn_arg_val->parent.data.p_array.elem_index = fn_arg_index;
                }

                break;
            }
        case ZigTypeIdBoundFn:
            {
                ZigType *fn_type = type_entry->data.bound_fn.fn_type;
                assert(fn_type->id == ZigTypeIdFn);
                if ((err = ir_make_type_info_value(ira, source_node, fn_type, &result)))
                    return err;

                break;
            }
    }

    assert(result != nullptr);
    ira->codegen->type_info_cache.put(type_entry, result);
    *out = result;
    return ErrorNone;
}

static IrInstruction *ir_analyze_instruction_type_info(IrAnalyze *ira,
        IrInstructionTypeInfo *instruction)
{
    Error err;
    IrInstruction *type_value = instruction->type_value->child;
    ZigType *type_entry = ir_resolve_type(ira, type_value);
    if (type_is_invalid(type_entry))
        return ira->codegen->invalid_instruction;

    ZigType *result_type = ir_type_info_get_type(ira, nullptr, nullptr);

    ConstExprValue *payload;
    if ((err = ir_make_type_info_value(ira, instruction->base.source_node, type_entry, &payload)))
        return ira->codegen->invalid_instruction;

    IrInstruction *result = ir_const(ira, &instruction->base, result_type);
    ConstExprValue *out_val = &result->value;
    bigint_init_unsigned(&out_val->data.x_union.tag, type_id_index(type_entry));
    out_val->data.x_union.payload = payload;

    if (payload != nullptr) {
        assert(payload->type->id == ZigTypeIdStruct);
        payload->parent.id = ConstParentIdUnion;
        payload->parent.data.p_union.union_val = out_val;
    }

    return result;
}

static IrInstruction *ir_analyze_instruction_type_id(IrAnalyze *ira,
        IrInstructionTypeId *instruction)
{
    IrInstruction *type_value = instruction->type_value->child;
    ZigType *type_entry = ir_resolve_type(ira, type_value);
    if (type_is_invalid(type_entry))
        return ira->codegen->invalid_instruction;

    ConstExprValue *var_value = get_builtin_value(ira->codegen, "TypeId");
    assert(var_value->type->id == ZigTypeIdMetaType);
    ZigType *result_type = var_value->data.x_type;

    IrInstruction *result = ir_const(ira, &instruction->base, result_type);
    bigint_init_unsigned(&result->value.data.x_enum_tag, type_id_index(type_entry));
    return result;
}

static IrInstruction *ir_analyze_instruction_set_eval_branch_quota(IrAnalyze *ira,
        IrInstructionSetEvalBranchQuota *instruction)
{
    uint64_t new_quota;
    if (!ir_resolve_usize(ira, instruction->new_quota->child, &new_quota))
        return ira->codegen->invalid_instruction;

    if (new_quota > ira->new_irb.exec->backward_branch_quota) {
        ira->new_irb.exec->backward_branch_quota = new_quota;
    }

    return ir_const_void(ira, &instruction->base);
}

static IrInstruction *ir_analyze_instruction_type_name(IrAnalyze *ira, IrInstructionTypeName *instruction) {
    IrInstruction *type_value = instruction->type_value->child;
    ZigType *type_entry = ir_resolve_type(ira, type_value);
    if (type_is_invalid(type_entry))
        return ira->codegen->invalid_instruction;

    if (!type_entry->cached_const_name_val) {
        type_entry->cached_const_name_val = create_const_str_lit(ira->codegen, &type_entry->name);
    }
    IrInstruction *result = ir_const(ira, &instruction->base, nullptr);
    copy_const_val(&result->value, type_entry->cached_const_name_val, true);
    return result;
}

static IrInstruction *ir_analyze_instruction_c_import(IrAnalyze *ira, IrInstructionCImport *instruction) {
    AstNode *node = instruction->base.source_node;
    assert(node->type == NodeTypeFnCallExpr);
    AstNode *block_node = node->data.fn_call_expr.params.at(0);

    ScopeCImport *cimport_scope = create_cimport_scope(ira->codegen, node, instruction->base.scope);

    // Execute the C import block like an inline function
    ZigType *void_type = ira->codegen->builtin_types.entry_void;
    ConstExprValue *cimport_result = ir_eval_const_value(ira->codegen, &cimport_scope->base, block_node, void_type,
        ira->new_irb.exec->backward_branch_count, ira->new_irb.exec->backward_branch_quota, nullptr,
        &cimport_scope->buf, block_node, nullptr, nullptr, nullptr);
    if (type_is_invalid(cimport_result->type))
        return ira->codegen->invalid_instruction;

    ImportTableEntry *child_import = allocate<ImportTableEntry>(1);
    child_import->decls_scope = create_decls_scope(ira->codegen, node, nullptr, nullptr, child_import);
    child_import->c_import_node = node;
    child_import->package = new_anonymous_package();
    child_import->package->package_table.put(buf_create_from_str("builtin"), ira->codegen->compile_var_package);
    child_import->package->package_table.put(buf_create_from_str("std"), ira->codegen->std_package);
    child_import->di_file = ZigLLVMCreateFile(ira->codegen->dbuilder,
        buf_ptr(buf_create_from_str("cimport.h")), buf_ptr(buf_create_from_str(".")));

    ZigList<ErrorMsg *> errors = {0};

    Error err;
    if ((err = parse_h_buf(child_import, &errors, &cimport_scope->buf, ira->codegen, node))) {
        if (err != ErrorCCompileErrors) {
            ir_add_error_node(ira, node, buf_sprintf("C import failed: %s", err_str(err)));
            return ira->codegen->invalid_instruction;
        }
    }

    if (errors.length > 0) {
        ErrorMsg *parent_err_msg = ir_add_error_node(ira, node, buf_sprintf("C import failed"));
        if (ira->codegen->libc_link_lib == nullptr) {
            add_error_note(ira->codegen, parent_err_msg, node,
                buf_sprintf("libc headers not available; compilation does not link against libc"));
        }
        for (size_t i = 0; i < errors.length; i += 1) {
            ErrorMsg *err_msg = errors.at(i);
            err_msg_add_note(parent_err_msg, err_msg);
        }

        return ira->codegen->invalid_instruction;
    }

    if (ira->codegen->verbose_cimport) {
        fprintf(stderr, "\nC imports:\n");
        fprintf(stderr, "-----------\n");
        ast_render(ira->codegen, stderr, child_import->root, 4);
    }

    scan_decls(ira->codegen, child_import->decls_scope, child_import->root);

    IrInstruction *result = ir_const(ira, &instruction->base, ira->codegen->builtin_types.entry_namespace);
    result->value.data.x_import = child_import;
    return result;
}

static IrInstruction *ir_analyze_instruction_c_include(IrAnalyze *ira, IrInstructionCInclude *instruction) {
    IrInstruction *name_value = instruction->name->child;
    if (type_is_invalid(name_value->value.type))
        return ira->codegen->invalid_instruction;

    Buf *include_name = ir_resolve_str(ira, name_value);
    if (!include_name)
        return ira->codegen->invalid_instruction;

    Buf *c_import_buf = exec_c_import_buf(ira->new_irb.exec);
    // We check for this error in pass1
    assert(c_import_buf);

    buf_appendf(c_import_buf, "#include <%s>\n", buf_ptr(include_name));

    return ir_const_void(ira, &instruction->base);
}

static IrInstruction *ir_analyze_instruction_c_define(IrAnalyze *ira, IrInstructionCDefine *instruction) {
    IrInstruction *name = instruction->name->child;
    if (type_is_invalid(name->value.type))
        return ira->codegen->invalid_instruction;

    Buf *define_name = ir_resolve_str(ira, name);
    if (!define_name)
        return ira->codegen->invalid_instruction;

    IrInstruction *value = instruction->value->child;
    if (type_is_invalid(value->value.type))
        return ira->codegen->invalid_instruction;

    Buf *define_value = ir_resolve_str(ira, value);
    if (!define_value)
        return ira->codegen->invalid_instruction;

    Buf *c_import_buf = exec_c_import_buf(ira->new_irb.exec);
    // We check for this error in pass1
    assert(c_import_buf);

    buf_appendf(c_import_buf, "#define %s %s\n", buf_ptr(define_name), buf_ptr(define_value));

    return ir_const_void(ira, &instruction->base);
}

static IrInstruction *ir_analyze_instruction_c_undef(IrAnalyze *ira, IrInstructionCUndef *instruction) {
    IrInstruction *name = instruction->name->child;
    if (type_is_invalid(name->value.type))
        return ira->codegen->invalid_instruction;

    Buf *undef_name = ir_resolve_str(ira, name);
    if (!undef_name)
        return ira->codegen->invalid_instruction;

    Buf *c_import_buf = exec_c_import_buf(ira->new_irb.exec);
    // We check for this error in pass1
    assert(c_import_buf);

    buf_appendf(c_import_buf, "#undef %s\n", buf_ptr(undef_name));

    return ir_const_void(ira, &instruction->base);
}

static IrInstruction *ir_analyze_instruction_embed_file(IrAnalyze *ira, IrInstructionEmbedFile *instruction) {
    IrInstruction *name = instruction->name->child;
    if (type_is_invalid(name->value.type))
        return ira->codegen->invalid_instruction;

    Buf *rel_file_path = ir_resolve_str(ira, name);
    if (!rel_file_path)
        return ira->codegen->invalid_instruction;

    ImportTableEntry *import = get_scope_import(instruction->base.scope);
    // figure out absolute path to resource
    Buf source_dir_path = BUF_INIT;
    os_path_dirname(import->path, &source_dir_path);

    Buf *resolve_paths[] = {
        &source_dir_path,
        rel_file_path,
    };
    Buf *file_path = buf_alloc();
    *file_path = os_path_resolve(resolve_paths, 2);

    // load from file system into const expr
    Buf *file_contents = buf_alloc();
    Error err;
    if ((err = file_fetch(ira->codegen, file_path, file_contents))) {
        if (err == ErrorFileNotFound) {
            ir_add_error(ira, instruction->name, buf_sprintf("unable to find '%s'", buf_ptr(file_path)));
            return ira->codegen->invalid_instruction;
        } else {
            ir_add_error(ira, instruction->name, buf_sprintf("unable to open '%s': %s", buf_ptr(file_path), err_str(err)));
            return ira->codegen->invalid_instruction;
        }
    }

    ZigType *result_type = get_array_type(ira->codegen,
            ira->codegen->builtin_types.entry_u8, buf_len(file_contents));
    IrInstruction *result = ir_const(ira, &instruction->base, result_type);
    init_const_str_lit(ira->codegen, &result->value, file_contents);
    return result;
}

static IrInstruction *ir_analyze_instruction_cmpxchg(IrAnalyze *ira, IrInstructionCmpxchgSrc *instruction) {
    ZigType *operand_type = ir_resolve_atomic_operand_type(ira, instruction->type_value->child);
    if (type_is_invalid(operand_type))
        return ira->codegen->invalid_instruction;

    IrInstruction *ptr = instruction->ptr->child;
    if (type_is_invalid(ptr->value.type))
        return ira->codegen->invalid_instruction;

    // TODO let this be volatile
    ZigType *ptr_type = get_pointer_to_type(ira->codegen, operand_type, false);
    IrInstruction *casted_ptr = ir_implicit_cast(ira, ptr, ptr_type);
    if (type_is_invalid(casted_ptr->value.type))
        return ira->codegen->invalid_instruction;

    IrInstruction *cmp_value = instruction->cmp_value->child;
    if (type_is_invalid(cmp_value->value.type))
        return ira->codegen->invalid_instruction;

    IrInstruction *new_value = instruction->new_value->child;
    if (type_is_invalid(new_value->value.type))
        return ira->codegen->invalid_instruction;

    IrInstruction *success_order_value = instruction->success_order_value->child;
    if (type_is_invalid(success_order_value->value.type))
        return ira->codegen->invalid_instruction;

    AtomicOrder success_order;
    if (!ir_resolve_atomic_order(ira, success_order_value, &success_order))
        return ira->codegen->invalid_instruction;

    IrInstruction *failure_order_value = instruction->failure_order_value->child;
    if (type_is_invalid(failure_order_value->value.type))
        return ira->codegen->invalid_instruction;

    AtomicOrder failure_order;
    if (!ir_resolve_atomic_order(ira, failure_order_value, &failure_order))
        return ira->codegen->invalid_instruction;

    IrInstruction *casted_cmp_value = ir_implicit_cast(ira, cmp_value, operand_type);
    if (type_is_invalid(casted_cmp_value->value.type))
        return ira->codegen->invalid_instruction;

    IrInstruction *casted_new_value = ir_implicit_cast(ira, new_value, operand_type);
    if (type_is_invalid(casted_new_value->value.type))
        return ira->codegen->invalid_instruction;

    if (success_order < AtomicOrderMonotonic) {
        ir_add_error(ira, success_order_value,
                buf_sprintf("success atomic ordering must be Monotonic or stricter"));
        return ira->codegen->invalid_instruction;
    }
    if (failure_order < AtomicOrderMonotonic) {
        ir_add_error(ira, failure_order_value,
                buf_sprintf("failure atomic ordering must be Monotonic or stricter"));
        return ira->codegen->invalid_instruction;
    }
    if (failure_order > success_order) {
        ir_add_error(ira, failure_order_value,
                buf_sprintf("failure atomic ordering must be no stricter than success"));
        return ira->codegen->invalid_instruction;
    }
    if (failure_order == AtomicOrderRelease || failure_order == AtomicOrderAcqRel) {
        ir_add_error(ira, failure_order_value,
                buf_sprintf("failure atomic ordering must not be Release or AcqRel"));
        return ira->codegen->invalid_instruction;
    }

    if (instr_is_comptime(casted_ptr) && instr_is_comptime(casted_cmp_value) && instr_is_comptime(casted_new_value)) {
        zig_panic("TODO compile-time execution of cmpxchg");
    }

    IrInstruction *result = ir_build_cmpxchg_gen(ira, &instruction->base,
            casted_ptr, casted_cmp_value, casted_new_value,
            success_order, failure_order, instruction->is_weak);
    result->value.type = get_optional_type(ira->codegen, operand_type);
    ir_add_alloca(ira, result, result->value.type);
    return result;
}

static IrInstruction *ir_analyze_instruction_fence(IrAnalyze *ira, IrInstructionFence *instruction) {
    IrInstruction *order_value = instruction->order_value->child;
    if (type_is_invalid(order_value->value.type))
        return ira->codegen->invalid_instruction;

    AtomicOrder order;
    if (!ir_resolve_atomic_order(ira, order_value, &order))
        return ira->codegen->invalid_instruction;

    IrInstruction *result = ir_build_fence(&ira->new_irb,
        instruction->base.scope, instruction->base.source_node, order_value, order);
    result->value.type = ira->codegen->builtin_types.entry_void;
    return result;
}

static IrInstruction *ir_analyze_instruction_truncate(IrAnalyze *ira, IrInstructionTruncate *instruction) {
    IrInstruction *dest_type_value = instruction->dest_type->child;
    ZigType *dest_type = ir_resolve_type(ira, dest_type_value);
    if (type_is_invalid(dest_type))
        return ira->codegen->invalid_instruction;

    if (dest_type->id != ZigTypeIdInt &&
        dest_type->id != ZigTypeIdComptimeInt)
    {
        ir_add_error(ira, dest_type_value, buf_sprintf("expected integer type, found '%s'", buf_ptr(&dest_type->name)));
        return ira->codegen->invalid_instruction;
    }

    IrInstruction *target = instruction->target->child;
    ZigType *src_type = target->value.type;
    if (type_is_invalid(src_type))
        return ira->codegen->invalid_instruction;

    if (src_type->id != ZigTypeIdInt &&
        src_type->id != ZigTypeIdComptimeInt)
    {
        ir_add_error(ira, target, buf_sprintf("expected integer type, found '%s'", buf_ptr(&src_type->name)));
        return ira->codegen->invalid_instruction;
    }

    if (dest_type->id == ZigTypeIdComptimeInt) {
        return ir_implicit_cast(ira, target, dest_type);
    }

    if (instr_is_comptime(target)) {
        ConstExprValue *val = ir_resolve_const(ira, target, UndefBad);
        if (val == nullptr)
            return ira->codegen->invalid_instruction;

        IrInstruction *result = ir_const(ira, &instruction->base, dest_type);
        bigint_truncate(&result->value.data.x_bigint, &val->data.x_bigint,
                dest_type->data.integral.bit_count, dest_type->data.integral.is_signed);
        return result;
    }

    if (src_type->data.integral.bit_count == 0 || dest_type->data.integral.bit_count == 0) {
        IrInstruction *result = ir_const(ira, &instruction->base, dest_type);
        bigint_init_unsigned(&result->value.data.x_bigint, 0);
        return result;
    }

    if (src_type->data.integral.is_signed != dest_type->data.integral.is_signed) {
        const char *sign_str = dest_type->data.integral.is_signed ? "signed" : "unsigned";
        ir_add_error(ira, target, buf_sprintf("expected %s integer type, found '%s'", sign_str, buf_ptr(&src_type->name)));
        return ira->codegen->invalid_instruction;
    } else if (src_type->data.integral.bit_count < dest_type->data.integral.bit_count) {
        ir_add_error(ira, target, buf_sprintf("type '%s' has fewer bits than destination type '%s'",
                    buf_ptr(&src_type->name), buf_ptr(&dest_type->name)));
        return ira->codegen->invalid_instruction;
    }

    IrInstruction *new_instruction = ir_build_truncate(&ira->new_irb, instruction->base.scope,
            instruction->base.source_node, dest_type_value, target);
    new_instruction->value.type = dest_type;
    return new_instruction;
}

static IrInstruction *ir_analyze_instruction_int_cast(IrAnalyze *ira, IrInstructionIntCast *instruction) {
    ZigType *dest_type = ir_resolve_type(ira, instruction->dest_type->child);
    if (type_is_invalid(dest_type))
        return ira->codegen->invalid_instruction;

    if (dest_type->id != ZigTypeIdInt && dest_type->id != ZigTypeIdComptimeInt) {
        ir_add_error(ira, instruction->dest_type, buf_sprintf("expected integer type, found '%s'", buf_ptr(&dest_type->name)));
        return ira->codegen->invalid_instruction;
    }

    IrInstruction *target = instruction->target->child;
    if (type_is_invalid(target->value.type))
        return ira->codegen->invalid_instruction;

    if (target->value.type->id != ZigTypeIdInt && target->value.type->id != ZigTypeIdComptimeInt) {
        ir_add_error(ira, instruction->target, buf_sprintf("expected integer type, found '%s'",
                    buf_ptr(&target->value.type->name)));
        return ira->codegen->invalid_instruction;
    }

    if (instr_is_comptime(target)) {
        return ir_implicit_cast(ira, target, dest_type);
    }

    if (dest_type->id == ZigTypeIdComptimeInt) {
        ir_add_error(ira, instruction->target, buf_sprintf("attempt to cast runtime value to '%s'",
                buf_ptr(&dest_type->name)));
        return ira->codegen->invalid_instruction;
    }

    return ir_analyze_widen_or_shorten(ira, &instruction->base, target, dest_type);
}

static IrInstruction *ir_analyze_instruction_float_cast(IrAnalyze *ira, IrInstructionFloatCast *instruction) {
    ZigType *dest_type = ir_resolve_type(ira, instruction->dest_type->child);
    if (type_is_invalid(dest_type))
        return ira->codegen->invalid_instruction;

    if (dest_type->id != ZigTypeIdFloat) {
        ir_add_error(ira, instruction->dest_type,
                buf_sprintf("expected float type, found '%s'", buf_ptr(&dest_type->name)));
        return ira->codegen->invalid_instruction;
    }

    IrInstruction *target = instruction->target->child;
    if (type_is_invalid(target->value.type))
        return ira->codegen->invalid_instruction;

    if (target->value.type->id == ZigTypeIdComptimeInt ||
        target->value.type->id == ZigTypeIdComptimeFloat)
    {
        if (ir_num_lit_fits_in_other_type(ira, target, dest_type, true)) {
            CastOp op;
            if (target->value.type->id == ZigTypeIdComptimeInt) {
                op = CastOpIntToFloat;
            } else {
                op = CastOpNumLitToConcrete;
            }
            return ir_resolve_cast(ira, &instruction->base, target, dest_type, op, false);
        } else {
            return ira->codegen->invalid_instruction;
        }
    }

    if (target->value.type->id != ZigTypeIdFloat) {
        ir_add_error(ira, instruction->target, buf_sprintf("expected float type, found '%s'",
                    buf_ptr(&target->value.type->name)));
        return ira->codegen->invalid_instruction;
    }

    return ir_analyze_widen_or_shorten(ira, &instruction->base, target, dest_type);
}

static IrInstruction *ir_analyze_instruction_err_set_cast(IrAnalyze *ira, IrInstructionErrSetCast *instruction) {
    ZigType *dest_type = ir_resolve_type(ira, instruction->dest_type->child);
    if (type_is_invalid(dest_type))
        return ira->codegen->invalid_instruction;

    if (dest_type->id != ZigTypeIdErrorSet) {
        ir_add_error(ira, instruction->dest_type,
                buf_sprintf("expected error set type, found '%s'", buf_ptr(&dest_type->name)));
        return ira->codegen->invalid_instruction;
    }

    IrInstruction *target = instruction->target->child;
    if (type_is_invalid(target->value.type))
        return ira->codegen->invalid_instruction;

    if (target->value.type->id != ZigTypeIdErrorSet) {
        ir_add_error(ira, instruction->target,
                buf_sprintf("expected error set type, found '%s'", buf_ptr(&target->value.type->name)));
        return ira->codegen->invalid_instruction;
    }

    return ir_analyze_err_set_cast(ira, &instruction->base, target, dest_type);
}

static IrInstruction *ir_analyze_instruction_from_bytes(IrAnalyze *ira, IrInstructionFromBytes *instruction) {
    Error err;

    ZigType *dest_child_type = ir_resolve_type(ira, instruction->dest_child_type->child);
    if (type_is_invalid(dest_child_type))
        return ira->codegen->invalid_instruction;

    IrInstruction *target = instruction->target->child;
    if (type_is_invalid(target->value.type))
        return ira->codegen->invalid_instruction;

    bool src_ptr_const;
    bool src_ptr_volatile;
    uint32_t src_ptr_align;
    if (target->value.type->id == ZigTypeIdPointer) {
        src_ptr_const = target->value.type->data.pointer.is_const;
        src_ptr_volatile = target->value.type->data.pointer.is_volatile;

        if ((err = resolve_ptr_align(ira, target->value.type, &src_ptr_align)))
            return ira->codegen->invalid_instruction;
    } else if (is_slice(target->value.type)) {
        ZigType *src_ptr_type = target->value.type->data.structure.fields[slice_ptr_index].type_entry;
        src_ptr_const = src_ptr_type->data.pointer.is_const;
        src_ptr_volatile = src_ptr_type->data.pointer.is_volatile;

        if ((err = resolve_ptr_align(ira, src_ptr_type, &src_ptr_align)))
            return ira->codegen->invalid_instruction;
    } else {
        src_ptr_const = true;
        src_ptr_volatile = false;

        if ((err = type_resolve(ira->codegen, target->value.type, ResolveStatusAlignmentKnown)))
            return ira->codegen->invalid_instruction;

        src_ptr_align = get_abi_alignment(ira->codegen, target->value.type);
    }

    if ((err = type_resolve(ira->codegen, dest_child_type, ResolveStatusSizeKnown)))
        return ira->codegen->invalid_instruction;

    ZigType *dest_ptr_type = get_pointer_to_type_extra(ira->codegen, dest_child_type,
            src_ptr_const, src_ptr_volatile, PtrLenUnknown,
            src_ptr_align, 0, 0);
    ZigType *dest_slice_type = get_slice_type(ira->codegen, dest_ptr_type);

    ZigType *u8_ptr = get_pointer_to_type_extra(ira->codegen, ira->codegen->builtin_types.entry_u8,
            src_ptr_const, src_ptr_volatile, PtrLenUnknown,
            src_ptr_align, 0, 0);
    ZigType *u8_slice = get_slice_type(ira->codegen, u8_ptr);

    IrInstruction *casted_value = ir_implicit_cast(ira, target, u8_slice);
    if (type_is_invalid(casted_value->value.type))
        return ira->codegen->invalid_instruction;

    bool have_known_len = false;
    uint64_t known_len;

    if (instr_is_comptime(casted_value)) {
        ConstExprValue *val = ir_resolve_const(ira, casted_value, UndefBad);
        if (!val)
            return ira->codegen->invalid_instruction;

        ConstExprValue *len_val = &val->data.x_struct.fields[slice_len_index];
        if (value_is_comptime(len_val)) {
            known_len = bigint_as_unsigned(&len_val->data.x_bigint);
            have_known_len = true;
        }
    }

    if (casted_value->value.data.rh_slice.id == RuntimeHintSliceIdLen) {
        known_len = casted_value->value.data.rh_slice.len;
        have_known_len = true;
    }

    if (have_known_len) {
        uint64_t child_type_size = type_size(ira->codegen, dest_child_type);
        uint64_t remainder = known_len % child_type_size;
        if (remainder != 0) {
            ErrorMsg *msg = ir_add_error(ira, &instruction->base,
                    buf_sprintf("unable to convert [%" ZIG_PRI_u64 "]u8 to %s: size mismatch",
                        known_len, buf_ptr(&dest_slice_type->name)));
            add_error_note(ira->codegen, msg, instruction->dest_child_type->source_node,
                buf_sprintf("%s has size %" ZIG_PRI_u64 "; remaining bytes: %" ZIG_PRI_u64,
                buf_ptr(&dest_child_type->name), child_type_size, remainder));
            return ira->codegen->invalid_instruction;
        }
    }

    IrInstruction *result = ir_build_resize_slice(ira, &instruction->base, casted_value, dest_slice_type);
    ir_add_alloca(ira, result, dest_slice_type);
    return result;
}

static IrInstruction *ir_analyze_instruction_to_bytes(IrAnalyze *ira, IrInstructionToBytes *instruction) {
    Error err;

    IrInstruction *target = instruction->target->child;
    if (type_is_invalid(target->value.type))
        return ira->codegen->invalid_instruction;

    if (!is_slice(target->value.type)) {
        ir_add_error(ira, instruction->target,
                buf_sprintf("expected slice, found '%s'", buf_ptr(&target->value.type->name)));
        return ira->codegen->invalid_instruction;
    }

    ZigType *src_ptr_type = target->value.type->data.structure.fields[slice_ptr_index].type_entry;

    uint32_t alignment;
    if ((err = resolve_ptr_align(ira, src_ptr_type, &alignment)))
        return ira->codegen->invalid_instruction;

    ZigType *dest_ptr_type = get_pointer_to_type_extra(ira->codegen, ira->codegen->builtin_types.entry_u8,
            src_ptr_type->data.pointer.is_const, src_ptr_type->data.pointer.is_volatile, PtrLenUnknown,
            alignment, 0, 0);
    ZigType *dest_slice_type = get_slice_type(ira->codegen, dest_ptr_type);

    if (instr_is_comptime(target)) {
        ConstExprValue *target_val = ir_resolve_const(ira, target, UndefBad);
        if (target_val == nullptr)
            return ira->codegen->invalid_instruction;

        IrInstruction *result = ir_const(ira, &instruction->base, dest_slice_type);
        result->value.data.x_struct.fields = create_const_vals(2);

        ConstExprValue *ptr_val = &result->value.data.x_struct.fields[slice_ptr_index];
        ConstExprValue *target_ptr_val = &target_val->data.x_struct.fields[slice_ptr_index];
        copy_const_val(ptr_val, target_ptr_val, false);
        ptr_val->type = dest_ptr_type;

        ConstExprValue *len_val = &result->value.data.x_struct.fields[slice_len_index];
        len_val->special = ConstValSpecialStatic;
        len_val->type = ira->codegen->builtin_types.entry_usize;
        ConstExprValue *target_len_val = &target_val->data.x_struct.fields[slice_len_index];
        ZigType *elem_type = src_ptr_type->data.pointer.child_type;
        BigInt elem_size_bigint;
        bigint_init_unsigned(&elem_size_bigint, type_size(ira->codegen, elem_type));
        bigint_mul(&len_val->data.x_bigint, &target_len_val->data.x_bigint, &elem_size_bigint);

        return result;
    }

    IrInstruction *result = ir_build_resize_slice(ira, &instruction->base, target, dest_slice_type);
    ir_add_alloca(ira, result, dest_slice_type);
    return result;
}

static Error resolve_ptr_align(IrAnalyze *ira, ZigType *ty, uint32_t *result_align) {
    Error err;

    ZigType *ptr_type = get_src_ptr_type(ty);
    assert(ptr_type != nullptr);
    if (ptr_type->id == ZigTypeIdPointer) {
        if ((err = type_resolve(ira->codegen, ptr_type->data.pointer.child_type, ResolveStatusAlignmentKnown)))
            return err;
    }

    *result_align = get_ptr_align(ira->codegen, ty);
    return ErrorNone;
}

static IrInstruction *ir_analyze_instruction_int_to_float(IrAnalyze *ira, IrInstructionIntToFloat *instruction) {
    ZigType *dest_type = ir_resolve_type(ira, instruction->dest_type->child);
    if (type_is_invalid(dest_type))
        return ira->codegen->invalid_instruction;

    IrInstruction *target = instruction->target->child;
    if (type_is_invalid(target->value.type))
        return ira->codegen->invalid_instruction;

    if (target->value.type->id != ZigTypeIdInt && target->value.type->id != ZigTypeIdComptimeInt) {
        ir_add_error(ira, instruction->target, buf_sprintf("expected int type, found '%s'",
                    buf_ptr(&target->value.type->name)));
        return ira->codegen->invalid_instruction;
    }

    return ir_resolve_cast(ira, &instruction->base, target, dest_type, CastOpIntToFloat, false);
}

static IrInstruction *ir_analyze_instruction_float_to_int(IrAnalyze *ira, IrInstructionFloatToInt *instruction) {
    ZigType *dest_type = ir_resolve_type(ira, instruction->dest_type->child);
    if (type_is_invalid(dest_type))
        return ira->codegen->invalid_instruction;

    IrInstruction *target = instruction->target->child;
    if (type_is_invalid(target->value.type))
        return ira->codegen->invalid_instruction;

    if (target->value.type->id == ZigTypeIdComptimeInt) {
        return ir_implicit_cast(ira, target, dest_type);
    }

    if (target->value.type->id != ZigTypeIdFloat && target->value.type->id != ZigTypeIdComptimeFloat) {
        ir_add_error(ira, instruction->target, buf_sprintf("expected float type, found '%s'",
                    buf_ptr(&target->value.type->name)));
        return ira->codegen->invalid_instruction;
    }

    return ir_resolve_cast(ira, &instruction->base, target, dest_type, CastOpFloatToInt, false);
}

static IrInstruction *ir_analyze_instruction_err_to_int(IrAnalyze *ira, IrInstructionErrToInt *instruction) {
    IrInstruction *target = instruction->target->child;
    if (type_is_invalid(target->value.type))
        return ira->codegen->invalid_instruction;

    IrInstruction *casted_target;
    if (target->value.type->id == ZigTypeIdErrorSet) {
        casted_target = target;
    } else {
        casted_target = ir_implicit_cast(ira, target, ira->codegen->builtin_types.entry_global_error_set);
        if (type_is_invalid(casted_target->value.type))
            return ira->codegen->invalid_instruction;
    }

    return ir_analyze_err_to_int(ira, &instruction->base, casted_target, ira->codegen->err_tag_type);
}

static IrInstruction *ir_analyze_instruction_int_to_err(IrAnalyze *ira, IrInstructionIntToErr *instruction) {
    IrInstruction *target = instruction->target->child;
    if (type_is_invalid(target->value.type))
        return ira->codegen->invalid_instruction;

    IrInstruction *casted_target = ir_implicit_cast(ira, target, ira->codegen->err_tag_type);
    if (type_is_invalid(casted_target->value.type))
        return ira->codegen->invalid_instruction;

    return ir_analyze_int_to_err(ira, &instruction->base, casted_target, ira->codegen->builtin_types.entry_global_error_set);
}

static IrInstruction *ir_analyze_instruction_bool_to_int(IrAnalyze *ira, IrInstructionBoolToInt *instruction) {
    IrInstruction *target = instruction->target->child;
    if (type_is_invalid(target->value.type))
        return ira->codegen->invalid_instruction;

    if (target->value.type->id != ZigTypeIdBool) {
        ir_add_error(ira, instruction->target, buf_sprintf("expected bool, found '%s'",
                    buf_ptr(&target->value.type->name)));
        return ira->codegen->invalid_instruction;
    }

    if (instr_is_comptime(target)) {
        bool is_true;
        if (!ir_resolve_bool(ira, target, &is_true))
            return ira->codegen->invalid_instruction;

        return ir_const_unsigned(ira, &instruction->base, is_true ? 1 : 0);
    }

    ZigType *u1_type = get_int_type(ira->codegen, false, 1);
    return ir_resolve_cast(ira, &instruction->base, target, u1_type, CastOpBoolToInt, false);
}

static IrInstruction *ir_analyze_instruction_int_type(IrAnalyze *ira, IrInstructionIntType *instruction) {
    IrInstruction *is_signed_value = instruction->is_signed->child;
    bool is_signed;
    if (!ir_resolve_bool(ira, is_signed_value, &is_signed))
        return ira->codegen->invalid_instruction;

    IrInstruction *bit_count_value = instruction->bit_count->child;
    uint64_t bit_count;
    if (!ir_resolve_unsigned(ira, bit_count_value, ira->codegen->builtin_types.entry_u16, &bit_count))
        return ira->codegen->invalid_instruction;

    return ir_const_type(ira, &instruction->base, get_int_type(ira->codegen, is_signed, (uint32_t)bit_count));
}

static IrInstruction *ir_analyze_instruction_vector_type(IrAnalyze *ira, IrInstructionVectorType *instruction) {
    uint64_t len;
    if (!ir_resolve_unsigned(ira, instruction->len->child, ira->codegen->builtin_types.entry_u32, &len))
        return ira->codegen->invalid_instruction;

    ZigType *elem_type = ir_resolve_type(ira, instruction->elem_type->child);
    if (type_is_invalid(elem_type))
        return ira->codegen->invalid_instruction;

    if (!is_valid_vector_elem_type(elem_type)) {
        ir_add_error(ira, instruction->elem_type,
            buf_sprintf("vector element type must be integer, float, or pointer; '%s' is invalid",
                buf_ptr(&elem_type->name)));
        return ira->codegen->invalid_instruction;
    }

    ZigType *vector_type = get_vector_type(ira->codegen, len, elem_type);

    return ir_const_type(ira, &instruction->base, vector_type);
}

static IrInstruction *ir_analyze_instruction_bool_not(IrAnalyze *ira, IrInstructionBoolNot *instruction) {
    IrInstruction *value = instruction->value->child;
    if (type_is_invalid(value->value.type))
        return ira->codegen->invalid_instruction;

    ZigType *bool_type = ira->codegen->builtin_types.entry_bool;

    IrInstruction *casted_value = ir_implicit_cast(ira, value, bool_type);
    if (type_is_invalid(casted_value->value.type))
        return ira->codegen->invalid_instruction;

    if (instr_is_comptime(casted_value)) {
        ConstExprValue *value = ir_resolve_const(ira, casted_value, UndefBad);
        if (value == nullptr)
            return ira->codegen->invalid_instruction;

        return ir_const_bool(ira, &instruction->base, !value->data.x_bool);
    }

    IrInstruction *result = ir_build_bool_not(&ira->new_irb, instruction->base.scope,
            instruction->base.source_node, casted_value);
    result->value.type = bool_type;
    return result;
}

static IrInstruction *ir_analyze_instruction_memset(IrAnalyze *ira, IrInstructionMemset *instruction) {
    Error err;

    IrInstruction *dest_ptr = instruction->dest_ptr->child;
    if (type_is_invalid(dest_ptr->value.type))
        return ira->codegen->invalid_instruction;

    IrInstruction *byte_value = instruction->byte->child;
    if (type_is_invalid(byte_value->value.type))
        return ira->codegen->invalid_instruction;

    IrInstruction *count_value = instruction->count->child;
    if (type_is_invalid(count_value->value.type))
        return ira->codegen->invalid_instruction;

    ZigType *dest_uncasted_type = dest_ptr->value.type;
    bool dest_is_volatile = (dest_uncasted_type->id == ZigTypeIdPointer) &&
        dest_uncasted_type->data.pointer.is_volatile;

    ZigType *usize = ira->codegen->builtin_types.entry_usize;
    ZigType *u8 = ira->codegen->builtin_types.entry_u8;
    uint32_t dest_align;
    if (dest_uncasted_type->id == ZigTypeIdPointer) {
        if ((err = resolve_ptr_align(ira, dest_uncasted_type, &dest_align)))
            return ira->codegen->invalid_instruction;
    } else {
        dest_align = get_abi_alignment(ira->codegen, u8);
    }
    ZigType *u8_ptr = get_pointer_to_type_extra(ira->codegen, u8, false, dest_is_volatile,
            PtrLenUnknown, dest_align, 0, 0);

    IrInstruction *casted_dest_ptr = ir_implicit_cast(ira, dest_ptr, u8_ptr);
    if (type_is_invalid(casted_dest_ptr->value.type))
        return ira->codegen->invalid_instruction;

    IrInstruction *casted_byte = ir_implicit_cast(ira, byte_value, u8);
    if (type_is_invalid(casted_byte->value.type))
        return ira->codegen->invalid_instruction;

    IrInstruction *casted_count = ir_implicit_cast(ira, count_value, usize);
    if (type_is_invalid(casted_count->value.type))
        return ira->codegen->invalid_instruction;

    // TODO test this at comptime with u8 and non-u8 types
    if (casted_dest_ptr->value.special == ConstValSpecialStatic &&
        casted_byte->value.special == ConstValSpecialStatic &&
        casted_count->value.special == ConstValSpecialStatic &&
        casted_dest_ptr->value.data.x_ptr.special != ConstPtrSpecialHardCodedAddr &&
        casted_dest_ptr->value.data.x_ptr.mut != ConstPtrMutRuntimeVar)
    {
        ConstExprValue *dest_ptr_val = &casted_dest_ptr->value;

        ConstExprValue *dest_elements;
        size_t start;
        size_t bound_end;
        switch (dest_ptr_val->data.x_ptr.special) {
            case ConstPtrSpecialInvalid:
            case ConstPtrSpecialDiscard:
                zig_unreachable();
            case ConstPtrSpecialRef:
                dest_elements = dest_ptr_val->data.x_ptr.data.ref.pointee;
                start = 0;
                bound_end = 1;
                break;
            case ConstPtrSpecialBaseArray:
                {
                    ConstExprValue *array_val = dest_ptr_val->data.x_ptr.data.base_array.array_val;
                    expand_undef_array(ira->codegen, array_val);
                    dest_elements = array_val->data.x_array.data.s_none.elements;
                    start = dest_ptr_val->data.x_ptr.data.base_array.elem_index;
                    bound_end = array_val->type->data.array.len;
                    break;
                }
            case ConstPtrSpecialBaseStruct:
                zig_panic("TODO memset on const inner struct");
            case ConstPtrSpecialBaseErrorUnionCode:
                zig_panic("TODO memset on const inner error union code");
            case ConstPtrSpecialBaseErrorUnionPayload:
                zig_panic("TODO memset on const inner error union payload");
            case ConstPtrSpecialBaseOptionalPayload:
                zig_panic("TODO memset on const inner optional payload");
            case ConstPtrSpecialHardCodedAddr:
                zig_unreachable();
            case ConstPtrSpecialFunction:
                zig_panic("TODO memset on ptr cast from function");
            case ConstPtrSpecialNull:
                zig_panic("TODO memset on null ptr");
        }

        size_t count = bigint_as_unsigned(&casted_count->value.data.x_bigint);
        size_t end = start + count;
        if (end > bound_end) {
            ir_add_error(ira, count_value, buf_sprintf("out of bounds pointer access"));
            return ira->codegen->invalid_instruction;
        }

        ConstExprValue *byte_val = &casted_byte->value;
        for (size_t i = start; i < end; i += 1) {
            dest_elements[i] = *byte_val;
        }

        return ir_const_void(ira, &instruction->base);
    }

    IrInstruction *result = ir_build_memset(&ira->new_irb, instruction->base.scope, instruction->base.source_node,
            casted_dest_ptr, casted_byte, casted_count);
    result->value.type = ira->codegen->builtin_types.entry_void;
    return result;
}

static IrInstruction *ir_analyze_instruction_memcpy(IrAnalyze *ira, IrInstructionMemcpy *instruction) {
    Error err;

    IrInstruction *dest_ptr = instruction->dest_ptr->child;
    if (type_is_invalid(dest_ptr->value.type))
        return ira->codegen->invalid_instruction;

    IrInstruction *src_ptr = instruction->src_ptr->child;
    if (type_is_invalid(src_ptr->value.type))
        return ira->codegen->invalid_instruction;

    IrInstruction *count_value = instruction->count->child;
    if (type_is_invalid(count_value->value.type))
        return ira->codegen->invalid_instruction;

    ZigType *u8 = ira->codegen->builtin_types.entry_u8;
    ZigType *dest_uncasted_type = dest_ptr->value.type;
    ZigType *src_uncasted_type = src_ptr->value.type;
    bool dest_is_volatile = (dest_uncasted_type->id == ZigTypeIdPointer) &&
        dest_uncasted_type->data.pointer.is_volatile;
    bool src_is_volatile = (src_uncasted_type->id == ZigTypeIdPointer) &&
        src_uncasted_type->data.pointer.is_volatile;

    uint32_t dest_align;
    if (dest_uncasted_type->id == ZigTypeIdPointer) {
        if ((err = resolve_ptr_align(ira, dest_uncasted_type, &dest_align)))
            return ira->codegen->invalid_instruction;
    } else {
        dest_align = get_abi_alignment(ira->codegen, u8);
    }

    uint32_t src_align;
    if (src_uncasted_type->id == ZigTypeIdPointer) {
        if ((err = resolve_ptr_align(ira, src_uncasted_type, &src_align)))
            return ira->codegen->invalid_instruction;
    } else {
        src_align = get_abi_alignment(ira->codegen, u8);
    }

    ZigType *usize = ira->codegen->builtin_types.entry_usize;
    ZigType *u8_ptr_mut = get_pointer_to_type_extra(ira->codegen, u8, false, dest_is_volatile,
            PtrLenUnknown, dest_align, 0, 0);
    ZigType *u8_ptr_const = get_pointer_to_type_extra(ira->codegen, u8, true, src_is_volatile,
            PtrLenUnknown, src_align, 0, 0);

    IrInstruction *casted_dest_ptr = ir_implicit_cast(ira, dest_ptr, u8_ptr_mut);
    if (type_is_invalid(casted_dest_ptr->value.type))
        return ira->codegen->invalid_instruction;

    IrInstruction *casted_src_ptr = ir_implicit_cast(ira, src_ptr, u8_ptr_const);
    if (type_is_invalid(casted_src_ptr->value.type))
        return ira->codegen->invalid_instruction;

    IrInstruction *casted_count = ir_implicit_cast(ira, count_value, usize);
    if (type_is_invalid(casted_count->value.type))
        return ira->codegen->invalid_instruction;

    // TODO test this at comptime with u8 and non-u8 types
    // TODO test with dest ptr being a global runtime variable 
    if (casted_dest_ptr->value.special == ConstValSpecialStatic &&
        casted_src_ptr->value.special == ConstValSpecialStatic &&
        casted_count->value.special == ConstValSpecialStatic &&
        casted_dest_ptr->value.data.x_ptr.special != ConstPtrSpecialHardCodedAddr)
    {
        size_t count = bigint_as_unsigned(&casted_count->value.data.x_bigint);

        ConstExprValue *dest_ptr_val = &casted_dest_ptr->value;
        ConstExprValue *dest_elements;
        size_t dest_start;
        size_t dest_end;
        switch (dest_ptr_val->data.x_ptr.special) {
            case ConstPtrSpecialInvalid:
            case ConstPtrSpecialDiscard:
                zig_unreachable();
            case ConstPtrSpecialRef:
                dest_elements = dest_ptr_val->data.x_ptr.data.ref.pointee;
                dest_start = 0;
                dest_end = 1;
                break;
            case ConstPtrSpecialBaseArray:
                {
                    ConstExprValue *array_val = dest_ptr_val->data.x_ptr.data.base_array.array_val;
                    expand_undef_array(ira->codegen, array_val);
                    dest_elements = array_val->data.x_array.data.s_none.elements;
                    dest_start = dest_ptr_val->data.x_ptr.data.base_array.elem_index;
                    dest_end = array_val->type->data.array.len;
                    break;
                }
            case ConstPtrSpecialBaseStruct:
                zig_panic("TODO memcpy on const inner struct");
            case ConstPtrSpecialBaseErrorUnionCode:
                zig_panic("TODO memcpy on const inner error union code");
            case ConstPtrSpecialBaseErrorUnionPayload:
                zig_panic("TODO memcpy on const inner error union payload");
            case ConstPtrSpecialBaseOptionalPayload:
                zig_panic("TODO memcpy on const inner optional payload");
            case ConstPtrSpecialHardCodedAddr:
                zig_unreachable();
            case ConstPtrSpecialFunction:
                zig_panic("TODO memcpy on ptr cast from function");
            case ConstPtrSpecialNull:
                zig_panic("TODO memcpy on null ptr");
        }

        if (dest_start + count > dest_end) {
            ir_add_error(ira, &instruction->base, buf_sprintf("out of bounds pointer access"));
            return ira->codegen->invalid_instruction;
        }

        ConstExprValue *src_ptr_val = &casted_src_ptr->value;
        ConstExprValue *src_elements;
        size_t src_start;
        size_t src_end;

        switch (src_ptr_val->data.x_ptr.special) {
            case ConstPtrSpecialInvalid:
            case ConstPtrSpecialDiscard:
                zig_unreachable();
            case ConstPtrSpecialRef:
                src_elements = src_ptr_val->data.x_ptr.data.ref.pointee;
                src_start = 0;
                src_end = 1;
                break;
            case ConstPtrSpecialBaseArray:
                {
                    ConstExprValue *array_val = src_ptr_val->data.x_ptr.data.base_array.array_val;
                    expand_undef_array(ira->codegen, array_val);
                    src_elements = array_val->data.x_array.data.s_none.elements;
                    src_start = src_ptr_val->data.x_ptr.data.base_array.elem_index;
                    src_end = array_val->type->data.array.len;
                    break;
                }
            case ConstPtrSpecialBaseStruct:
                zig_panic("TODO memcpy on const inner struct");
            case ConstPtrSpecialBaseErrorUnionCode:
                zig_panic("TODO memcpy on const inner error union code");
            case ConstPtrSpecialBaseErrorUnionPayload:
                zig_panic("TODO memcpy on const inner error union payload");
            case ConstPtrSpecialBaseOptionalPayload:
                zig_panic("TODO memcpy on const inner optional payload");
            case ConstPtrSpecialHardCodedAddr:
                zig_unreachable();
            case ConstPtrSpecialFunction:
                zig_panic("TODO memcpy on ptr cast from function");
            case ConstPtrSpecialNull:
                zig_panic("TODO memcpy on null ptr");
        }

        if (src_start + count > src_end) {
            ir_add_error(ira, &instruction->base, buf_sprintf("out of bounds pointer access"));
            return ira->codegen->invalid_instruction;
        }

        // TODO check for noalias violations - this should be generalized to work for any function

        for (size_t i = 0; i < count; i += 1) {
            dest_elements[dest_start + i] = src_elements[src_start + i];
        }

        return ir_const_void(ira, &instruction->base);
    }

    IrInstruction *result = ir_build_memcpy(&ira->new_irb, instruction->base.scope, instruction->base.source_node,
            casted_dest_ptr, casted_src_ptr, casted_count);
    result->value.type = ira->codegen->builtin_types.entry_void;
    return result;
}

static IrInstruction *ir_analyze_instruction_slice(IrAnalyze *ira, IrInstructionSlice *instruction) {
    IrInstruction *ptr_ptr = instruction->ptr->child;
    if (type_is_invalid(ptr_ptr->value.type))
        return ira->codegen->invalid_instruction;

    ZigType *ptr_ptr_type = ptr_ptr->value.type;
    assert(ptr_ptr_type->id == ZigTypeIdPointer);
    ZigType *array_type = ptr_ptr_type->data.pointer.child_type;

    IrInstruction *start = instruction->start->child;
    if (type_is_invalid(start->value.type))
        return ira->codegen->invalid_instruction;

    ZigType *usize = ira->codegen->builtin_types.entry_usize;
    IrInstruction *casted_start = ir_implicit_cast(ira, start, usize);
    if (type_is_invalid(casted_start->value.type))
        return ira->codegen->invalid_instruction;

    IrInstruction *end;
    if (instruction->end) {
        end = instruction->end->child;
        if (type_is_invalid(end->value.type))
            return ira->codegen->invalid_instruction;
        end = ir_implicit_cast(ira, end, usize);
        if (type_is_invalid(end->value.type))
            return ira->codegen->invalid_instruction;
    } else {
        end = nullptr;
    }

    ZigType *return_type;

    if (array_type->id == ZigTypeIdArray) {
        bool is_comptime_const = ptr_ptr->value.special == ConstValSpecialStatic &&
            ptr_ptr->value.data.x_ptr.mut == ConstPtrMutComptimeConst;
        ZigType *slice_ptr_type = get_pointer_to_type_extra(ira->codegen, array_type->data.array.child_type,
            ptr_ptr_type->data.pointer.is_const || is_comptime_const,
            ptr_ptr_type->data.pointer.is_volatile,
            PtrLenUnknown,
            ptr_ptr_type->data.pointer.explicit_alignment, 0, 0);
        return_type = get_slice_type(ira->codegen, slice_ptr_type);
    } else if (array_type->id == ZigTypeIdPointer) {
        if (array_type->data.pointer.ptr_len == PtrLenSingle) {
            ZigType *main_type = array_type->data.pointer.child_type;
            if (main_type->id == ZigTypeIdArray) {
                ZigType *slice_ptr_type = get_pointer_to_type_extra(ira->codegen,
                        main_type->data.pointer.child_type,
                        array_type->data.pointer.is_const, array_type->data.pointer.is_volatile,
                        PtrLenUnknown,
                        array_type->data.pointer.explicit_alignment, 0, 0);
                return_type = get_slice_type(ira->codegen, slice_ptr_type);
            } else {
                ir_add_error(ira, &instruction->base, buf_sprintf("slice of single-item pointer"));
                return ira->codegen->invalid_instruction;
            }
        } else {
            return_type = get_slice_type(ira->codegen, array_type);
            if (!end) {
                ir_add_error(ira, &instruction->base, buf_sprintf("slice of pointer must include end value"));
                return ira->codegen->invalid_instruction;
            }
        }
    } else if (is_slice(array_type)) {
        ZigType *ptr_type = array_type->data.structure.fields[slice_ptr_index].type_entry;
        return_type = get_slice_type(ira->codegen, ptr_type);
    } else {
        ir_add_error(ira, &instruction->base,
            buf_sprintf("slice of non-array type '%s'", buf_ptr(&array_type->name)));
        return ira->codegen->invalid_instruction;
    }

    if (instr_is_comptime(ptr_ptr) &&
        value_is_comptime(&casted_start->value) &&
        (!end || value_is_comptime(&end->value)))
    {
        ConstExprValue *array_val;
        ConstExprValue *parent_ptr;
        size_t abs_offset;
        size_t rel_end;
        bool ptr_is_undef = false;
        if (array_type->id == ZigTypeIdArray ||
            (array_type->id == ZigTypeIdPointer && array_type->data.pointer.ptr_len == PtrLenSingle))
        {
            if (array_type->id == ZigTypeIdPointer) {
                ZigType *child_array_type = array_type->data.pointer.child_type;
                assert(child_array_type->id == ZigTypeIdArray);
                parent_ptr = const_ptr_pointee(ira, ira->codegen, &ptr_ptr->value, instruction->base.source_node);
                if (parent_ptr == nullptr)
                    return ira->codegen->invalid_instruction;

                array_val = const_ptr_pointee(ira, ira->codegen, parent_ptr, instruction->base.source_node);
                if (array_val == nullptr)
                    return ira->codegen->invalid_instruction;

                rel_end = child_array_type->data.array.len;
                abs_offset = 0;
            } else {
                array_val = const_ptr_pointee(ira, ira->codegen, &ptr_ptr->value, instruction->base.source_node);
                if (array_val == nullptr)
                    return ira->codegen->invalid_instruction;
                rel_end = array_type->data.array.len;
                parent_ptr = nullptr;
                abs_offset = 0;
            }
        } else if (array_type->id == ZigTypeIdPointer) {
            assert(array_type->data.pointer.ptr_len == PtrLenUnknown);
            parent_ptr = const_ptr_pointee(ira, ira->codegen, &ptr_ptr->value, instruction->base.source_node);
            if (parent_ptr == nullptr)
                return ira->codegen->invalid_instruction;

            if (parent_ptr->special == ConstValSpecialUndef) {
                array_val = nullptr;
                abs_offset = 0;
                rel_end = SIZE_MAX;
                ptr_is_undef = true;
            } else switch (parent_ptr->data.x_ptr.special) {
                case ConstPtrSpecialInvalid:
                case ConstPtrSpecialDiscard:
                    zig_unreachable();
                case ConstPtrSpecialRef:
                    if (parent_ptr->data.x_ptr.data.ref.pointee->type->id == ZigTypeIdArray) {
                        array_val = parent_ptr->data.x_ptr.data.ref.pointee;
                        abs_offset = 0;
                        rel_end = array_val->type->data.array.len;
                    } else {
                        array_val = nullptr;
                        abs_offset = SIZE_MAX;
                        rel_end = 1;
                    }
                    break;
                case ConstPtrSpecialBaseArray:
                    array_val = parent_ptr->data.x_ptr.data.base_array.array_val;
                    abs_offset = parent_ptr->data.x_ptr.data.base_array.elem_index;
                    rel_end = array_val->type->data.array.len - abs_offset;
                    break;
                case ConstPtrSpecialBaseStruct:
                    zig_panic("TODO slice const inner struct");
                case ConstPtrSpecialBaseErrorUnionCode:
                    zig_panic("TODO slice const inner error union code");
                case ConstPtrSpecialBaseErrorUnionPayload:
                    zig_panic("TODO slice const inner error union payload");
                case ConstPtrSpecialBaseOptionalPayload:
                    zig_panic("TODO slice const inner optional payload");
                case ConstPtrSpecialHardCodedAddr:
                    array_val = nullptr;
                    abs_offset = 0;
                    rel_end = SIZE_MAX;
                    break;
                case ConstPtrSpecialFunction:
                    zig_panic("TODO slice of ptr cast from function");
                case ConstPtrSpecialNull:
                    zig_panic("TODO slice of null ptr");
            }
        } else if (is_slice(array_type)) {
            ConstExprValue *slice_ptr = const_ptr_pointee(ira, ira->codegen, &ptr_ptr->value, instruction->base.source_node);
            if (slice_ptr == nullptr)
                return ira->codegen->invalid_instruction;

            parent_ptr = &slice_ptr->data.x_struct.fields[slice_ptr_index];
            if (parent_ptr->special == ConstValSpecialUndef) {
                ir_add_error(ira, &instruction->base, buf_sprintf("slice of undefined"));
                return ira->codegen->invalid_instruction;
            }

            ConstExprValue *len_val = &slice_ptr->data.x_struct.fields[slice_len_index];

            switch (parent_ptr->data.x_ptr.special) {
                case ConstPtrSpecialInvalid:
                case ConstPtrSpecialDiscard:
                    zig_unreachable();
                case ConstPtrSpecialRef:
                    array_val = nullptr;
                    abs_offset = SIZE_MAX;
                    rel_end = 1;
                    break;
                case ConstPtrSpecialBaseArray:
                    array_val = parent_ptr->data.x_ptr.data.base_array.array_val;
                    abs_offset = parent_ptr->data.x_ptr.data.base_array.elem_index;
                    rel_end = bigint_as_unsigned(&len_val->data.x_bigint);
                    break;
                case ConstPtrSpecialBaseStruct:
                    zig_panic("TODO slice const inner struct");
                case ConstPtrSpecialBaseErrorUnionCode:
                    zig_panic("TODO slice const inner error union code");
                case ConstPtrSpecialBaseErrorUnionPayload:
                    zig_panic("TODO slice const inner error union payload");
                case ConstPtrSpecialBaseOptionalPayload:
                    zig_panic("TODO slice const inner optional payload");
                case ConstPtrSpecialHardCodedAddr:
                    array_val = nullptr;
                    abs_offset = 0;
                    rel_end = bigint_as_unsigned(&len_val->data.x_bigint);
                    break;
                case ConstPtrSpecialFunction:
                    zig_panic("TODO slice of slice cast from function");
                case ConstPtrSpecialNull:
                    zig_panic("TODO slice of null");
            }
        } else {
            zig_unreachable();
        }

        uint64_t start_scalar = bigint_as_unsigned(&casted_start->value.data.x_bigint);
        if (!ptr_is_undef && start_scalar > rel_end) {
            ir_add_error(ira, &instruction->base, buf_sprintf("out of bounds slice"));
            return ira->codegen->invalid_instruction;
        }

        uint64_t end_scalar;
        if (end) {
            end_scalar = bigint_as_unsigned(&end->value.data.x_bigint);
        } else {
            end_scalar = rel_end;
        }
        if (!ptr_is_undef) {
            if (end_scalar > rel_end) {
                ir_add_error(ira, &instruction->base, buf_sprintf("out of bounds slice"));
                return ira->codegen->invalid_instruction;
            }
            if (start_scalar > end_scalar) {
                ir_add_error(ira, &instruction->base, buf_sprintf("slice start is greater than end"));
                return ira->codegen->invalid_instruction;
            }
        }
        if (ptr_is_undef && start_scalar != end_scalar) {
            ir_add_error(ira, &instruction->base, buf_sprintf("non-zero length slice of undefined pointer"));
            return ira->codegen->invalid_instruction;
        }

        IrInstruction *result = ir_const(ira, &instruction->base, return_type);
        ConstExprValue *out_val = &result->value;
        out_val->data.x_struct.fields = create_const_vals(2);

        ConstExprValue *ptr_val = &out_val->data.x_struct.fields[slice_ptr_index];

        if (array_val) {
            size_t index = abs_offset + start_scalar;
            bool is_const = slice_is_const(return_type);
            init_const_ptr_array(ira->codegen, ptr_val, array_val, index, is_const, PtrLenUnknown);
            if (array_type->id == ZigTypeIdArray) {
                ptr_val->data.x_ptr.mut = ptr_ptr->value.data.x_ptr.mut;
            } else if (is_slice(array_type)) {
                ptr_val->data.x_ptr.mut = parent_ptr->data.x_ptr.mut;
            } else if (array_type->id == ZigTypeIdPointer) {
                ptr_val->data.x_ptr.mut = parent_ptr->data.x_ptr.mut;
            }
        } else if (ptr_is_undef) {
            ptr_val->type = get_pointer_to_type(ira->codegen, parent_ptr->type->data.pointer.child_type,
                    slice_is_const(return_type));
            ptr_val->special = ConstValSpecialUndef;
        } else switch (parent_ptr->data.x_ptr.special) {
            case ConstPtrSpecialInvalid:
            case ConstPtrSpecialDiscard:
                zig_unreachable();
            case ConstPtrSpecialRef:
                init_const_ptr_ref(ira->codegen, ptr_val,
                        parent_ptr->data.x_ptr.data.ref.pointee, slice_is_const(return_type));
                break;
            case ConstPtrSpecialBaseArray:
                zig_unreachable();
            case ConstPtrSpecialBaseStruct:
                zig_panic("TODO");
            case ConstPtrSpecialBaseErrorUnionCode:
                zig_panic("TODO");
            case ConstPtrSpecialBaseErrorUnionPayload:
                zig_panic("TODO");
            case ConstPtrSpecialBaseOptionalPayload:
                zig_panic("TODO");
            case ConstPtrSpecialHardCodedAddr:
                init_const_ptr_hard_coded_addr(ira->codegen, ptr_val,
                    parent_ptr->type->data.pointer.child_type,
                    parent_ptr->data.x_ptr.data.hard_coded_addr.addr + start_scalar,
                    slice_is_const(return_type));
                break;
            case ConstPtrSpecialFunction:
                zig_panic("TODO");
            case ConstPtrSpecialNull:
                zig_panic("TODO");
        }

        ConstExprValue *len_val = &out_val->data.x_struct.fields[slice_len_index];
        init_const_usize(ira->codegen, len_val, end_scalar - start_scalar);

        return result;
    }

    IrInstruction *new_instruction = ir_build_slice(&ira->new_irb,
            instruction->base.scope, instruction->base.source_node,
            ptr_ptr, casted_start, end, instruction->safety_check_on);
    new_instruction->value.type = return_type;
    ir_add_alloca(ira, new_instruction, return_type);
    return new_instruction;
}

static IrInstruction *ir_analyze_instruction_member_count(IrAnalyze *ira, IrInstructionMemberCount *instruction) {
    Error err;
    IrInstruction *container = instruction->container->child;
    if (type_is_invalid(container->value.type))
        return ira->codegen->invalid_instruction;
    ZigType *container_type = ir_resolve_type(ira, container);

    if ((err = ensure_complete_type(ira->codegen, container_type)))
        return ira->codegen->invalid_instruction;

    uint64_t result;
    if (type_is_invalid(container_type)) {
        return ira->codegen->invalid_instruction;
    } else if (container_type->id == ZigTypeIdEnum) {
        result = container_type->data.enumeration.src_field_count;
    } else if (container_type->id == ZigTypeIdStruct) {
        result = container_type->data.structure.src_field_count;
    } else if (container_type->id == ZigTypeIdUnion) {
        result = container_type->data.unionation.src_field_count;
    } else if (container_type->id == ZigTypeIdErrorSet) {
        if (!resolve_inferred_error_set(ira->codegen, container_type, instruction->base.source_node)) {
            return ira->codegen->invalid_instruction;
        }
        if (type_is_global_error_set(container_type)) {
            ir_add_error(ira, &instruction->base, buf_sprintf("global error set member count not available at comptime"));
            return ira->codegen->invalid_instruction;
        }
        result = container_type->data.error_set.err_count;
    } else {
        ir_add_error(ira, &instruction->base, buf_sprintf("no value count available for type '%s'", buf_ptr(&container_type->name)));
        return ira->codegen->invalid_instruction;
    }

    return ir_const_unsigned(ira, &instruction->base, result);
}

static IrInstruction *ir_analyze_instruction_member_type(IrAnalyze *ira, IrInstructionMemberType *instruction) {
    Error err;
    IrInstruction *container_type_value = instruction->container_type->child;
    ZigType *container_type = ir_resolve_type(ira, container_type_value);
    if (type_is_invalid(container_type))
        return ira->codegen->invalid_instruction;

    if ((err = ensure_complete_type(ira->codegen, container_type)))
        return ira->codegen->invalid_instruction;


    uint64_t member_index;
    IrInstruction *index_value = instruction->member_index->child;
    if (!ir_resolve_usize(ira, index_value, &member_index))
        return ira->codegen->invalid_instruction;

    if (container_type->id == ZigTypeIdStruct) {
        if (member_index >= container_type->data.structure.src_field_count) {
            ir_add_error(ira, index_value,
                buf_sprintf("member index %" ZIG_PRI_u64 " out of bounds; '%s' has %" PRIu32 " members",
                    member_index, buf_ptr(&container_type->name), container_type->data.structure.src_field_count));
            return ira->codegen->invalid_instruction;
        }
        TypeStructField *field = &container_type->data.structure.fields[member_index];

        return ir_const_type(ira, &instruction->base, field->type_entry);
    } else if (container_type->id == ZigTypeIdUnion) {
        if (member_index >= container_type->data.unionation.src_field_count) {
            ir_add_error(ira, index_value,
                buf_sprintf("member index %" ZIG_PRI_u64 " out of bounds; '%s' has %" PRIu32 " members",
                    member_index, buf_ptr(&container_type->name), container_type->data.unionation.src_field_count));
            return ira->codegen->invalid_instruction;
        }
        TypeUnionField *field = &container_type->data.unionation.fields[member_index];

        return ir_const_type(ira, &instruction->base, field->type_entry);
    } else {
        ir_add_error(ira, container_type_value,
            buf_sprintf("type '%s' does not support @memberType", buf_ptr(&container_type->name)));
        return ira->codegen->invalid_instruction;
    }
}

static IrInstruction *ir_analyze_instruction_member_name(IrAnalyze *ira, IrInstructionMemberName *instruction) {
    Error err;
    IrInstruction *container_type_value = instruction->container_type->child;
    ZigType *container_type = ir_resolve_type(ira, container_type_value);
    if (type_is_invalid(container_type))
        return ira->codegen->invalid_instruction;

    if ((err = ensure_complete_type(ira->codegen, container_type)))
        return ira->codegen->invalid_instruction;

    uint64_t member_index;
    IrInstruction *index_value = instruction->member_index->child;
    if (!ir_resolve_usize(ira, index_value, &member_index))
        return ira->codegen->invalid_instruction;

    if (container_type->id == ZigTypeIdStruct) {
        if (member_index >= container_type->data.structure.src_field_count) {
            ir_add_error(ira, index_value,
                buf_sprintf("member index %" ZIG_PRI_u64 " out of bounds; '%s' has %" PRIu32 " members",
                    member_index, buf_ptr(&container_type->name), container_type->data.structure.src_field_count));
            return ira->codegen->invalid_instruction;
        }
        TypeStructField *field = &container_type->data.structure.fields[member_index];

        IrInstruction *result = ir_const(ira, &instruction->base, nullptr);
        init_const_str_lit(ira->codegen, &result->value, field->name);
        return result;
    } else if (container_type->id == ZigTypeIdEnum) {
        if (member_index >= container_type->data.enumeration.src_field_count) {
            ir_add_error(ira, index_value,
                buf_sprintf("member index %" ZIG_PRI_u64 " out of bounds; '%s' has %" PRIu32 " members",
                    member_index, buf_ptr(&container_type->name), container_type->data.enumeration.src_field_count));
            return ira->codegen->invalid_instruction;
        }
        TypeEnumField *field = &container_type->data.enumeration.fields[member_index];

        IrInstruction *result = ir_const(ira, &instruction->base, nullptr);
        init_const_str_lit(ira->codegen, &result->value, field->name);
        return result;
    } else if (container_type->id == ZigTypeIdUnion) {
        if (member_index >= container_type->data.unionation.src_field_count) {
            ir_add_error(ira, index_value,
                buf_sprintf("member index %" ZIG_PRI_u64 " out of bounds; '%s' has %" PRIu32 " members",
                    member_index, buf_ptr(&container_type->name), container_type->data.unionation.src_field_count));
            return ira->codegen->invalid_instruction;
        }
        TypeUnionField *field = &container_type->data.unionation.fields[member_index];

        IrInstruction *result = ir_const(ira, &instruction->base, nullptr);
        init_const_str_lit(ira->codegen, &result->value, field->name);
        return result;
    } else {
        ir_add_error(ira, container_type_value,
            buf_sprintf("type '%s' does not support @memberName", buf_ptr(&container_type->name)));
        return ira->codegen->invalid_instruction;
    }
}

static IrInstruction *ir_analyze_instruction_breakpoint(IrAnalyze *ira, IrInstructionBreakpoint *instruction) {
    IrInstruction *result = ir_build_breakpoint(&ira->new_irb,
        instruction->base.scope, instruction->base.source_node);
    result->value.type = ira->codegen->builtin_types.entry_void;
    return result;
}

static IrInstruction *ir_analyze_instruction_return_address(IrAnalyze *ira, IrInstructionReturnAddress *instruction) {
    IrInstruction *result = ir_build_return_address(&ira->new_irb,
        instruction->base.scope, instruction->base.source_node);
    ZigType *u8 = ira->codegen->builtin_types.entry_u8;
    ZigType *u8_ptr_const = get_pointer_to_type(ira->codegen, u8, true);
    result->value.type = u8_ptr_const;
    return result;
}

static IrInstruction *ir_analyze_instruction_frame_address(IrAnalyze *ira, IrInstructionFrameAddress *instruction) {
    IrInstruction *result = ir_build_frame_address(&ira->new_irb,
            instruction->base.scope, instruction->base.source_node);
    ZigType *u8 = ira->codegen->builtin_types.entry_u8;
    ZigType *u8_ptr_const = get_pointer_to_type(ira->codegen, u8, true);
    result->value.type = u8_ptr_const;
    return result;
}

static IrInstruction *ir_analyze_instruction_handle(IrAnalyze *ira, IrInstructionHandle *instruction) {
    IrInstruction *result = ir_build_handle(&ira->new_irb, instruction->base.scope, instruction->base.source_node);
    ZigFn *fn_entry = exec_fn_entry(ira->new_irb.exec);
    assert(fn_entry != nullptr);
    result->value.type = get_promise_type(ira->codegen, fn_entry->type_entry->data.fn.fn_type_id.return_type);
    return result;
}

static IrInstruction *ir_analyze_instruction_align_of(IrAnalyze *ira, IrInstructionAlignOf *instruction) {
    Error err;
    IrInstruction *type_value = instruction->type_value->child;
    if (type_is_invalid(type_value->value.type))
        return ira->codegen->invalid_instruction;
    ZigType *type_entry = ir_resolve_type(ira, type_value);

    if ((err = type_resolve(ira->codegen, type_entry, ResolveStatusAlignmentKnown)))
        return ira->codegen->invalid_instruction;

    switch (type_entry->id) {
        case ZigTypeIdInvalid:
            zig_unreachable();
        case ZigTypeIdMetaType:
        case ZigTypeIdUnreachable:
        case ZigTypeIdComptimeFloat:
        case ZigTypeIdComptimeInt:
        case ZigTypeIdUndefined:
        case ZigTypeIdNull:
        case ZigTypeIdNamespace:
        case ZigTypeIdBoundFn:
        case ZigTypeIdArgTuple:
        case ZigTypeIdVoid:
        case ZigTypeIdOpaque:
            ir_add_error(ira, instruction->type_value,
                    buf_sprintf("no align available for type '%s'", buf_ptr(&type_entry->name)));
            return ira->codegen->invalid_instruction;
        case ZigTypeIdBool:
        case ZigTypeIdInt:
        case ZigTypeIdFloat:
        case ZigTypeIdPointer:
        case ZigTypeIdPromise:
        case ZigTypeIdArray:
        case ZigTypeIdStruct:
        case ZigTypeIdOptional:
        case ZigTypeIdErrorUnion:
        case ZigTypeIdErrorSet:
        case ZigTypeIdEnum:
        case ZigTypeIdUnion:
        case ZigTypeIdFn:
        case ZigTypeIdVector:
            {
                uint64_t align_in_bytes = get_abi_alignment(ira->codegen, type_entry);
                return ir_const_unsigned(ira, &instruction->base, align_in_bytes);
            }
    }
    zig_unreachable();
}

static IrInstruction *ir_analyze_instruction_overflow_op(IrAnalyze *ira, IrInstructionOverflowOp *instruction) {
    Error err;

    IrInstruction *type_value = instruction->type_value->child;
    if (type_is_invalid(type_value->value.type))
        return ira->codegen->invalid_instruction;

    ZigType *dest_type = ir_resolve_type(ira, type_value);
    if (type_is_invalid(dest_type))
        return ira->codegen->invalid_instruction;

    if (dest_type->id != ZigTypeIdInt) {
        ir_add_error(ira, type_value,
            buf_sprintf("expected integer type, found '%s'", buf_ptr(&dest_type->name)));
        return ira->codegen->invalid_instruction;
    }

    IrInstruction *op1 = instruction->op1->child;
    if (type_is_invalid(op1->value.type))
        return ira->codegen->invalid_instruction;

    IrInstruction *casted_op1 = ir_implicit_cast(ira, op1, dest_type);
    if (type_is_invalid(casted_op1->value.type))
        return ira->codegen->invalid_instruction;

    IrInstruction *op2 = instruction->op2->child;
    if (type_is_invalid(op2->value.type))
        return ira->codegen->invalid_instruction;

    IrInstruction *casted_op2;
    if (instruction->op == IrOverflowOpShl) {
        ZigType *shift_amt_type = get_smallest_unsigned_int_type(ira->codegen,
                dest_type->data.integral.bit_count - 1);
        casted_op2 = ir_implicit_cast(ira, op2, shift_amt_type);
    } else {
        casted_op2 = ir_implicit_cast(ira, op2, dest_type);
    }
    if (type_is_invalid(casted_op2->value.type))
        return ira->codegen->invalid_instruction;

    IrInstruction *result_ptr = instruction->result_ptr->child;
    if (type_is_invalid(result_ptr->value.type))
        return ira->codegen->invalid_instruction;

    ZigType *expected_ptr_type;
    if (result_ptr->value.type->id == ZigTypeIdPointer) {
        uint32_t alignment;
        if ((err = resolve_ptr_align(ira, result_ptr->value.type, &alignment)))
            return ira->codegen->invalid_instruction;
        expected_ptr_type = get_pointer_to_type_extra(ira->codegen, dest_type,
                false, result_ptr->value.type->data.pointer.is_volatile,
                PtrLenSingle,
                alignment, 0, 0);
    } else {
        expected_ptr_type = get_pointer_to_type(ira->codegen, dest_type, false);
    }

    IrInstruction *casted_result_ptr = ir_implicit_cast(ira, result_ptr, expected_ptr_type);
    if (type_is_invalid(casted_result_ptr->value.type))
        return ira->codegen->invalid_instruction;

    if (casted_op1->value.special == ConstValSpecialStatic &&
        casted_op2->value.special == ConstValSpecialStatic &&
        casted_result_ptr->value.special == ConstValSpecialStatic)
    {
        BigInt *op1_bigint = &casted_op1->value.data.x_bigint;
        BigInt *op2_bigint = &casted_op2->value.data.x_bigint;
        ConstExprValue *pointee_val = const_ptr_pointee(ira, ira->codegen, &casted_result_ptr->value, casted_result_ptr->source_node);
        if (pointee_val == nullptr)
            return ira->codegen->invalid_instruction;
        BigInt *dest_bigint = &pointee_val->data.x_bigint;
        switch (instruction->op) {
            case IrOverflowOpAdd:
                bigint_add(dest_bigint, op1_bigint, op2_bigint);
                break;
            case IrOverflowOpSub:
                bigint_sub(dest_bigint, op1_bigint, op2_bigint);
                break;
            case IrOverflowOpMul:
                bigint_mul(dest_bigint, op1_bigint, op2_bigint);
                break;
            case IrOverflowOpShl:
                bigint_shl(dest_bigint, op1_bigint, op2_bigint);
                break;
        }
        bool result_bool = false;
        if (!bigint_fits_in_bits(dest_bigint, dest_type->data.integral.bit_count,
            dest_type->data.integral.is_signed))
        {
            result_bool = true;
            BigInt tmp_bigint;
            bigint_init_bigint(&tmp_bigint, dest_bigint);
            bigint_truncate(dest_bigint, &tmp_bigint, dest_type->data.integral.bit_count,
                    dest_type->data.integral.is_signed);
        }
        pointee_val->special = ConstValSpecialStatic;
        return ir_const_bool(ira, &instruction->base, result_bool);
    }

    IrInstruction *result = ir_build_overflow_op(&ira->new_irb,
            instruction->base.scope, instruction->base.source_node,
            instruction->op, type_value, casted_op1, casted_op2, casted_result_ptr, dest_type);
    result->value.type = ira->codegen->builtin_types.entry_bool;
    return result;
}

static IrInstruction *ir_analyze_instruction_test_err(IrAnalyze *ira, IrInstructionTestErr *instruction) {
    IrInstruction *value = instruction->value->child;
    if (type_is_invalid(value->value.type))
        return ira->codegen->invalid_instruction;

    ZigType *type_entry = value->value.type;
    if (type_is_invalid(type_entry)) {
        return ira->codegen->invalid_instruction;
    } else if (type_entry->id == ZigTypeIdErrorUnion) {
        if (instr_is_comptime(value)) {
            ConstExprValue *err_union_val = ir_resolve_const(ira, value, UndefBad);
            if (!err_union_val)
                return ira->codegen->invalid_instruction;

            if (err_union_val->special != ConstValSpecialRuntime) {
                ErrorTableEntry *err = err_union_val->data.x_err_union.error_set->data.x_err_set;
                return ir_const_bool(ira, &instruction->base, (err != nullptr));
            }
        }

        ZigType *err_set_type = type_entry->data.error_union.err_set_type;
        if (!resolve_inferred_error_set(ira->codegen, err_set_type, instruction->base.source_node)) {
            return ira->codegen->invalid_instruction;
        }
        if (!type_is_global_error_set(err_set_type) &&
            err_set_type->data.error_set.err_count == 0)
        {
            assert(err_set_type->data.error_set.infer_fn == nullptr);
            return ir_const_bool(ira, &instruction->base, false);
        }

        IrInstruction *result = ir_build_test_err(&ira->new_irb,
            instruction->base.scope, instruction->base.source_node, value);
        result->value.type = ira->codegen->builtin_types.entry_bool;
        return result;
    } else if (type_entry->id == ZigTypeIdErrorSet) {
        return ir_const_bool(ira, &instruction->base, true);
    } else {
        return ir_const_bool(ira, &instruction->base, false);
    }
}

static IrInstruction *ir_analyze_instruction_unwrap_err_code(IrAnalyze *ira, IrInstructionUnwrapErrCode *instruction) {
    IrInstruction *base_ptr = instruction->err_union->child;
    if (type_is_invalid(base_ptr->value.type))
        return ira->codegen->invalid_instruction;
    ZigType *ptr_type = base_ptr->value.type;

    // This will be a pointer type because unwrap err payload IR instruction operates on a pointer to a thing.
    assert(ptr_type->id == ZigTypeIdPointer);

    ZigType *type_entry = ptr_type->data.pointer.child_type;
    if (type_is_invalid(type_entry))
        return ira->codegen->invalid_instruction;

    if (type_entry->id != ZigTypeIdErrorUnion) {
        ir_add_error(ira, base_ptr,
            buf_sprintf("expected error union type, found '%s'", buf_ptr(&type_entry->name)));
        return ira->codegen->invalid_instruction;
    }

    if (instr_is_comptime(base_ptr)) {
        ConstExprValue *ptr_val = ir_resolve_const(ira, base_ptr, UndefBad);
        if (!ptr_val)
            return ira->codegen->invalid_instruction;
        ConstExprValue *err_union_val = const_ptr_pointee(ira, ira->codegen, ptr_val, instruction->base.source_node);
        if (err_union_val == nullptr)
            return ira->codegen->invalid_instruction;
        if (err_union_val->special != ConstValSpecialRuntime) {
            ErrorTableEntry *err = err_union_val->data.x_err_union.error_set->data.x_err_set;
            assert(err);

            IrInstruction *result = ir_const(ira, &instruction->base,
                    type_entry->data.error_union.err_set_type);
            result->value.data.x_err_set = err;
            return result;
        }
    }

    IrInstruction *result = ir_build_unwrap_err_code(&ira->new_irb,
        instruction->base.scope, instruction->base.source_node, base_ptr);
    result->value.type = type_entry->data.error_union.err_set_type;
    return result;
}

static IrInstruction *ir_analyze_instruction_unwrap_err_payload(IrAnalyze *ira,
    IrInstructionUnwrapErrPayload *instruction)
{
    assert(instruction->value->child);
    IrInstruction *value = instruction->value->child;
    if (type_is_invalid(value->value.type))
        return ira->codegen->invalid_instruction;
    ZigType *ptr_type = value->value.type;

    // This will be a pointer type because unwrap err payload IR instruction operates on a pointer to a thing.
    assert(ptr_type->id == ZigTypeIdPointer);

    ZigType *type_entry = ptr_type->data.pointer.child_type;
    if (type_is_invalid(type_entry))
        return ira->codegen->invalid_instruction;

    if (type_entry->id != ZigTypeIdErrorUnion) {
        ir_add_error(ira, value,
            buf_sprintf("expected error union type, found '%s'", buf_ptr(&type_entry->name)));
        return ira->codegen->invalid_instruction;
    }

    ZigType *payload_type = type_entry->data.error_union.payload_type;
    if (type_is_invalid(payload_type))
        return ira->codegen->invalid_instruction;

    ZigType *result_type = get_pointer_to_type_extra(ira->codegen, payload_type,
            ptr_type->data.pointer.is_const, ptr_type->data.pointer.is_volatile,
            PtrLenSingle, 0, 0, 0);
    if (instr_is_comptime(value)) {
        ConstExprValue *ptr_val = ir_resolve_const(ira, value, UndefBad);
        if (!ptr_val)
            return ira->codegen->invalid_instruction;
        ConstExprValue *err_union_val = const_ptr_pointee(ira, ira->codegen, ptr_val, instruction->base.source_node);
        if (err_union_val == nullptr)
            return ira->codegen->invalid_instruction;
        if (err_union_val->special != ConstValSpecialRuntime) {
            ErrorTableEntry *err = err_union_val->data.x_err_union.error_set->data.x_err_set;
            if (err != nullptr) {
                ir_add_error(ira, &instruction->base,
                    buf_sprintf("caught unexpected error '%s'", buf_ptr(&err->name)));
                return ira->codegen->invalid_instruction;
            }

            IrInstruction *result = ir_const(ira, &instruction->base, result_type);
            result->value.data.x_ptr.special = ConstPtrSpecialRef;
            result->value.data.x_ptr.data.ref.pointee = err_union_val->data.x_err_union.payload;
            return result;
        }
    }

    IrInstruction *result = ir_build_unwrap_err_payload(&ira->new_irb,
        instruction->base.scope, instruction->base.source_node, value, instruction->safety_check_on);
    result->value.type = result_type;
    return result;
}

static IrInstruction *ir_analyze_instruction_fn_proto(IrAnalyze *ira, IrInstructionFnProto *instruction) {
    AstNode *proto_node = instruction->base.source_node;
    assert(proto_node->type == NodeTypeFnProto);

    if (proto_node->data.fn_proto.auto_err_set) {
        ir_add_error(ira, &instruction->base,
            buf_sprintf("inferring error set of return type valid only for function definitions"));
        return ira->codegen->invalid_instruction;
    }

    FnTypeId fn_type_id = {0};
    init_fn_type_id(&fn_type_id, proto_node, proto_node->data.fn_proto.params.length);

    for (; fn_type_id.next_param_index < fn_type_id.param_count; fn_type_id.next_param_index += 1) {
        AstNode *param_node = proto_node->data.fn_proto.params.at(fn_type_id.next_param_index);
        assert(param_node->type == NodeTypeParamDecl);

        bool param_is_var_args = param_node->data.param_decl.is_var_args;
        if (param_is_var_args) {
            if (fn_type_id.cc == CallingConventionC) {
                fn_type_id.param_count = fn_type_id.next_param_index;
                continue;
            } else if (fn_type_id.cc == CallingConventionUnspecified) {
                return ir_const_type(ira, &instruction->base, get_generic_fn_type(ira->codegen, &fn_type_id));
            } else {
                zig_unreachable();
            }
        }
        FnTypeParamInfo *param_info = &fn_type_id.param_info[fn_type_id.next_param_index];
        param_info->is_noalias = param_node->data.param_decl.is_noalias;

        if (instruction->param_types[fn_type_id.next_param_index] == nullptr) {
            param_info->type = nullptr;
            return ir_const_type(ira, &instruction->base, get_generic_fn_type(ira->codegen, &fn_type_id));
        } else {
            IrInstruction *param_type_value = instruction->param_types[fn_type_id.next_param_index]->child;
            if (type_is_invalid(param_type_value->value.type))
                return ira->codegen->invalid_instruction;
            ZigType *param_type = ir_resolve_type(ira, param_type_value);
            switch (type_requires_comptime(ira->codegen, param_type)) {
            case ReqCompTimeYes:
                if (!calling_convention_allows_zig_types(fn_type_id.cc)) {
                    ir_add_error(ira, param_type_value,
                        buf_sprintf("parameter of type '%s' not allowed in function with calling convention '%s'",
                            buf_ptr(&param_type->name), calling_convention_name(fn_type_id.cc)));
                    return ira->codegen->invalid_instruction;
                }
                param_info->type = param_type;
                fn_type_id.next_param_index += 1;
                return ir_const_type(ira, &instruction->base, get_generic_fn_type(ira->codegen, &fn_type_id));
            case ReqCompTimeInvalid:
                return ira->codegen->invalid_instruction;
            case ReqCompTimeNo:
                break;
            }
            if (!type_has_bits(param_type) && !calling_convention_allows_zig_types(fn_type_id.cc)) {
                ir_add_error(ira, param_type_value,
                    buf_sprintf("parameter of type '%s' has 0 bits; not allowed in function with calling convention '%s'",
                        buf_ptr(&param_type->name), calling_convention_name(fn_type_id.cc)));
                return ira->codegen->invalid_instruction;
            }
            param_info->type = param_type;
        }

    }

    if (instruction->align_value != nullptr) {
        if (!ir_resolve_align(ira, instruction->align_value->child, &fn_type_id.alignment))
            return ira->codegen->invalid_instruction;
    }

    IrInstruction *return_type_value = instruction->return_type->child;
    fn_type_id.return_type = ir_resolve_type(ira, return_type_value);
    if (type_is_invalid(fn_type_id.return_type))
        return ira->codegen->invalid_instruction;
    if (fn_type_id.return_type->id == ZigTypeIdOpaque) {
        ir_add_error(ira, instruction->return_type,
            buf_sprintf("return type cannot be opaque"));
        return ira->codegen->invalid_instruction;
    }

    if (fn_type_id.cc == CallingConventionAsync) {
        if (instruction->async_allocator_type_value == nullptr) {
            ir_add_error(ira, &instruction->base,
                buf_sprintf("async fn proto missing allocator type"));
            return ira->codegen->invalid_instruction;
        }
        IrInstruction *async_allocator_type_value = instruction->async_allocator_type_value->child;
        fn_type_id.async_allocator_type = ir_resolve_type(ira, async_allocator_type_value);
        if (type_is_invalid(fn_type_id.async_allocator_type))
            return ira->codegen->invalid_instruction;
    }

    return ir_const_type(ira, &instruction->base, get_fn_type(ira->codegen, &fn_type_id));
}

static IrInstruction *ir_analyze_instruction_test_comptime(IrAnalyze *ira, IrInstructionTestComptime *instruction) {
    IrInstruction *value = instruction->value->child;
    if (type_is_invalid(value->value.type))
        return ira->codegen->invalid_instruction;

    return ir_const_bool(ira, &instruction->base, instr_is_comptime(value));
}

static IrInstruction *ir_analyze_instruction_check_switch_prongs(IrAnalyze *ira,
        IrInstructionCheckSwitchProngs *instruction)
{
    IrInstruction *target_value = instruction->target_value->child;
    ZigType *switch_type = target_value->value.type;
    if (type_is_invalid(switch_type))
        return ira->codegen->invalid_instruction;

    if (switch_type->id == ZigTypeIdEnum) {
        HashMap<BigInt, AstNode *, bigint_hash, bigint_eql> field_prev_uses = {};
        field_prev_uses.init(switch_type->data.enumeration.src_field_count);

        for (size_t range_i = 0; range_i < instruction->range_count; range_i += 1) {
            IrInstructionCheckSwitchProngsRange *range = &instruction->ranges[range_i];

            IrInstruction *start_value = range->start->child;
            if (type_is_invalid(start_value->value.type))
                return ira->codegen->invalid_instruction;

            IrInstruction *end_value = range->end->child;
            if (type_is_invalid(end_value->value.type))
                return ira->codegen->invalid_instruction;

            if (start_value->value.type->id != ZigTypeIdEnum) {
                ir_add_error(ira, range->start, buf_sprintf("not an enum type"));
                return ira->codegen->invalid_instruction;
            }

            BigInt start_index;
            bigint_init_bigint(&start_index, &start_value->value.data.x_enum_tag);

            assert(end_value->value.type->id == ZigTypeIdEnum);
            BigInt end_index;
            bigint_init_bigint(&end_index, &end_value->value.data.x_enum_tag);

            BigInt field_index;
            bigint_init_bigint(&field_index, &start_index);
            for (;;) {
                Cmp cmp = bigint_cmp(&field_index, &end_index);
                if (cmp == CmpGT) {
                    break;
                }
                auto entry = field_prev_uses.put_unique(field_index, start_value->source_node);
                if (entry) {
                    AstNode *prev_node = entry->value;
                    TypeEnumField *enum_field = find_enum_field_by_tag(switch_type, &field_index);
                    assert(enum_field != nullptr);
                    ErrorMsg *msg = ir_add_error(ira, start_value,
                        buf_sprintf("duplicate switch value: '%s.%s'", buf_ptr(&switch_type->name),
                            buf_ptr(enum_field->name)));
                    add_error_note(ira->codegen, msg, prev_node, buf_sprintf("other value is here"));
                }
                bigint_incr(&field_index);
            }
        }
        if (!instruction->have_else_prong) {
            for (uint32_t i = 0; i < switch_type->data.enumeration.src_field_count; i += 1) {
                TypeEnumField *enum_field = &switch_type->data.enumeration.fields[i];

                auto entry = field_prev_uses.maybe_get(enum_field->value);
                if (!entry) {
                    ir_add_error(ira, &instruction->base,
                        buf_sprintf("enumeration value '%s.%s' not handled in switch", buf_ptr(&switch_type->name),
                            buf_ptr(enum_field->name)));
                }
            }
        }
    } else if (switch_type->id == ZigTypeIdErrorSet) {
        if (!resolve_inferred_error_set(ira->codegen, switch_type, target_value->source_node)) {
            return ira->codegen->invalid_instruction;
        }

        AstNode **field_prev_uses = allocate<AstNode *>(ira->codegen->errors_by_index.length);

        for (size_t range_i = 0; range_i < instruction->range_count; range_i += 1) {
            IrInstructionCheckSwitchProngsRange *range = &instruction->ranges[range_i];

            IrInstruction *start_value = range->start->child;
            if (type_is_invalid(start_value->value.type))
                return ira->codegen->invalid_instruction;

            IrInstruction *end_value = range->end->child;
            if (type_is_invalid(end_value->value.type))
                return ira->codegen->invalid_instruction;

            assert(start_value->value.type->id == ZigTypeIdErrorSet);
            uint32_t start_index = start_value->value.data.x_err_set->value;

            assert(end_value->value.type->id == ZigTypeIdErrorSet);
            uint32_t end_index = end_value->value.data.x_err_set->value;

            if (start_index != end_index) {
                ir_add_error(ira, end_value, buf_sprintf("ranges not allowed when switching on errors"));
                return ira->codegen->invalid_instruction;
            }

            AstNode *prev_node = field_prev_uses[start_index];
            if (prev_node != nullptr) {
                Buf *err_name = &ira->codegen->errors_by_index.at(start_index)->name;
                ErrorMsg *msg = ir_add_error(ira, start_value,
                    buf_sprintf("duplicate switch value: '%s.%s'", buf_ptr(&switch_type->name), buf_ptr(err_name)));
                add_error_note(ira->codegen, msg, prev_node, buf_sprintf("other value is here"));
            }
            field_prev_uses[start_index] = start_value->source_node;
        }
        if (!instruction->have_else_prong) {
            if (type_is_global_error_set(switch_type)) {
                ir_add_error(ira, &instruction->base,
                    buf_sprintf("else prong required when switching on type 'anyerror'"));
                return ira->codegen->invalid_instruction;
            } else {
                for (uint32_t i = 0; i < switch_type->data.error_set.err_count; i += 1) {
                    ErrorTableEntry *err_entry = switch_type->data.error_set.errors[i];

                    AstNode *prev_node = field_prev_uses[err_entry->value];
                    if (prev_node == nullptr) {
                        ir_add_error(ira, &instruction->base,
                            buf_sprintf("error.%s not handled in switch", buf_ptr(&err_entry->name)));
                    }
                }
            }
        }

        free(field_prev_uses);
    } else if (switch_type->id == ZigTypeIdInt) {
        RangeSet rs = {0};
        for (size_t range_i = 0; range_i < instruction->range_count; range_i += 1) {
            IrInstructionCheckSwitchProngsRange *range = &instruction->ranges[range_i];

            IrInstruction *start_value = range->start->child;
            if (type_is_invalid(start_value->value.type))
                return ira->codegen->invalid_instruction;
            IrInstruction *casted_start_value = ir_implicit_cast(ira, start_value, switch_type);
            if (type_is_invalid(casted_start_value->value.type))
                return ira->codegen->invalid_instruction;

            IrInstruction *end_value = range->end->child;
            if (type_is_invalid(end_value->value.type))
                return ira->codegen->invalid_instruction;
            IrInstruction *casted_end_value = ir_implicit_cast(ira, end_value, switch_type);
            if (type_is_invalid(casted_end_value->value.type))
                return ira->codegen->invalid_instruction;

            ConstExprValue *start_val = ir_resolve_const(ira, casted_start_value, UndefBad);
            if (!start_val)
                return ira->codegen->invalid_instruction;

            ConstExprValue *end_val = ir_resolve_const(ira, casted_end_value, UndefBad);
            if (!end_val)
                return ira->codegen->invalid_instruction;

            assert(start_val->type->id == ZigTypeIdInt || start_val->type->id == ZigTypeIdComptimeInt);
            assert(end_val->type->id == ZigTypeIdInt || end_val->type->id == ZigTypeIdComptimeInt);
            AstNode *prev_node = rangeset_add_range(&rs, &start_val->data.x_bigint, &end_val->data.x_bigint,
                    start_value->source_node);
            if (prev_node != nullptr) {
                ErrorMsg *msg = ir_add_error(ira, start_value, buf_sprintf("duplicate switch value"));
                add_error_note(ira->codegen, msg, prev_node, buf_sprintf("previous value is here"));
                return ira->codegen->invalid_instruction;
            }
        }
        if (!instruction->have_else_prong) {
            BigInt min_val;
            eval_min_max_value_int(ira->codegen, switch_type, &min_val, false);
            BigInt max_val;
            eval_min_max_value_int(ira->codegen, switch_type, &max_val, true);
            if (!rangeset_spans(&rs, &min_val, &max_val)) {
                ir_add_error(ira, &instruction->base, buf_sprintf("switch must handle all possibilities"));
                return ira->codegen->invalid_instruction;
            }
        }
    } else if (switch_type->id == ZigTypeIdBool) {
        int seenTrue = 0;
        int seenFalse = 0;
        for (size_t range_i = 0; range_i < instruction->range_count; range_i += 1) {
            IrInstructionCheckSwitchProngsRange *range = &instruction->ranges[range_i];

            IrInstruction *value = range->start->child;

            IrInstruction *casted_value = ir_implicit_cast(ira, value, switch_type);
            if (type_is_invalid(casted_value->value.type))
                return ira->codegen->invalid_instruction;

            ConstExprValue *const_expr_val = ir_resolve_const(ira, casted_value, UndefBad);
            if (!const_expr_val)
                return ira->codegen->invalid_instruction;

            assert(const_expr_val->type->id == ZigTypeIdBool);

            if (const_expr_val->data.x_bool == true) {
                seenTrue += 1;
            } else {
                seenFalse += 1;
            }

            if ((seenTrue > 1) || (seenFalse > 1)) {
                ir_add_error(ira, value, buf_sprintf("duplicate switch value"));
                return ira->codegen->invalid_instruction;
            }
        }
        if (((seenTrue < 1) || (seenFalse < 1)) && !instruction->have_else_prong) {
            ir_add_error(ira, &instruction->base, buf_sprintf("switch must handle all possibilities"));
            return ira->codegen->invalid_instruction;
        }
    } else if (!instruction->have_else_prong) {
        ir_add_error(ira, &instruction->base,
            buf_sprintf("else prong required when switching on type '%s'", buf_ptr(&switch_type->name)));
        return ira->codegen->invalid_instruction;
    }
    return ir_const_void(ira, &instruction->base);
}

static IrInstruction *ir_analyze_instruction_check_statement_is_void(IrAnalyze *ira,
        IrInstructionCheckStatementIsVoid *instruction)
{
    IrInstruction *statement_value = instruction->statement_value->child;
    ZigType *statement_type = statement_value->value.type;
    if (type_is_invalid(statement_type))
        return ira->codegen->invalid_instruction;

    if (statement_type->id != ZigTypeIdVoid) {
        ir_add_error(ira, &instruction->base, buf_sprintf("expression value is ignored"));
    }

    return ir_const_void(ira, &instruction->base);
}

static IrInstruction *ir_analyze_instruction_panic(IrAnalyze *ira, IrInstructionPanic *instruction) {
    IrInstruction *msg = instruction->msg->child;
    if (type_is_invalid(msg->value.type))
        return ir_unreach_error(ira);

    if (ir_should_inline(ira->new_irb.exec, instruction->base.scope)) {
        ir_add_error(ira, &instruction->base, buf_sprintf("encountered @panic at compile-time"));
        return ir_unreach_error(ira);
    }

    ZigType *u8_ptr_type = get_pointer_to_type_extra(ira->codegen, ira->codegen->builtin_types.entry_u8,
            true, false, PtrLenUnknown, 0, 0, 0);
    ZigType *str_type = get_slice_type(ira->codegen, u8_ptr_type);
    IrInstruction *casted_msg = ir_implicit_cast(ira, msg, str_type);
    if (type_is_invalid(casted_msg->value.type))
        return ir_unreach_error(ira);

    IrInstruction *new_instruction = ir_build_panic(&ira->new_irb, instruction->base.scope,
            instruction->base.source_node, casted_msg);
    return ir_finish_anal(ira, new_instruction);
}

static IrInstruction *ir_align_cast(IrAnalyze *ira, IrInstruction *target, uint32_t align_bytes, bool safety_check_on) {
    Error err;

    ZigType *target_type = target->value.type;
    assert(!type_is_invalid(target_type));

    ZigType *result_type;
    uint32_t old_align_bytes;

    if (target_type->id == ZigTypeIdPointer) {
        result_type = adjust_ptr_align(ira->codegen, target_type, align_bytes);
        if ((err = resolve_ptr_align(ira, target_type, &old_align_bytes)))
            return ira->codegen->invalid_instruction;
    } else if (target_type->id == ZigTypeIdFn) {
        FnTypeId fn_type_id = target_type->data.fn.fn_type_id;
        old_align_bytes = fn_type_id.alignment;
        fn_type_id.alignment = align_bytes;
        result_type = get_fn_type(ira->codegen, &fn_type_id);
    } else if (target_type->id == ZigTypeIdOptional &&
            target_type->data.maybe.child_type->id == ZigTypeIdPointer)
    {
        ZigType *ptr_type = target_type->data.maybe.child_type;
        if ((err = resolve_ptr_align(ira, ptr_type, &old_align_bytes)))
            return ira->codegen->invalid_instruction;
        ZigType *better_ptr_type = adjust_ptr_align(ira->codegen, ptr_type, align_bytes);

        result_type = get_optional_type(ira->codegen, better_ptr_type);
    } else if (target_type->id == ZigTypeIdOptional &&
            target_type->data.maybe.child_type->id == ZigTypeIdFn)
    {
        FnTypeId fn_type_id = target_type->data.maybe.child_type->data.fn.fn_type_id;
        old_align_bytes = fn_type_id.alignment;
        fn_type_id.alignment = align_bytes;
        ZigType *fn_type = get_fn_type(ira->codegen, &fn_type_id);
        result_type = get_optional_type(ira->codegen, fn_type);
    } else if (is_slice(target_type)) {
        ZigType *slice_ptr_type = target_type->data.structure.fields[slice_ptr_index].type_entry;
        if ((err = resolve_ptr_align(ira, slice_ptr_type, &old_align_bytes)))
            return ira->codegen->invalid_instruction;
        ZigType *result_ptr_type = adjust_ptr_align(ira->codegen, slice_ptr_type, align_bytes);
        result_type = get_slice_type(ira->codegen, result_ptr_type);
    } else {
        ir_add_error(ira, target,
                buf_sprintf("expected pointer or slice, found '%s'", buf_ptr(&target_type->name)));
        return ira->codegen->invalid_instruction;
    }

    if (instr_is_comptime(target)) {
        ConstExprValue *val = ir_resolve_const(ira, target, UndefBad);
        if (!val)
            return ira->codegen->invalid_instruction;

        if (val->data.x_ptr.special == ConstPtrSpecialHardCodedAddr &&
            val->data.x_ptr.data.hard_coded_addr.addr % align_bytes != 0)
        {
            ir_add_error(ira, target,
                    buf_sprintf("pointer address 0x%" ZIG_PRI_x64 " is not aligned to %" PRIu32 " bytes",
                        val->data.x_ptr.data.hard_coded_addr.addr, align_bytes));
            return ira->codegen->invalid_instruction;
        }

        IrInstruction *result = ir_const(ira, target, result_type);
        copy_const_val(&result->value, val, false);
        result->value.type = result_type;
        return result;
    }

    IrInstruction *result;
    if (safety_check_on && align_bytes > old_align_bytes && align_bytes != 1) {
        result = ir_build_align_cast(&ira->new_irb, target->scope, target->source_node, nullptr, target);
    } else {
        result = ir_build_cast(&ira->new_irb, target->scope, target->source_node, result_type, target, CastOpNoop);
    }
    result->value.type = result_type;
    return result;
}

static IrInstruction *ir_analyze_ptr_cast(IrAnalyze *ira, IrInstruction *source_instr, IrInstruction *ptr,
        ZigType *dest_type, IrInstruction *dest_type_src, bool safety_check_on)
{
    Error err;

    ZigType *src_type = ptr->value.type;
    assert(!type_is_invalid(src_type));

    // We have a check for zero bits later so we use get_src_ptr_type to
    // validate src_type and dest_type.

    ZigType *src_ptr_type = get_src_ptr_type(src_type);
    if (src_ptr_type == nullptr) {
        ir_add_error(ira, ptr, buf_sprintf("expected pointer, found '%s'", buf_ptr(&src_type->name)));
        return ira->codegen->invalid_instruction;
    }

    ZigType *dest_ptr_type = get_src_ptr_type(dest_type);
    if (dest_ptr_type == nullptr) {
        ir_add_error(ira, dest_type_src,
                buf_sprintf("expected pointer, found '%s'", buf_ptr(&dest_type->name)));
        return ira->codegen->invalid_instruction;
    }

    if (get_ptr_const(src_type) && !get_ptr_const(dest_type)) {
        ir_add_error(ira, source_instr, buf_sprintf("cast discards const qualifier"));
        return ira->codegen->invalid_instruction;
    }
    uint32_t src_align_bytes;
    if ((err = resolve_ptr_align(ira, src_type, &src_align_bytes)))
        return ira->codegen->invalid_instruction;

    uint32_t dest_align_bytes;
    if ((err = resolve_ptr_align(ira, dest_type, &dest_align_bytes)))
        return ira->codegen->invalid_instruction;

    if (instr_is_comptime(ptr)) {
        bool dest_allows_addr_zero = ptr_allows_addr_zero(dest_type);
        UndefAllowed is_undef_allowed = dest_allows_addr_zero ? UndefOk : UndefBad;
        ConstExprValue *val = ir_resolve_const(ira, ptr, is_undef_allowed);
        if (!val)
            return ira->codegen->invalid_instruction;

        if (val->special == ConstValSpecialStatic) {
            bool is_addr_zero = val->data.x_ptr.special == ConstPtrSpecialNull ||
                (val->data.x_ptr.special == ConstPtrSpecialHardCodedAddr &&
                    val->data.x_ptr.data.hard_coded_addr.addr == 0);
            if (is_addr_zero && !dest_allows_addr_zero) {
                ir_add_error(ira, source_instr,
                        buf_sprintf("null pointer casted to type '%s'", buf_ptr(&dest_type->name)));
                return ira->codegen->invalid_instruction;
            }
        }

        IrInstruction *result = ir_const(ira, source_instr, dest_type);
        copy_const_val(&result->value, val, false);
        result->value.type = dest_type;

        // Keep the bigger alignment, it can only help-
        // unless the target is zero bits.
        if (src_align_bytes > dest_align_bytes && type_has_bits(dest_type)) {
            result =  ir_align_cast(ira, result, src_align_bytes, false);
        }

        return result;
    }

    if (dest_align_bytes > src_align_bytes) {
        ErrorMsg *msg = ir_add_error(ira, source_instr, buf_sprintf("cast increases pointer alignment"));
        add_error_note(ira->codegen, msg, ptr->source_node,
                buf_sprintf("'%s' has alignment %" PRIu32, buf_ptr(&src_type->name), src_align_bytes));
        add_error_note(ira->codegen, msg, dest_type_src->source_node,
                buf_sprintf("'%s' has alignment %" PRIu32, buf_ptr(&dest_type->name), dest_align_bytes));
        return ira->codegen->invalid_instruction;
    }

    IrInstruction *casted_ptr = ir_build_ptr_cast_gen(ira, source_instr, dest_type, ptr, safety_check_on);

    if (type_has_bits(dest_type) && !type_has_bits(src_type)) {
        ErrorMsg *msg = ir_add_error(ira, source_instr,
            buf_sprintf("'%s' and '%s' do not have the same in-memory representation",
                buf_ptr(&src_type->name), buf_ptr(&dest_type->name)));
        add_error_note(ira->codegen, msg, ptr->source_node,
                buf_sprintf("'%s' has no in-memory bits", buf_ptr(&src_type->name)));
        add_error_note(ira->codegen, msg, dest_type_src->source_node,
                buf_sprintf("'%s' has in-memory bits", buf_ptr(&dest_type->name)));
        return ira->codegen->invalid_instruction;
    }

    // Keep the bigger alignment, it can only help-
    // unless the target is zero bits.
    IrInstruction *result;
    if (src_align_bytes > dest_align_bytes && type_has_bits(dest_type)) {
        result = ir_align_cast(ira, casted_ptr, src_align_bytes, false);
        if (type_is_invalid(result->value.type))
            return ira->codegen->invalid_instruction;
    } else {
        result = casted_ptr;
    }
    return result;
}

static IrInstruction *ir_analyze_instruction_ptr_cast(IrAnalyze *ira, IrInstructionPtrCastSrc *instruction) {
    IrInstruction *dest_type_value = instruction->dest_type->child;
    ZigType *dest_type = ir_resolve_type(ira, dest_type_value);
    if (type_is_invalid(dest_type))
        return ira->codegen->invalid_instruction;

    IrInstruction *ptr = instruction->ptr->child;
    ZigType *src_type = ptr->value.type;
    if (type_is_invalid(src_type))
        return ira->codegen->invalid_instruction;

    return ir_analyze_ptr_cast(ira, &instruction->base, ptr, dest_type, dest_type_value,
            instruction->safety_check_on);
}

static void buf_write_value_bytes_array(CodeGen *codegen, uint8_t *buf, ConstExprValue *val, size_t len) {
    size_t buf_i = 0;
    // TODO optimize the buf case
    expand_undef_array(codegen, val);
    for (size_t elem_i = 0; elem_i < val->type->data.array.len; elem_i += 1) {
        ConstExprValue *elem = &val->data.x_array.data.s_none.elements[elem_i];
        buf_write_value_bytes(codegen, &buf[buf_i], elem);
        buf_i += type_size(codegen, elem->type);
    }
}

static void buf_write_value_bytes(CodeGen *codegen, uint8_t *buf, ConstExprValue *val) {
    if (val->special == ConstValSpecialUndef)
        val->special = ConstValSpecialStatic;
    assert(val->special == ConstValSpecialStatic);
    switch (val->type->id) {
        case ZigTypeIdInvalid:
        case ZigTypeIdMetaType:
        case ZigTypeIdOpaque:
        case ZigTypeIdBoundFn:
        case ZigTypeIdArgTuple:
        case ZigTypeIdNamespace:
        case ZigTypeIdUnreachable:
        case ZigTypeIdComptimeFloat:
        case ZigTypeIdComptimeInt:
        case ZigTypeIdUndefined:
        case ZigTypeIdNull:
        case ZigTypeIdPromise:
            zig_unreachable();
        case ZigTypeIdVoid:
            return;
        case ZigTypeIdBool:
            buf[0] = val->data.x_bool ? 1 : 0;
            return;
        case ZigTypeIdInt:
            bigint_write_twos_complement(&val->data.x_bigint, buf, val->type->data.integral.bit_count,
                    codegen->is_big_endian);
            return;
        case ZigTypeIdEnum:
            bigint_write_twos_complement(&val->data.x_enum_tag, buf,
                    val->type->data.enumeration.tag_int_type->data.integral.bit_count,
                    codegen->is_big_endian);
            return;
        case ZigTypeIdFloat:
            float_write_ieee597(val, buf, codegen->is_big_endian);
            return;
        case ZigTypeIdPointer:
            if (val->data.x_ptr.special == ConstPtrSpecialHardCodedAddr) {
                BigInt bn;
                bigint_init_unsigned(&bn, val->data.x_ptr.data.hard_coded_addr.addr);
                bigint_write_twos_complement(&bn, buf, codegen->builtin_types.entry_usize->data.integral.bit_count, codegen->is_big_endian);
                return;
            } else {
                zig_unreachable();
            }
        case ZigTypeIdArray:
            return buf_write_value_bytes_array(codegen, buf, val, val->type->data.array.len);
        case ZigTypeIdVector:
            return buf_write_value_bytes_array(codegen, buf, val, val->type->data.vector.len);
        case ZigTypeIdStruct:
            switch (val->type->data.structure.layout) {
                case ContainerLayoutAuto:
                    zig_unreachable();
                case ContainerLayoutExtern: {
                    size_t src_field_count = val->type->data.structure.src_field_count;
                    for (size_t field_i = 0; field_i < src_field_count; field_i += 1) {
                        TypeStructField *type_field = &val->type->data.structure.fields[field_i];
                        if (type_field->gen_index == SIZE_MAX)
                            continue;
                        ConstExprValue *field_val = &val->data.x_struct.fields[field_i];
                        size_t offset = LLVMOffsetOfElement(codegen->target_data_ref, val->type->type_ref,
                                type_field->gen_index);
                        buf_write_value_bytes(codegen, buf + offset, field_val);
                    }
                    return;
                }
                case ContainerLayoutPacked: {
                    size_t src_field_count = val->type->data.structure.src_field_count;
                    size_t gen_field_count = val->type->data.structure.gen_field_count;
                    size_t gen_i = 0;
                    size_t src_i = 0;
                    size_t offset = 0;
                    bool is_big_endian = codegen->is_big_endian;
                    uint8_t child_buf_prealloc[16];
                    size_t child_buf_len = 16;
                    uint8_t *child_buf = child_buf_prealloc;
                    while (gen_i < gen_field_count) {
                        LLVMTypeRef gen_llvm_int_type = LLVMStructGetTypeAtIndex(val->type->type_ref,
                                    (unsigned)gen_i);
                        size_t big_int_bit_count = LLVMGetIntTypeWidth(gen_llvm_int_type);
                        size_t big_int_byte_count = big_int_bit_count / 8;
                        if (big_int_byte_count > child_buf_len) {
                            child_buf = allocate_nonzero<uint8_t>(big_int_byte_count);
                            child_buf_len = big_int_byte_count;
                        }
                        BigInt big_int;
                        bigint_init_unsigned(&big_int, 0);
                        size_t used_bits = 0;
                        while (src_i < src_field_count) {
                            TypeStructField *field = &val->type->data.structure.fields[src_i];
                            assert(field->gen_index != SIZE_MAX);
                            if (field->gen_index != gen_i)
                                break;
                            uint32_t packed_bits_size = type_size_bits(codegen, field->type_entry);
                            buf_write_value_bytes(codegen, child_buf, &val->data.x_struct.fields[src_i]);
                            BigInt child_val;
                            bigint_read_twos_complement(&child_val, child_buf, packed_bits_size, is_big_endian,
                                    false);
                            if (is_big_endian) {
                                BigInt shift_amt;
                                bigint_init_unsigned(&shift_amt, packed_bits_size);
                                BigInt shifted;
                                bigint_shl(&shifted, &big_int, &shift_amt);
                                bigint_or(&big_int, &shifted, &child_val);
                            } else {
                                BigInt shift_amt;
                                bigint_init_unsigned(&shift_amt, used_bits);
                                BigInt child_val_shifted;
                                bigint_shl(&child_val_shifted, &child_val, &shift_amt);
                                BigInt tmp;
                                bigint_or(&tmp, &big_int, &child_val_shifted);
                                big_int = tmp;
                                used_bits += packed_bits_size;
                            }
                            src_i += 1;
                        }
                        bigint_write_twos_complement(&big_int, buf + offset, big_int_bit_count, is_big_endian);
                        offset += big_int_byte_count;
                        gen_i += 1;
                    }
                    return;
                }
            }
        case ZigTypeIdOptional:
            zig_panic("TODO buf_write_value_bytes maybe type");
        case ZigTypeIdErrorUnion:
            zig_panic("TODO buf_write_value_bytes error union");
        case ZigTypeIdErrorSet:
            zig_panic("TODO buf_write_value_bytes pure error type");
        case ZigTypeIdFn:
            zig_panic("TODO buf_write_value_bytes fn type");
        case ZigTypeIdUnion:
            zig_panic("TODO buf_write_value_bytes union type");
    }
    zig_unreachable();
}

static Error buf_read_value_bytes_array(IrAnalyze *ira, CodeGen *codegen, AstNode *source_node, uint8_t *buf,
        ConstExprValue *val, ZigType *elem_type, size_t len)
{
    Error err;
    uint64_t elem_size = type_size(codegen, elem_type);

    switch (val->data.x_array.special) {
        case ConstArraySpecialNone:
            val->data.x_array.data.s_none.elements = create_const_vals(len);
            for (size_t i = 0; i < len; i++) {
                ConstExprValue *elem = &val->data.x_array.data.s_none.elements[i];
                elem->special = ConstValSpecialStatic;
                elem->type = elem_type;
                if ((err = buf_read_value_bytes(ira, codegen, source_node, buf + (elem_size * i), elem)))
                    return err;
            }
            return ErrorNone;
        case ConstArraySpecialUndef:
            zig_panic("TODO buf_read_value_bytes ConstArraySpecialUndef array type");
        case ConstArraySpecialBuf:
            zig_panic("TODO buf_read_value_bytes ConstArraySpecialBuf array type");
    }
    zig_unreachable();
}

static Error buf_read_value_bytes(IrAnalyze *ira, CodeGen *codegen, AstNode *source_node, uint8_t *buf, ConstExprValue *val) {
    Error err;
    assert(val->special == ConstValSpecialStatic);
    switch (val->type->id) {
        case ZigTypeIdInvalid:
        case ZigTypeIdMetaType:
        case ZigTypeIdOpaque:
        case ZigTypeIdBoundFn:
        case ZigTypeIdArgTuple:
        case ZigTypeIdNamespace:
        case ZigTypeIdUnreachable:
        case ZigTypeIdComptimeFloat:
        case ZigTypeIdComptimeInt:
        case ZigTypeIdUndefined:
        case ZigTypeIdNull:
        case ZigTypeIdPromise:
            zig_unreachable();
        case ZigTypeIdVoid:
            return ErrorNone;
        case ZigTypeIdBool:
            val->data.x_bool = (buf[0] != 0);
            return ErrorNone;
        case ZigTypeIdInt:
            bigint_read_twos_complement(&val->data.x_bigint, buf, val->type->data.integral.bit_count,
                    codegen->is_big_endian, val->type->data.integral.is_signed);
            return ErrorNone;
        case ZigTypeIdFloat:
            float_read_ieee597(val, buf, codegen->is_big_endian);
            return ErrorNone;
        case ZigTypeIdPointer:
            {
                val->data.x_ptr.special = ConstPtrSpecialHardCodedAddr;
                BigInt bn;
                bigint_read_twos_complement(&bn, buf, codegen->builtin_types.entry_usize->data.integral.bit_count,
                        codegen->is_big_endian, false);
                val->data.x_ptr.data.hard_coded_addr.addr = bigint_as_unsigned(&bn);
                return ErrorNone;
            }
        case ZigTypeIdArray:
            return buf_read_value_bytes_array(ira, codegen, source_node, buf, val, val->type->data.array.child_type,
                    val->type->data.array.len);
        case ZigTypeIdVector:
            return buf_read_value_bytes_array(ira, codegen, source_node, buf, val, val->type->data.vector.elem_type,
                    val->type->data.vector.len);
        case ZigTypeIdEnum:
            switch (val->type->data.enumeration.layout) {
                case ContainerLayoutAuto:
                    zig_panic("TODO buf_read_value_bytes enum auto");
                case ContainerLayoutPacked:
                    zig_panic("TODO buf_read_value_bytes enum packed");
                case ContainerLayoutExtern: {
                    ZigType *tag_int_type = val->type->data.enumeration.tag_int_type;
                    assert(tag_int_type->id == ZigTypeIdInt);
                    bigint_read_twos_complement(&val->data.x_enum_tag, buf, tag_int_type->data.integral.bit_count,
                            codegen->is_big_endian, tag_int_type->data.integral.is_signed);
                    return ErrorNone;
                }
            }
            zig_unreachable();
        case ZigTypeIdStruct:
            switch (val->type->data.structure.layout) {
                case ContainerLayoutAuto: {
                    ErrorMsg *msg = opt_ir_add_error_node(ira, codegen, source_node,
                        buf_sprintf("non-extern, non-packed struct '%s' cannot have its bytes reinterpreted",
                            buf_ptr(&val->type->name)));
                    add_error_note(codegen, msg, val->type->data.structure.decl_node,
                            buf_sprintf("declared here"));
                    return ErrorSemanticAnalyzeFail;
                }
                case ContainerLayoutExtern: {
                    size_t src_field_count = val->type->data.structure.src_field_count;
                    val->data.x_struct.fields = create_const_vals(src_field_count);
                    for (size_t field_i = 0; field_i < src_field_count; field_i += 1) {
                        ConstExprValue *field_val = &val->data.x_struct.fields[field_i];
                        field_val->special = ConstValSpecialStatic;
                        TypeStructField *type_field = &val->type->data.structure.fields[field_i];
                        field_val->type = type_field->type_entry;
                        if (type_field->gen_index == SIZE_MAX)
                            continue;
                        size_t offset = LLVMOffsetOfElement(codegen->target_data_ref, val->type->type_ref,
                                type_field->gen_index);
                        uint8_t *new_buf = buf + offset;
                        if ((err = buf_read_value_bytes(ira, codegen, source_node, new_buf, field_val)))
                            return err;
                    }
                    return ErrorNone;
                }
                case ContainerLayoutPacked: {
                    size_t src_field_count = val->type->data.structure.src_field_count;
                    val->data.x_struct.fields = create_const_vals(src_field_count);
                    size_t gen_field_count = val->type->data.structure.gen_field_count;
                    size_t gen_i = 0;
                    size_t src_i = 0;
                    size_t offset = 0;
                    bool is_big_endian = codegen->is_big_endian;
                    uint8_t child_buf_prealloc[16];
                    size_t child_buf_len = 16;
                    uint8_t *child_buf = child_buf_prealloc;
                    while (gen_i < gen_field_count) {
                        LLVMTypeRef gen_llvm_int_type = LLVMStructGetTypeAtIndex(val->type->type_ref,
                                    (unsigned)gen_i);
                        size_t big_int_bit_count = LLVMGetIntTypeWidth(gen_llvm_int_type);
                        size_t big_int_byte_count = big_int_bit_count / 8;
                        if (big_int_byte_count > child_buf_len) {
                            child_buf = allocate_nonzero<uint8_t>(big_int_byte_count);
                            child_buf_len = big_int_byte_count;
                        }
                        BigInt big_int;
                        bigint_read_twos_complement(&big_int, buf + offset, big_int_bit_count, is_big_endian, false);
                        while (src_i < src_field_count) {
                            TypeStructField *field = &val->type->data.structure.fields[src_i];
                            assert(field->gen_index != SIZE_MAX);
                            if (field->gen_index != gen_i)
                                break;
                            ConstExprValue *field_val = &val->data.x_struct.fields[src_i];
                            field_val->special = ConstValSpecialStatic;
                            field_val->type = field->type_entry;
                            uint32_t packed_bits_size = type_size_bits(codegen, field->type_entry);

                            BigInt child_val;
                            if (is_big_endian) {
                                zig_panic("TODO buf_read_value_bytes packed struct big endian");
                            } else {
                                BigInt packed_bits_size_bi;
                                bigint_init_unsigned(&packed_bits_size_bi, packed_bits_size);
                                bigint_truncate(&child_val, &big_int, packed_bits_size, false);
                                BigInt tmp;
                                bigint_shr(&tmp, &big_int, &packed_bits_size_bi);
                                big_int = tmp;
                            }

                            bigint_write_twos_complement(&child_val, child_buf, big_int_bit_count, is_big_endian);
                            if ((err = buf_read_value_bytes(ira, codegen, source_node, child_buf, field_val))) {
                                return err;
                            }

                            src_i += 1;
                        }
                        offset += big_int_byte_count;
                        gen_i += 1;
                    }
                    return ErrorNone;
                }
            }
            zig_unreachable();
        case ZigTypeIdOptional:
            zig_panic("TODO buf_read_value_bytes maybe type");
        case ZigTypeIdErrorUnion:
            zig_panic("TODO buf_read_value_bytes error union");
        case ZigTypeIdErrorSet:
            zig_panic("TODO buf_read_value_bytes pure error type");
        case ZigTypeIdFn:
            zig_panic("TODO buf_read_value_bytes fn type");
        case ZigTypeIdUnion:
            zig_panic("TODO buf_read_value_bytes union type");
    }
    zig_unreachable();
}

static bool type_can_bit_cast(ZigType *t) {
    switch (t->id) {
        case ZigTypeIdInvalid:
            zig_unreachable();
        case ZigTypeIdMetaType:
        case ZigTypeIdOpaque:
        case ZigTypeIdBoundFn:
        case ZigTypeIdArgTuple:
        case ZigTypeIdNamespace:
        case ZigTypeIdUnreachable:
        case ZigTypeIdComptimeFloat:
        case ZigTypeIdComptimeInt:
        case ZigTypeIdUndefined:
        case ZigTypeIdNull:
        case ZigTypeIdPointer:
            return false;
        default:
            // TODO list these types out explicitly, there are probably some other invalid ones here
            return true;
    }
}

static IrInstruction *ir_analyze_bit_cast(IrAnalyze *ira, IrInstruction *source_instr, IrInstruction *value,
        ZigType *dest_type)
{
    Error err;

    ZigType *src_type = value->value.type;
    assert(get_codegen_ptr_type(src_type) == nullptr);
    assert(type_can_bit_cast(src_type));
    assert(get_codegen_ptr_type(dest_type) == nullptr);
    assert(type_can_bit_cast(dest_type));

    if ((err = type_resolve(ira->codegen, dest_type, ResolveStatusSizeKnown)))
        return ira->codegen->invalid_instruction;

    if ((err = type_resolve(ira->codegen, src_type, ResolveStatusSizeKnown)))
        return ira->codegen->invalid_instruction;


    uint64_t dest_size_bytes = type_size(ira->codegen, dest_type);
    uint64_t src_size_bytes = type_size(ira->codegen, src_type);
    if (dest_size_bytes != src_size_bytes) {
        ir_add_error(ira, source_instr,
            buf_sprintf("destination type '%s' has size %" ZIG_PRI_u64 " but source type '%s' has size %" ZIG_PRI_u64,
                buf_ptr(&dest_type->name), dest_size_bytes,
                buf_ptr(&src_type->name), src_size_bytes));
        return ira->codegen->invalid_instruction;
    }

    uint64_t dest_size_bits = type_size_bits(ira->codegen, dest_type);
    uint64_t src_size_bits = type_size_bits(ira->codegen, src_type);
    if (dest_size_bits != src_size_bits) {
        ir_add_error(ira, source_instr,
            buf_sprintf("destination type '%s' has %" ZIG_PRI_u64 " bits but source type '%s' has %" ZIG_PRI_u64 " bits",
                buf_ptr(&dest_type->name), dest_size_bits,
                buf_ptr(&src_type->name), src_size_bits));
        return ira->codegen->invalid_instruction;
    }

    if (instr_is_comptime(value)) {
        ConstExprValue *val = ir_resolve_const(ira, value, UndefBad);
        if (!val)
            return ira->codegen->invalid_instruction;

        IrInstruction *result = ir_const(ira, source_instr, dest_type);
        uint8_t *buf = allocate_nonzero<uint8_t>(src_size_bytes);
        buf_write_value_bytes(ira->codegen, buf, val);
        if ((err = buf_read_value_bytes(ira, ira->codegen, source_instr->source_node, buf, &result->value)))
            return ira->codegen->invalid_instruction;
        return result;
    }

    IrInstruction *result = ir_build_bit_cast_gen(ira, source_instr, value, dest_type);
    if (handle_is_ptr(dest_type) && !handle_is_ptr(src_type)) {
        ir_add_alloca(ira, result, dest_type);
    }
    return result;
}

static IrInstruction *ir_analyze_instruction_bit_cast(IrAnalyze *ira, IrInstructionBitCast *instruction) {
    IrInstruction *dest_type_value = instruction->dest_type->child;
    ZigType *dest_type = ir_resolve_type(ira, dest_type_value);
    if (type_is_invalid(dest_type))
        return ira->codegen->invalid_instruction;

    IrInstruction *value = instruction->value->child;
    ZigType *src_type = value->value.type;
    if (type_is_invalid(src_type))
        return ira->codegen->invalid_instruction;

    if (get_codegen_ptr_type(src_type) != nullptr) {
        ir_add_error(ira, value,
            buf_sprintf("unable to @bitCast from pointer type '%s'", buf_ptr(&src_type->name)));
        return ira->codegen->invalid_instruction;
    }

    if (!type_can_bit_cast(src_type)) {
        ir_add_error(ira, dest_type_value,
                buf_sprintf("unable to @bitCast from type '%s'", buf_ptr(&src_type->name)));
        return ira->codegen->invalid_instruction;
    }

    if (get_codegen_ptr_type(dest_type) != nullptr) {
        ir_add_error(ira, dest_type_value,
                buf_sprintf("unable to @bitCast to pointer type '%s'", buf_ptr(&dest_type->name)));
        return ira->codegen->invalid_instruction;
    }

    if (!type_can_bit_cast(dest_type)) {
        ir_add_error(ira, dest_type_value,
                buf_sprintf("unable to @bitCast to type '%s'", buf_ptr(&dest_type->name)));
        return ira->codegen->invalid_instruction;
    }

    return ir_analyze_bit_cast(ira, &instruction->base, value, dest_type);
}

static IrInstruction *ir_analyze_int_to_ptr(IrAnalyze *ira, IrInstruction *source_instr, IrInstruction *target,
        ZigType *ptr_type)
{
    assert(get_src_ptr_type(ptr_type) != nullptr);
    assert(type_has_bits(ptr_type));

    IrInstruction *casted_int = ir_implicit_cast(ira, target, ira->codegen->builtin_types.entry_usize);
    if (type_is_invalid(casted_int->value.type))
        return ira->codegen->invalid_instruction;

    if (instr_is_comptime(casted_int)) {
        ConstExprValue *val = ir_resolve_const(ira, casted_int, UndefBad);
        if (!val)
            return ira->codegen->invalid_instruction;

        IrInstruction *result = ir_const(ira, source_instr, ptr_type);
        result->value.data.x_ptr.special = ConstPtrSpecialHardCodedAddr;
        result->value.data.x_ptr.mut = ConstPtrMutRuntimeVar;
        result->value.data.x_ptr.data.hard_coded_addr.addr = bigint_as_unsigned(&val->data.x_bigint);
        return result;
    }

    IrInstruction *result = ir_build_int_to_ptr(&ira->new_irb, source_instr->scope,
            source_instr->source_node, nullptr, casted_int);
    result->value.type = ptr_type;
    return result;
}

static IrInstruction *ir_analyze_instruction_int_to_ptr(IrAnalyze *ira, IrInstructionIntToPtr *instruction) {
    Error err;
    IrInstruction *dest_type_value = instruction->dest_type->child;
    ZigType *dest_type = ir_resolve_type(ira, dest_type_value);
    if (type_is_invalid(dest_type))
        return ira->codegen->invalid_instruction;

    // We explicitly check for the size, so we can use get_src_ptr_type
    if (get_src_ptr_type(dest_type) == nullptr) {
        ir_add_error(ira, dest_type_value, buf_sprintf("expected pointer, found '%s'", buf_ptr(&dest_type->name)));
        return ira->codegen->invalid_instruction;
    }

    if ((err = type_resolve(ira->codegen, dest_type, ResolveStatusZeroBitsKnown)))
        return ira->codegen->invalid_instruction;
    if (!type_has_bits(dest_type)) {
        ir_add_error(ira, dest_type_value,
                buf_sprintf("type '%s' has 0 bits and cannot store information", buf_ptr(&dest_type->name)));
        return ira->codegen->invalid_instruction;
    }


    IrInstruction *target = instruction->target->child;
    if (type_is_invalid(target->value.type))
        return ira->codegen->invalid_instruction;

    return ir_analyze_int_to_ptr(ira, &instruction->base, target, dest_type);
}

static IrInstruction *ir_analyze_instruction_decl_ref(IrAnalyze *ira,
        IrInstructionDeclRef *instruction)
{
    Tld *tld = instruction->tld;
    LVal lval = instruction->lval;

    resolve_top_level_decl(ira->codegen, tld, lval == LValPtr, instruction->base.source_node);
    if (tld->resolution == TldResolutionInvalid)
        return ira->codegen->invalid_instruction;

    switch (tld->id) {
        case TldIdContainer:
        case TldIdCompTime:
            zig_unreachable();
        case TldIdVar: {
            TldVar *tld_var = (TldVar *)tld;
            ZigVar *var = tld_var->var;

            IrInstruction *var_ptr = ir_get_var_ptr(ira, &instruction->base, var);
            if (type_is_invalid(var_ptr->value.type))
                return ira->codegen->invalid_instruction;

            if (tld_var->extern_lib_name != nullptr) {
                add_link_lib_symbol(ira, tld_var->extern_lib_name, &var->name, instruction->base.source_node);
            }

            if (lval == LValPtr) {
                return var_ptr;
            } else {
                return ir_get_deref(ira, &instruction->base, var_ptr);
            }
        }
        case TldIdFn: {
            TldFn *tld_fn = (TldFn *)tld;
            ZigFn *fn_entry = tld_fn->fn_entry;
            assert(fn_entry->type_entry);

            if (tld_fn->extern_lib_name != nullptr) {
                add_link_lib_symbol(ira, tld_fn->extern_lib_name, &fn_entry->symbol_name, instruction->base.source_node);
            }

            IrInstruction *ref_instruction = ir_create_const_fn(&ira->new_irb, instruction->base.scope,
                    instruction->base.source_node, fn_entry);
            if (lval == LValPtr) {
                return ir_get_ref(ira, &instruction->base, ref_instruction, true, false);
            } else {
                return ref_instruction;
            }
        }
    }
    zig_unreachable();
}

static IrInstruction *ir_analyze_instruction_ptr_to_int(IrAnalyze *ira, IrInstructionPtrToInt *instruction) {
    IrInstruction *target = instruction->target->child;
    if (type_is_invalid(target->value.type))
        return ira->codegen->invalid_instruction;

    ZigType *usize = ira->codegen->builtin_types.entry_usize;

    // We check size explicitly so we can use get_src_ptr_type here.
    if (get_src_ptr_type(target->value.type) == nullptr) {
        ir_add_error(ira, target,
                buf_sprintf("expected pointer, found '%s'", buf_ptr(&target->value.type->name)));
        return ira->codegen->invalid_instruction;
    }

    if (!type_has_bits(target->value.type)) {
        ir_add_error(ira, target,
                buf_sprintf("pointer to size 0 type has no address"));
        return ira->codegen->invalid_instruction;
    }

    if (instr_is_comptime(target)) {
        ConstExprValue *val = ir_resolve_const(ira, target, UndefBad);
        if (!val)
            return ira->codegen->invalid_instruction;
        if (val->type->id == ZigTypeIdPointer && val->data.x_ptr.special == ConstPtrSpecialHardCodedAddr) {
            IrInstruction *result = ir_const(ira, &instruction->base, usize);
            bigint_init_unsigned(&result->value.data.x_bigint, val->data.x_ptr.data.hard_coded_addr.addr);
            result->value.type = usize;
            return result;
        }
    }

    IrInstruction *result = ir_build_ptr_to_int(&ira->new_irb, instruction->base.scope,
            instruction->base.source_node, target);
    result->value.type = usize;
    return result;
}

static IrInstruction *ir_analyze_instruction_ptr_type(IrAnalyze *ira, IrInstructionPtrType *instruction) {
    Error err;
    ZigType *child_type = ir_resolve_type(ira, instruction->child_type->child);
    if (type_is_invalid(child_type))
        return ira->codegen->invalid_instruction;

    if (child_type->id == ZigTypeIdUnreachable) {
        ir_add_error(ira, &instruction->base, buf_sprintf("pointer to noreturn not allowed"));
        return ira->codegen->invalid_instruction;
    } else if (child_type->id == ZigTypeIdOpaque && instruction->ptr_len == PtrLenUnknown) {
        ir_add_error(ira, &instruction->base, buf_sprintf("unknown-length pointer to opaque"));
        return ira->codegen->invalid_instruction;
    } else if (instruction->ptr_len == PtrLenC) {
        if (!type_allowed_in_extern(ira->codegen, child_type)) {
            ir_add_error(ira, &instruction->base,
                buf_sprintf("C pointers cannot point to non-C-ABI-compatible type '%s'", buf_ptr(&child_type->name)));
            return ira->codegen->invalid_instruction;
        } else if (child_type->id == ZigTypeIdOpaque) {
            ir_add_error(ira, &instruction->base, buf_sprintf("C pointers cannot point opaque types"));
            return ira->codegen->invalid_instruction;
        }
    }

    uint32_t align_bytes;
    if (instruction->align_value != nullptr) {
        if (!ir_resolve_align(ira, instruction->align_value->child, &align_bytes))
            return ira->codegen->invalid_instruction;
        if ((err = type_resolve(ira->codegen, child_type, ResolveStatusAlignmentKnown)))
            return ira->codegen->invalid_instruction;
        if (!type_has_bits(child_type)) {
            align_bytes = 0;
        }
    } else {
        if ((err = type_resolve(ira->codegen, child_type, ResolveStatusZeroBitsKnown)))
            return ira->codegen->invalid_instruction;
        align_bytes = 0;
    }

    ZigType *result_type = get_pointer_to_type_extra(ira->codegen, child_type,
            instruction->is_const, instruction->is_volatile,
            instruction->ptr_len, align_bytes,
            instruction->bit_offset_start, instruction->host_int_bytes);
    return ir_const_type(ira, &instruction->base, result_type);
}

static IrInstruction *ir_analyze_instruction_align_cast(IrAnalyze *ira, IrInstructionAlignCast *instruction) {
    uint32_t align_bytes;
    IrInstruction *align_bytes_inst = instruction->align_bytes->child;
    if (!ir_resolve_align(ira, align_bytes_inst, &align_bytes))
        return ira->codegen->invalid_instruction;

    IrInstruction *target = instruction->target->child;
    if (type_is_invalid(target->value.type))
        return ira->codegen->invalid_instruction;

    IrInstruction *result = ir_align_cast(ira, target, align_bytes, true);
    if (type_is_invalid(result->value.type))
        return ira->codegen->invalid_instruction;

    return result;
}

static IrInstruction *ir_analyze_instruction_opaque_type(IrAnalyze *ira, IrInstructionOpaqueType *instruction) {
    Buf *name = get_anon_type_name(ira->codegen, ira->new_irb.exec, "opaque", instruction->base.source_node);
    ZigType *result_type = get_opaque_type(ira->codegen, instruction->base.scope, instruction->base.source_node,
            buf_ptr(name));
    return ir_const_type(ira, &instruction->base, result_type);
}

static IrInstruction *ir_analyze_instruction_set_align_stack(IrAnalyze *ira, IrInstructionSetAlignStack *instruction) {
    uint32_t align_bytes;
    IrInstruction *align_bytes_inst = instruction->align_bytes->child;
    if (!ir_resolve_align(ira, align_bytes_inst, &align_bytes))
        return ira->codegen->invalid_instruction;

    if (align_bytes > 256) {
        ir_add_error(ira, &instruction->base, buf_sprintf("attempt to @setAlignStack(%" PRIu32 "); maximum is 256", align_bytes));
        return ira->codegen->invalid_instruction;
    }

    ZigFn *fn_entry = exec_fn_entry(ira->new_irb.exec);
    if (fn_entry == nullptr) {
        ir_add_error(ira, &instruction->base, buf_sprintf("@setAlignStack outside function"));
        return ira->codegen->invalid_instruction;
    }
    if (fn_entry->type_entry->data.fn.fn_type_id.cc == CallingConventionNaked) {
        ir_add_error(ira, &instruction->base, buf_sprintf("@setAlignStack in naked function"));
        return ira->codegen->invalid_instruction;
    }

    if (fn_entry->fn_inline == FnInlineAlways) {
        ir_add_error(ira, &instruction->base, buf_sprintf("@setAlignStack in inline function"));
        return ira->codegen->invalid_instruction;
    }

    if (fn_entry->set_alignstack_node != nullptr) {
        ErrorMsg *msg = ir_add_error_node(ira, instruction->base.source_node,
            buf_sprintf("alignstack set twice"));
        add_error_note(ira->codegen, msg, fn_entry->set_alignstack_node, buf_sprintf("first set here"));
        return ira->codegen->invalid_instruction;
    }

    fn_entry->set_alignstack_node = instruction->base.source_node;
    fn_entry->alignstack_value = align_bytes;

    return ir_const_void(ira, &instruction->base);
}

static IrInstruction *ir_analyze_instruction_arg_type(IrAnalyze *ira, IrInstructionArgType *instruction) {
    IrInstruction *fn_type_inst = instruction->fn_type->child;
    ZigType *fn_type = ir_resolve_type(ira, fn_type_inst);
    if (type_is_invalid(fn_type))
        return ira->codegen->invalid_instruction;

    IrInstruction *arg_index_inst = instruction->arg_index->child;
    uint64_t arg_index;
    if (!ir_resolve_usize(ira, arg_index_inst, &arg_index))
        return ira->codegen->invalid_instruction;

    if (fn_type->id != ZigTypeIdFn) {
        ir_add_error(ira, fn_type_inst, buf_sprintf("expected function, found '%s'", buf_ptr(&fn_type->name)));
        return ira->codegen->invalid_instruction;
    }

    FnTypeId *fn_type_id = &fn_type->data.fn.fn_type_id;
    if (arg_index >= fn_type_id->param_count) {
        ir_add_error(ira, arg_index_inst,
                buf_sprintf("arg index %" ZIG_PRI_u64 " out of bounds; '%s' has %" ZIG_PRI_usize " arguments",
                    arg_index, buf_ptr(&fn_type->name), fn_type_id->param_count));
        return ira->codegen->invalid_instruction;
    }

    ZigType *result_type = fn_type_id->param_info[arg_index].type;
    if (result_type == nullptr) {
        // Args are only unresolved if our function is generic.
        assert(fn_type->data.fn.is_generic);

        ir_add_error(ira, arg_index_inst,
            buf_sprintf("@ArgType could not resolve the type of arg %" ZIG_PRI_u64 " because '%s' is generic",
                arg_index, buf_ptr(&fn_type->name)));
        return ira->codegen->invalid_instruction;
    }
    return ir_const_type(ira, &instruction->base, result_type);
}

static IrInstruction *ir_analyze_instruction_tag_type(IrAnalyze *ira, IrInstructionTagType *instruction) {
    Error err;
    IrInstruction *target_inst = instruction->target->child;
    ZigType *enum_type = ir_resolve_type(ira, target_inst);
    if (type_is_invalid(enum_type))
        return ira->codegen->invalid_instruction;

    if (enum_type->id == ZigTypeIdEnum) {
        if ((err = ensure_complete_type(ira->codegen, enum_type)))
            return ira->codegen->invalid_instruction;

        return ir_const_type(ira, &instruction->base, enum_type->data.enumeration.tag_int_type);
    } else if (enum_type->id == ZigTypeIdUnion) {
        ZigType *tag_type = ir_resolve_union_tag_type(ira, instruction->target, enum_type);
        if (type_is_invalid(tag_type))
            return ira->codegen->invalid_instruction;
        return ir_const_type(ira, &instruction->base, tag_type);
    } else {
        ir_add_error(ira, target_inst, buf_sprintf("expected enum or union, found '%s'",
            buf_ptr(&enum_type->name)));
        return ira->codegen->invalid_instruction;
    }
}

static IrInstruction *ir_analyze_instruction_cancel(IrAnalyze *ira, IrInstructionCancel *instruction) {
    IrInstruction *target_inst = instruction->target->child;
    if (type_is_invalid(target_inst->value.type))
        return ira->codegen->invalid_instruction;
    IrInstruction *casted_target = ir_implicit_cast(ira, target_inst, ira->codegen->builtin_types.entry_promise);
    if (type_is_invalid(casted_target->value.type))
        return ira->codegen->invalid_instruction;

    IrInstruction *result = ir_build_cancel(&ira->new_irb, instruction->base.scope, instruction->base.source_node, casted_target);
    result->value.type = ira->codegen->builtin_types.entry_void;
    result->value.special = ConstValSpecialStatic;
    return result;
}

static IrInstruction *ir_analyze_instruction_coro_id(IrAnalyze *ira, IrInstructionCoroId *instruction) {
    IrInstruction *promise_ptr = instruction->promise_ptr->child;
    if (type_is_invalid(promise_ptr->value.type))
        return ira->codegen->invalid_instruction;

    IrInstruction *result = ir_build_coro_id(&ira->new_irb, instruction->base.scope, instruction->base.source_node,
            promise_ptr);
    result->value.type = ira->codegen->builtin_types.entry_usize;
    return result;
}

static IrInstruction *ir_analyze_instruction_coro_alloc(IrAnalyze *ira, IrInstructionCoroAlloc *instruction) {
    IrInstruction *coro_id = instruction->coro_id->child;
    if (type_is_invalid(coro_id->value.type))
        return ira->codegen->invalid_instruction;

    IrInstruction *result = ir_build_coro_alloc(&ira->new_irb, instruction->base.scope, instruction->base.source_node,
            coro_id);
    result->value.type = ira->codegen->builtin_types.entry_bool;
    return result;
}

static IrInstruction *ir_analyze_instruction_coro_size(IrAnalyze *ira, IrInstructionCoroSize *instruction) {
    IrInstruction *result = ir_build_coro_size(&ira->new_irb, instruction->base.scope, instruction->base.source_node);
    result->value.type = ira->codegen->builtin_types.entry_usize;
    return result;
}

static IrInstruction *ir_analyze_instruction_coro_begin(IrAnalyze *ira, IrInstructionCoroBegin *instruction) {
    IrInstruction *coro_id = instruction->coro_id->child;
    if (type_is_invalid(coro_id->value.type))
        return ira->codegen->invalid_instruction;

    IrInstruction *coro_mem_ptr = instruction->coro_mem_ptr->child;
    if (type_is_invalid(coro_mem_ptr->value.type))
        return ira->codegen->invalid_instruction;

    ZigFn *fn_entry = exec_fn_entry(ira->new_irb.exec);
    assert(fn_entry != nullptr);
    IrInstruction *result = ir_build_coro_begin(&ira->new_irb, instruction->base.scope, instruction->base.source_node,
            coro_id, coro_mem_ptr);
    result->value.type = get_promise_type(ira->codegen, fn_entry->type_entry->data.fn.fn_type_id.return_type);
    return result;
}

static IrInstruction *ir_analyze_instruction_get_implicit_allocator(IrAnalyze *ira, IrInstructionGetImplicitAllocator *instruction) {
    return ir_get_implicit_allocator(ira, &instruction->base, instruction->id);
}

static IrInstruction *ir_analyze_instruction_coro_alloc_fail(IrAnalyze *ira, IrInstructionCoroAllocFail *instruction) {
    IrInstruction *err_val = instruction->err_val->child;
    if (type_is_invalid(err_val->value.type))
        return ir_unreach_error(ira);

    IrInstruction *result = ir_build_coro_alloc_fail(&ira->new_irb, instruction->base.scope, instruction->base.source_node, err_val);
    result->value.type = ira->codegen->builtin_types.entry_unreachable;
    return ir_finish_anal(ira, result);
}

static IrInstruction *ir_analyze_instruction_coro_suspend(IrAnalyze *ira, IrInstructionCoroSuspend *instruction) {
    IrInstruction *save_point = nullptr;
    if (instruction->save_point != nullptr) {
        save_point = instruction->save_point->child;
        if (type_is_invalid(save_point->value.type))
            return ira->codegen->invalid_instruction;
    }

    IrInstruction *is_final = instruction->is_final->child;
    if (type_is_invalid(is_final->value.type))
        return ira->codegen->invalid_instruction;

    IrInstruction *result = ir_build_coro_suspend(&ira->new_irb, instruction->base.scope,
            instruction->base.source_node, save_point, is_final);
    result->value.type = ira->codegen->builtin_types.entry_u8;
    return result;
}

static IrInstruction *ir_analyze_instruction_coro_end(IrAnalyze *ira, IrInstructionCoroEnd *instruction) {
    IrInstruction *result = ir_build_coro_end(&ira->new_irb, instruction->base.scope,
            instruction->base.source_node);
    result->value.type = ira->codegen->builtin_types.entry_void;
    return result;
}

static IrInstruction *ir_analyze_instruction_coro_free(IrAnalyze *ira, IrInstructionCoroFree *instruction) {
    IrInstruction *coro_id = instruction->coro_id->child;
    if (type_is_invalid(coro_id->value.type))
        return ira->codegen->invalid_instruction;

    IrInstruction *coro_handle = instruction->coro_handle->child;
    if (type_is_invalid(coro_handle->value.type))
        return ira->codegen->invalid_instruction;

    IrInstruction *result = ir_build_coro_free(&ira->new_irb, instruction->base.scope,
            instruction->base.source_node, coro_id, coro_handle);
    ZigType *ptr_type = get_pointer_to_type(ira->codegen, ira->codegen->builtin_types.entry_u8, false);
    result->value.type = get_optional_type(ira->codegen, ptr_type);
    return result;
}

static IrInstruction *ir_analyze_instruction_coro_resume(IrAnalyze *ira, IrInstructionCoroResume *instruction) {
    IrInstruction *awaiter_handle = instruction->awaiter_handle->child;
    if (type_is_invalid(awaiter_handle->value.type))
        return ira->codegen->invalid_instruction;

    IrInstruction *casted_target = ir_implicit_cast(ira, awaiter_handle, ira->codegen->builtin_types.entry_promise);
    if (type_is_invalid(casted_target->value.type))
        return ira->codegen->invalid_instruction;

    IrInstruction *result = ir_build_coro_resume(&ira->new_irb, instruction->base.scope,
            instruction->base.source_node, casted_target);
    result->value.type = ira->codegen->builtin_types.entry_void;
    return result;
}

static IrInstruction *ir_analyze_instruction_coro_save(IrAnalyze *ira, IrInstructionCoroSave *instruction) {
    IrInstruction *coro_handle = instruction->coro_handle->child;
    if (type_is_invalid(coro_handle->value.type))
        return ira->codegen->invalid_instruction;

    IrInstruction *result = ir_build_coro_save(&ira->new_irb, instruction->base.scope,
            instruction->base.source_node, coro_handle);
    result->value.type = ira->codegen->builtin_types.entry_usize;
    return result;
}

static IrInstruction *ir_analyze_instruction_coro_promise(IrAnalyze *ira, IrInstructionCoroPromise *instruction) {
    IrInstruction *coro_handle = instruction->coro_handle->child;
    if (type_is_invalid(coro_handle->value.type))
        return ira->codegen->invalid_instruction;

    if (coro_handle->value.type->id != ZigTypeIdPromise ||
        coro_handle->value.type->data.promise.result_type == nullptr)
    {
        ir_add_error(ira, &instruction->base, buf_sprintf("expected promise->T, found '%s'",
                    buf_ptr(&coro_handle->value.type->name)));
        return ira->codegen->invalid_instruction;
    }

    ZigType *coro_frame_type = get_promise_frame_type(ira->codegen,
            coro_handle->value.type->data.promise.result_type);

    IrInstruction *result = ir_build_coro_promise(&ira->new_irb, instruction->base.scope,
            instruction->base.source_node, coro_handle);
    result->value.type = get_pointer_to_type(ira->codegen, coro_frame_type, false);
    return result;
}

static IrInstruction *ir_analyze_instruction_coro_alloc_helper(IrAnalyze *ira, IrInstructionCoroAllocHelper *instruction) {
    IrInstruction *alloc_fn = instruction->alloc_fn->child;
    if (type_is_invalid(alloc_fn->value.type))
        return ira->codegen->invalid_instruction;

    IrInstruction *coro_size = instruction->coro_size->child;
    if (type_is_invalid(coro_size->value.type))
        return ira->codegen->invalid_instruction;

    IrInstruction *result = ir_build_coro_alloc_helper(&ira->new_irb, instruction->base.scope,
            instruction->base.source_node, alloc_fn, coro_size);
    ZigType *u8_ptr_type = get_pointer_to_type(ira->codegen, ira->codegen->builtin_types.entry_u8, false);
    result->value.type = get_optional_type(ira->codegen, u8_ptr_type);
    return result;
}

static ZigType *ir_resolve_atomic_operand_type(IrAnalyze *ira, IrInstruction *op) {
    ZigType *operand_type = ir_resolve_type(ira, op);
    if (type_is_invalid(operand_type))
        return ira->codegen->builtin_types.entry_invalid;

    if (operand_type->id == ZigTypeIdInt) {
        if (operand_type->data.integral.bit_count < 8) {
            ir_add_error(ira, op,
                buf_sprintf("expected integer type 8 bits or larger, found %" PRIu32 "-bit integer type",
                    operand_type->data.integral.bit_count));
            return ira->codegen->builtin_types.entry_invalid;
        }
        if (operand_type->data.integral.bit_count > ira->codegen->pointer_size_bytes * 8) {
            ir_add_error(ira, op,
                buf_sprintf("expected integer type pointer size or smaller, found %" PRIu32 "-bit integer type",
                    operand_type->data.integral.bit_count));
            return ira->codegen->builtin_types.entry_invalid;
        }
        if (!is_power_of_2(operand_type->data.integral.bit_count)) {
            ir_add_error(ira, op,
                buf_sprintf("%" PRIu32 "-bit integer type is not a power of 2", operand_type->data.integral.bit_count));
            return ira->codegen->builtin_types.entry_invalid;
        }
    } else if (get_codegen_ptr_type(operand_type) == nullptr) {
        ir_add_error(ira, op,
            buf_sprintf("expected integer or pointer type, found '%s'", buf_ptr(&operand_type->name)));
        return ira->codegen->builtin_types.entry_invalid;
    }

    return operand_type;
}

static IrInstruction *ir_analyze_instruction_atomic_rmw(IrAnalyze *ira, IrInstructionAtomicRmw *instruction) {
    ZigType *operand_type = ir_resolve_atomic_operand_type(ira, instruction->operand_type->child);
    if (type_is_invalid(operand_type))
        return ira->codegen->invalid_instruction;

    IrInstruction *ptr_inst = instruction->ptr->child;
    if (type_is_invalid(ptr_inst->value.type))
        return ira->codegen->invalid_instruction;

    // TODO let this be volatile
    ZigType *ptr_type = get_pointer_to_type(ira->codegen, operand_type, false);
    IrInstruction *casted_ptr = ir_implicit_cast(ira, ptr_inst, ptr_type);
    if (type_is_invalid(casted_ptr->value.type))
        return ira->codegen->invalid_instruction;

    AtomicRmwOp op;
    if (instruction->op == nullptr) {
        op = instruction->resolved_op;
    } else {
        if (!ir_resolve_atomic_rmw_op(ira, instruction->op->child, &op)) {
            return ira->codegen->invalid_instruction;
        }
    }

    IrInstruction *operand = instruction->operand->child;
    if (type_is_invalid(operand->value.type))
        return ira->codegen->invalid_instruction;

    IrInstruction *casted_operand = ir_implicit_cast(ira, operand, operand_type);
    if (type_is_invalid(casted_operand->value.type))
        return ira->codegen->invalid_instruction;

    AtomicOrder ordering;
    if (instruction->ordering == nullptr) {
        ordering = instruction->resolved_ordering;
    } else {
        if (!ir_resolve_atomic_order(ira, instruction->ordering->child, &ordering))
            return ira->codegen->invalid_instruction;
        if (ordering == AtomicOrderUnordered) {
            ir_add_error(ira, instruction->ordering,
                buf_sprintf("@atomicRmw atomic ordering must not be Unordered"));
            return ira->codegen->invalid_instruction;
        }
    }

    if (instr_is_comptime(casted_operand) && instr_is_comptime(casted_ptr) && casted_ptr->value.data.x_ptr.mut == ConstPtrMutComptimeVar)
    {
        zig_panic("TODO compile-time execution of atomicRmw");
    }

    IrInstruction *result = ir_build_atomic_rmw(&ira->new_irb, instruction->base.scope,
            instruction->base.source_node, nullptr, casted_ptr, nullptr, casted_operand, nullptr,
            op, ordering);
    result->value.type = operand_type;
    return result;
}

static IrInstruction *ir_analyze_instruction_atomic_load(IrAnalyze *ira, IrInstructionAtomicLoad *instruction) {
    ZigType *operand_type = ir_resolve_atomic_operand_type(ira, instruction->operand_type->child);
    if (type_is_invalid(operand_type))
        return ira->codegen->invalid_instruction;

    IrInstruction *ptr_inst = instruction->ptr->child;
    if (type_is_invalid(ptr_inst->value.type))
        return ira->codegen->invalid_instruction;

    ZigType *ptr_type = get_pointer_to_type(ira->codegen, operand_type, true);
    IrInstruction *casted_ptr = ir_implicit_cast(ira, ptr_inst, ptr_type);
    if (type_is_invalid(casted_ptr->value.type))
        return ira->codegen->invalid_instruction;

    AtomicOrder ordering;
    if (instruction->ordering == nullptr) {
        ordering = instruction->resolved_ordering;
    } else {
        if (!ir_resolve_atomic_order(ira, instruction->ordering->child, &ordering))
            return ira->codegen->invalid_instruction;
    }

    if (ordering == AtomicOrderRelease || ordering == AtomicOrderAcqRel) {
        assert(instruction->ordering != nullptr);
        ir_add_error(ira, instruction->ordering,
            buf_sprintf("@atomicLoad atomic ordering must not be Release or AcqRel"));
        return ira->codegen->invalid_instruction;
    }

    if (instr_is_comptime(casted_ptr)) {
        IrInstruction *result = ir_get_deref(ira, &instruction->base, casted_ptr);
        assert(result->value.type != nullptr);
        return result;
    }

    IrInstruction *result = ir_build_atomic_load(&ira->new_irb, instruction->base.scope,
            instruction->base.source_node, nullptr, casted_ptr, nullptr, ordering);
    result->value.type = operand_type;
    return result;
}

static IrInstruction *ir_analyze_instruction_promise_result_type(IrAnalyze *ira, IrInstructionPromiseResultType *instruction) {
    ZigType *promise_type = ir_resolve_type(ira, instruction->promise_type->child);
    if (type_is_invalid(promise_type))
        return ira->codegen->invalid_instruction;

    if (promise_type->id != ZigTypeIdPromise || promise_type->data.promise.result_type == nullptr) {
        ir_add_error(ira, &instruction->base, buf_sprintf("expected promise->T, found '%s'",
                    buf_ptr(&promise_type->name)));
        return ira->codegen->invalid_instruction;
    }

    return ir_const_type(ira, &instruction->base, promise_type->data.promise.result_type);
}

static IrInstruction *ir_analyze_instruction_await_bookkeeping(IrAnalyze *ira, IrInstructionAwaitBookkeeping *instruction) {
    ZigType *promise_result_type = ir_resolve_type(ira, instruction->promise_result_type->child);
    if (type_is_invalid(promise_result_type))
        return ira->codegen->invalid_instruction;

    ZigFn *fn_entry = exec_fn_entry(ira->new_irb.exec);
    assert(fn_entry != nullptr);

    if (type_can_fail(promise_result_type)) {
        fn_entry->calls_or_awaits_errorable_fn = true;
    }

    return ir_const_void(ira, &instruction->base);
}

static IrInstruction *ir_analyze_instruction_merge_err_ret_traces(IrAnalyze *ira,
        IrInstructionMergeErrRetTraces *instruction)
{
    IrInstruction *coro_promise_ptr = instruction->coro_promise_ptr->child;
    if (type_is_invalid(coro_promise_ptr->value.type))
        return ira->codegen->invalid_instruction;

    assert(coro_promise_ptr->value.type->id == ZigTypeIdPointer);
    ZigType *promise_frame_type = coro_promise_ptr->value.type->data.pointer.child_type;
    assert(promise_frame_type->id == ZigTypeIdStruct);
    ZigType *promise_result_type = promise_frame_type->data.structure.fields[1].type_entry;

    if (!type_can_fail(promise_result_type)) {
        return ir_const_void(ira, &instruction->base);
    }

    IrInstruction *src_err_ret_trace_ptr = instruction->src_err_ret_trace_ptr->child;
    if (type_is_invalid(src_err_ret_trace_ptr->value.type))
        return ira->codegen->invalid_instruction;

    IrInstruction *dest_err_ret_trace_ptr = instruction->dest_err_ret_trace_ptr->child;
    if (type_is_invalid(dest_err_ret_trace_ptr->value.type))
        return ira->codegen->invalid_instruction;

    IrInstruction *result = ir_build_merge_err_ret_traces(&ira->new_irb, instruction->base.scope,
            instruction->base.source_node, coro_promise_ptr, src_err_ret_trace_ptr, dest_err_ret_trace_ptr);
    result->value.type = ira->codegen->builtin_types.entry_void;
    return result;
}

static IrInstruction *ir_analyze_instruction_save_err_ret_addr(IrAnalyze *ira, IrInstructionSaveErrRetAddr *instruction) {
    IrInstruction *result = ir_build_save_err_ret_addr(&ira->new_irb, instruction->base.scope,
            instruction->base.source_node);
    result->value.type = ira->codegen->builtin_types.entry_void;
    return result;
}

static IrInstruction *ir_analyze_instruction_mark_err_ret_trace_ptr(IrAnalyze *ira, IrInstructionMarkErrRetTracePtr *instruction) {
    IrInstruction *err_ret_trace_ptr = instruction->err_ret_trace_ptr->child;
    if (type_is_invalid(err_ret_trace_ptr->value.type))
        return ira->codegen->invalid_instruction;

    IrInstruction *result = ir_build_mark_err_ret_trace_ptr(&ira->new_irb, instruction->base.scope,
            instruction->base.source_node, err_ret_trace_ptr);
    result->value.type = ira->codegen->builtin_types.entry_void;
    return result;
}

static IrInstruction *ir_analyze_instruction_sqrt(IrAnalyze *ira, IrInstructionSqrt *instruction) {
    ZigType *float_type = ir_resolve_type(ira, instruction->type->child);
    if (type_is_invalid(float_type))
        return ira->codegen->invalid_instruction;

    IrInstruction *op = instruction->op->child;
    if (type_is_invalid(op->value.type))
        return ira->codegen->invalid_instruction;

    bool ok_type = float_type->id == ZigTypeIdComptimeFloat || float_type->id == ZigTypeIdFloat;
    if (!ok_type) {
        ir_add_error(ira, instruction->type, buf_sprintf("@sqrt does not support type '%s'", buf_ptr(&float_type->name)));
        return ira->codegen->invalid_instruction;
    }

    IrInstruction *casted_op = ir_implicit_cast(ira, op, float_type);
    if (type_is_invalid(casted_op->value.type))
        return ira->codegen->invalid_instruction;

    if (instr_is_comptime(casted_op)) {
        ConstExprValue *val = ir_resolve_const(ira, casted_op, UndefBad);
        if (!val)
            return ira->codegen->invalid_instruction;

        IrInstruction *result = ir_const(ira, &instruction->base, float_type);
        ConstExprValue *out_val = &result->value;

        if (float_type->id == ZigTypeIdComptimeFloat) {
            bigfloat_sqrt(&out_val->data.x_bigfloat, &val->data.x_bigfloat);
        } else if (float_type->id == ZigTypeIdFloat) {
            switch (float_type->data.floating.bit_count) {
                case 16:
                    out_val->data.x_f16 = f16_sqrt(val->data.x_f16);
                    break;
                case 32:
                    out_val->data.x_f32 = sqrtf(val->data.x_f32);
                    break;
                case 64:
                    out_val->data.x_f64 = sqrt(val->data.x_f64);
                    break;
                case 128:
                    f128M_sqrt(&val->data.x_f128, &out_val->data.x_f128);
                    break;
                default:
                    zig_unreachable();
            }
        } else {
            zig_unreachable();
        }

        return result;
    }

    assert(float_type->id == ZigTypeIdFloat);
    if (float_type->data.floating.bit_count != 16 &&
        float_type->data.floating.bit_count != 32 &&
        float_type->data.floating.bit_count != 64) {
        ir_add_error(ira, instruction->type, buf_sprintf("compiler TODO: add implementation of sqrt for '%s'", buf_ptr(&float_type->name)));
        return ira->codegen->invalid_instruction;
    }

    IrInstruction *result = ir_build_sqrt(&ira->new_irb, instruction->base.scope,
            instruction->base.source_node, nullptr, casted_op);
    result->value.type = float_type;
    return result;
}

static IrInstruction *ir_analyze_instruction_bswap(IrAnalyze *ira, IrInstructionBswap *instruction) {
    ZigType *int_type = ir_resolve_type(ira, instruction->type->child);
    if (type_is_invalid(int_type))
        return ira->codegen->invalid_instruction;

    IrInstruction *op = instruction->op->child;
    if (type_is_invalid(op->value.type))
        return ira->codegen->invalid_instruction;

    if (int_type->id != ZigTypeIdInt) {
        ir_add_error(ira, instruction->type,
            buf_sprintf("expected integer type, found '%s'", buf_ptr(&int_type->name)));
        return ira->codegen->invalid_instruction;
    }

    if (int_type->data.integral.bit_count % 8 != 0) {
        ir_add_error(ira, instruction->type,
            buf_sprintf("@bswap integer type '%s' has %" PRIu32 " bits which is not evenly divisible by 8",
                buf_ptr(&int_type->name), int_type->data.integral.bit_count));
        return ira->codegen->invalid_instruction;
    }

    IrInstruction *casted_op = ir_implicit_cast(ira, op, int_type);
    if (type_is_invalid(casted_op->value.type))
        return ira->codegen->invalid_instruction;

    if (int_type->data.integral.bit_count == 0) {
        IrInstruction *result = ir_const(ira, &instruction->base, int_type);
        bigint_init_unsigned(&result->value.data.x_bigint, 0);
        return result;
    }

    if (int_type->data.integral.bit_count == 8) {
        return casted_op;
    }

    if (instr_is_comptime(casted_op)) {
        ConstExprValue *val = ir_resolve_const(ira, casted_op, UndefBad);
        if (!val)
            return ira->codegen->invalid_instruction;

        IrInstruction *result = ir_const(ira, &instruction->base, int_type);
        size_t buf_size = int_type->data.integral.bit_count / 8;
        uint8_t *buf = allocate_nonzero<uint8_t>(buf_size);
        bigint_write_twos_complement(&val->data.x_bigint, buf, int_type->data.integral.bit_count, true);
        bigint_read_twos_complement(&result->value.data.x_bigint, buf, int_type->data.integral.bit_count, false,
                int_type->data.integral.is_signed);
        return result;
    }

    IrInstruction *result = ir_build_bswap(&ira->new_irb, instruction->base.scope,
            instruction->base.source_node, nullptr, casted_op);
    result->value.type = int_type;
    return result;
}

static IrInstruction *ir_analyze_instruction_bit_reverse(IrAnalyze *ira, IrInstructionBitReverse *instruction) {
    ZigType *int_type = ir_resolve_type(ira, instruction->type->child);
    if (type_is_invalid(int_type))
        return ira->codegen->invalid_instruction;

    IrInstruction *op = instruction->op->child;
    if (type_is_invalid(op->value.type))
        return ira->codegen->invalid_instruction;

    if (int_type->id != ZigTypeIdInt) {
        ir_add_error(ira, instruction->type,
            buf_sprintf("expected integer type, found '%s'", buf_ptr(&int_type->name)));
        return ira->codegen->invalid_instruction;
    }

    IrInstruction *casted_op = ir_implicit_cast(ira, op, int_type);
    if (type_is_invalid(casted_op->value.type))
        return ira->codegen->invalid_instruction;

    if (int_type->data.integral.bit_count == 0) {
        IrInstruction *result = ir_const(ira, &instruction->base, int_type);
        bigint_init_unsigned(&result->value.data.x_bigint, 0);
        return result;
    }

    if (instr_is_comptime(casted_op)) {
        ConstExprValue *val = ir_resolve_const(ira, casted_op, UndefBad);
        if (!val)
            return ira->codegen->invalid_instruction;

        IrInstruction *result = ir_const(ira, &instruction->base, int_type);
        size_t num_bits = int_type->data.integral.bit_count;
        size_t buf_size = (num_bits + 7) / 8;
        uint8_t *comptime_buf = allocate_nonzero<uint8_t>(buf_size);
        uint8_t *result_buf = allocate_nonzero<uint8_t>(buf_size);
        memset(comptime_buf,0,buf_size);
        memset(result_buf,0,buf_size);

        bigint_write_twos_complement(&val->data.x_bigint,comptime_buf,num_bits,ira->codegen->is_big_endian);

        size_t bit_i = 0;
        size_t bit_rev_i = num_bits - 1;
        for (; bit_i < num_bits; bit_i++, bit_rev_i--) {
            if (comptime_buf[bit_i / 8] & (1 << (bit_i % 8))) {
                result_buf[bit_rev_i / 8] |= (1 << (bit_rev_i % 8));
            }
        }

        bigint_read_twos_complement(&result->value.data.x_bigint,
                                    result_buf,
                                    int_type->data.integral.bit_count,
                                    ira->codegen->is_big_endian,
                                    int_type->data.integral.is_signed);

        return result;
    }

    IrInstruction *result = ir_build_bit_reverse(&ira->new_irb, instruction->base.scope,
            instruction->base.source_node, nullptr, casted_op);
    result->value.type = int_type;
    return result;
}


static IrInstruction *ir_analyze_instruction_enum_to_int(IrAnalyze *ira, IrInstructionEnumToInt *instruction) {
    IrInstruction *target = instruction->target->child;
    if (type_is_invalid(target->value.type))
        return ira->codegen->invalid_instruction;

    return ir_analyze_enum_to_int(ira, &instruction->base, target);
}

static IrInstruction *ir_analyze_instruction_int_to_enum(IrAnalyze *ira, IrInstructionIntToEnum *instruction) {
    Error err;
    IrInstruction *dest_type_value = instruction->dest_type->child;
    ZigType *dest_type = ir_resolve_type(ira, dest_type_value);
    if (type_is_invalid(dest_type))
        return ira->codegen->invalid_instruction;

    if (dest_type->id != ZigTypeIdEnum) {
        ir_add_error(ira, instruction->dest_type,
            buf_sprintf("expected enum, found type '%s'", buf_ptr(&dest_type->name)));
        return ira->codegen->invalid_instruction;
    }

    if ((err = type_resolve(ira->codegen, dest_type, ResolveStatusZeroBitsKnown)))
        return ira->codegen->invalid_instruction;

    ZigType *tag_type = dest_type->data.enumeration.tag_int_type;

    IrInstruction *target = instruction->target->child;
    if (type_is_invalid(target->value.type))
        return ira->codegen->invalid_instruction;

    IrInstruction *casted_target = ir_implicit_cast(ira, target, tag_type);
    if (type_is_invalid(casted_target->value.type))
        return ira->codegen->invalid_instruction;

    return ir_analyze_int_to_enum(ira, &instruction->base, casted_target, dest_type);
}

static IrInstruction *ir_analyze_instruction_check_runtime_scope(IrAnalyze *ira, IrInstructionCheckRuntimeScope *instruction) {
    IrInstruction *block_comptime_inst = instruction->scope_is_comptime->child;
    bool scope_is_comptime;
    if (!ir_resolve_bool(ira, block_comptime_inst, &scope_is_comptime))
        return ira->codegen->invalid_instruction;

    IrInstruction *is_comptime_inst = instruction->is_comptime->child;
    bool is_comptime;
    if (!ir_resolve_bool(ira, is_comptime_inst, &is_comptime))
        return ira->codegen->invalid_instruction;

    if (!scope_is_comptime && is_comptime) {
        ErrorMsg *msg = ir_add_error(ira, &instruction->base,
            buf_sprintf("comptime control flow inside runtime block"));
        add_error_note(ira->codegen, msg, block_comptime_inst->source_node,
                buf_sprintf("runtime block created here"));
        return ira->codegen->invalid_instruction;
    }

    return ir_const_void(ira, &instruction->base);
}

static IrInstruction *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstruction *instruction) {
    switch (instruction->id) {
        case IrInstructionIdInvalid:
        case IrInstructionIdWidenOrShorten:
        case IrInstructionIdStructInit:
        case IrInstructionIdUnionInit:
        case IrInstructionIdStructFieldPtr:
        case IrInstructionIdUnionFieldPtr:
        case IrInstructionIdOptionalWrap:
        case IrInstructionIdErrWrapCode:
        case IrInstructionIdErrWrapPayload:
        case IrInstructionIdCast:
        case IrInstructionIdDeclVarGen:
        case IrInstructionIdPtrCastGen:
        case IrInstructionIdCmpxchgGen:
        case IrInstructionIdArrayToVector:
        case IrInstructionIdVectorToArray:
        case IrInstructionIdAssertZero:
        case IrInstructionIdResizeSlice:
        case IrInstructionIdLoadPtrGen:
        case IrInstructionIdBitCastGen:
            zig_unreachable();

        case IrInstructionIdReturn:
            return ir_analyze_instruction_return(ira, (IrInstructionReturn *)instruction);
        case IrInstructionIdConst:
            return ir_analyze_instruction_const(ira, (IrInstructionConst *)instruction);
        case IrInstructionIdUnOp:
            return ir_analyze_instruction_un_op(ira, (IrInstructionUnOp *)instruction);
        case IrInstructionIdBinOp:
            return ir_analyze_instruction_bin_op(ira, (IrInstructionBinOp *)instruction);
        case IrInstructionIdDeclVarSrc:
            return ir_analyze_instruction_decl_var(ira, (IrInstructionDeclVarSrc *)instruction);
        case IrInstructionIdLoadPtr:
            return ir_analyze_instruction_load_ptr(ira, (IrInstructionLoadPtr *)instruction);
        case IrInstructionIdStorePtr:
            return ir_analyze_instruction_store_ptr(ira, (IrInstructionStorePtr *)instruction);
        case IrInstructionIdElemPtr:
            return ir_analyze_instruction_elem_ptr(ira, (IrInstructionElemPtr *)instruction);
        case IrInstructionIdVarPtr:
            return ir_analyze_instruction_var_ptr(ira, (IrInstructionVarPtr *)instruction);
        case IrInstructionIdFieldPtr:
            return ir_analyze_instruction_field_ptr(ira, (IrInstructionFieldPtr *)instruction);
        case IrInstructionIdCall:
            return ir_analyze_instruction_call(ira, (IrInstructionCall *)instruction);
        case IrInstructionIdBr:
            return ir_analyze_instruction_br(ira, (IrInstructionBr *)instruction);
        case IrInstructionIdCondBr:
            return ir_analyze_instruction_cond_br(ira, (IrInstructionCondBr *)instruction);
        case IrInstructionIdUnreachable:
            return ir_analyze_instruction_unreachable(ira, (IrInstructionUnreachable *)instruction);
        case IrInstructionIdPhi:
            return ir_analyze_instruction_phi(ira, (IrInstructionPhi *)instruction);
        case IrInstructionIdTypeOf:
            return ir_analyze_instruction_typeof(ira, (IrInstructionTypeOf *)instruction);
        case IrInstructionIdToPtrType:
            return ir_analyze_instruction_to_ptr_type(ira, (IrInstructionToPtrType *)instruction);
        case IrInstructionIdPtrTypeChild:
            return ir_analyze_instruction_ptr_type_child(ira, (IrInstructionPtrTypeChild *)instruction);
        case IrInstructionIdSetCold:
            return ir_analyze_instruction_set_cold(ira, (IrInstructionSetCold *)instruction);
        case IrInstructionIdSetRuntimeSafety:
            return ir_analyze_instruction_set_runtime_safety(ira, (IrInstructionSetRuntimeSafety *)instruction);
        case IrInstructionIdSetFloatMode:
            return ir_analyze_instruction_set_float_mode(ira, (IrInstructionSetFloatMode *)instruction);
        case IrInstructionIdSliceType:
            return ir_analyze_instruction_slice_type(ira, (IrInstructionSliceType *)instruction);
        case IrInstructionIdAsm:
            return ir_analyze_instruction_asm(ira, (IrInstructionAsm *)instruction);
        case IrInstructionIdArrayType:
            return ir_analyze_instruction_array_type(ira, (IrInstructionArrayType *)instruction);
        case IrInstructionIdPromiseType:
            return ir_analyze_instruction_promise_type(ira, (IrInstructionPromiseType *)instruction);
        case IrInstructionIdSizeOf:
            return ir_analyze_instruction_size_of(ira, (IrInstructionSizeOf *)instruction);
        case IrInstructionIdTestNonNull:
            return ir_analyze_instruction_test_non_null(ira, (IrInstructionTestNonNull *)instruction);
        case IrInstructionIdOptionalUnwrapPtr:
            return ir_analyze_instruction_optional_unwrap_ptr(ira, (IrInstructionOptionalUnwrapPtr *)instruction);
        case IrInstructionIdClz:
            return ir_analyze_instruction_clz(ira, (IrInstructionClz *)instruction);
        case IrInstructionIdCtz:
            return ir_analyze_instruction_ctz(ira, (IrInstructionCtz *)instruction);
        case IrInstructionIdPopCount:
            return ir_analyze_instruction_pop_count(ira, (IrInstructionPopCount *)instruction);
        case IrInstructionIdSwitchBr:
            return ir_analyze_instruction_switch_br(ira, (IrInstructionSwitchBr *)instruction);
        case IrInstructionIdSwitchTarget:
            return ir_analyze_instruction_switch_target(ira, (IrInstructionSwitchTarget *)instruction);
        case IrInstructionIdSwitchVar:
            return ir_analyze_instruction_switch_var(ira, (IrInstructionSwitchVar *)instruction);
        case IrInstructionIdUnionTag:
            return ir_analyze_instruction_union_tag(ira, (IrInstructionUnionTag *)instruction);
        case IrInstructionIdImport:
            return ir_analyze_instruction_import(ira, (IrInstructionImport *)instruction);
        case IrInstructionIdRef:
            return ir_analyze_instruction_ref(ira, (IrInstructionRef *)instruction);
        case IrInstructionIdContainerInitList:
            return ir_analyze_instruction_container_init_list(ira, (IrInstructionContainerInitList *)instruction);
        case IrInstructionIdContainerInitFields:
            return ir_analyze_instruction_container_init_fields(ira, (IrInstructionContainerInitFields *)instruction);
        case IrInstructionIdCompileErr:
            return ir_analyze_instruction_compile_err(ira, (IrInstructionCompileErr *)instruction);
        case IrInstructionIdCompileLog:
            return ir_analyze_instruction_compile_log(ira, (IrInstructionCompileLog *)instruction);
        case IrInstructionIdErrName:
            return ir_analyze_instruction_err_name(ira, (IrInstructionErrName *)instruction);
        case IrInstructionIdTypeName:
            return ir_analyze_instruction_type_name(ira, (IrInstructionTypeName *)instruction);
        case IrInstructionIdCImport:
            return ir_analyze_instruction_c_import(ira, (IrInstructionCImport *)instruction);
        case IrInstructionIdCInclude:
            return ir_analyze_instruction_c_include(ira, (IrInstructionCInclude *)instruction);
        case IrInstructionIdCDefine:
            return ir_analyze_instruction_c_define(ira, (IrInstructionCDefine *)instruction);
        case IrInstructionIdCUndef:
            return ir_analyze_instruction_c_undef(ira, (IrInstructionCUndef *)instruction);
        case IrInstructionIdEmbedFile:
            return ir_analyze_instruction_embed_file(ira, (IrInstructionEmbedFile *)instruction);
        case IrInstructionIdCmpxchgSrc:
            return ir_analyze_instruction_cmpxchg(ira, (IrInstructionCmpxchgSrc *)instruction);
        case IrInstructionIdFence:
            return ir_analyze_instruction_fence(ira, (IrInstructionFence *)instruction);
        case IrInstructionIdTruncate:
            return ir_analyze_instruction_truncate(ira, (IrInstructionTruncate *)instruction);
        case IrInstructionIdIntCast:
            return ir_analyze_instruction_int_cast(ira, (IrInstructionIntCast *)instruction);
        case IrInstructionIdFloatCast:
            return ir_analyze_instruction_float_cast(ira, (IrInstructionFloatCast *)instruction);
        case IrInstructionIdErrSetCast:
            return ir_analyze_instruction_err_set_cast(ira, (IrInstructionErrSetCast *)instruction);
        case IrInstructionIdFromBytes:
            return ir_analyze_instruction_from_bytes(ira, (IrInstructionFromBytes *)instruction);
        case IrInstructionIdToBytes:
            return ir_analyze_instruction_to_bytes(ira, (IrInstructionToBytes *)instruction);
        case IrInstructionIdIntToFloat:
            return ir_analyze_instruction_int_to_float(ira, (IrInstructionIntToFloat *)instruction);
        case IrInstructionIdFloatToInt:
            return ir_analyze_instruction_float_to_int(ira, (IrInstructionFloatToInt *)instruction);
        case IrInstructionIdBoolToInt:
            return ir_analyze_instruction_bool_to_int(ira, (IrInstructionBoolToInt *)instruction);
        case IrInstructionIdIntType:
            return ir_analyze_instruction_int_type(ira, (IrInstructionIntType *)instruction);
        case IrInstructionIdVectorType:
            return ir_analyze_instruction_vector_type(ira, (IrInstructionVectorType *)instruction);
        case IrInstructionIdBoolNot:
            return ir_analyze_instruction_bool_not(ira, (IrInstructionBoolNot *)instruction);
        case IrInstructionIdMemset:
            return ir_analyze_instruction_memset(ira, (IrInstructionMemset *)instruction);
        case IrInstructionIdMemcpy:
            return ir_analyze_instruction_memcpy(ira, (IrInstructionMemcpy *)instruction);
        case IrInstructionIdSlice:
            return ir_analyze_instruction_slice(ira, (IrInstructionSlice *)instruction);
        case IrInstructionIdMemberCount:
            return ir_analyze_instruction_member_count(ira, (IrInstructionMemberCount *)instruction);
        case IrInstructionIdMemberType:
            return ir_analyze_instruction_member_type(ira, (IrInstructionMemberType *)instruction);
        case IrInstructionIdMemberName:
            return ir_analyze_instruction_member_name(ira, (IrInstructionMemberName *)instruction);
        case IrInstructionIdBreakpoint:
            return ir_analyze_instruction_breakpoint(ira, (IrInstructionBreakpoint *)instruction);
        case IrInstructionIdReturnAddress:
            return ir_analyze_instruction_return_address(ira, (IrInstructionReturnAddress *)instruction);
        case IrInstructionIdFrameAddress:
            return ir_analyze_instruction_frame_address(ira, (IrInstructionFrameAddress *)instruction);
        case IrInstructionIdHandle:
            return ir_analyze_instruction_handle(ira, (IrInstructionHandle *)instruction);
        case IrInstructionIdAlignOf:
            return ir_analyze_instruction_align_of(ira, (IrInstructionAlignOf *)instruction);
        case IrInstructionIdOverflowOp:
            return ir_analyze_instruction_overflow_op(ira, (IrInstructionOverflowOp *)instruction);
        case IrInstructionIdTestErr:
            return ir_analyze_instruction_test_err(ira, (IrInstructionTestErr *)instruction);
        case IrInstructionIdUnwrapErrCode:
            return ir_analyze_instruction_unwrap_err_code(ira, (IrInstructionUnwrapErrCode *)instruction);
        case IrInstructionIdUnwrapErrPayload:
            return ir_analyze_instruction_unwrap_err_payload(ira, (IrInstructionUnwrapErrPayload *)instruction);
        case IrInstructionIdFnProto:
            return ir_analyze_instruction_fn_proto(ira, (IrInstructionFnProto *)instruction);
        case IrInstructionIdTestComptime:
            return ir_analyze_instruction_test_comptime(ira, (IrInstructionTestComptime *)instruction);
        case IrInstructionIdCheckSwitchProngs:
            return ir_analyze_instruction_check_switch_prongs(ira, (IrInstructionCheckSwitchProngs *)instruction);
        case IrInstructionIdCheckStatementIsVoid:
            return ir_analyze_instruction_check_statement_is_void(ira, (IrInstructionCheckStatementIsVoid *)instruction);
        case IrInstructionIdDeclRef:
            return ir_analyze_instruction_decl_ref(ira, (IrInstructionDeclRef *)instruction);
        case IrInstructionIdPanic:
            return ir_analyze_instruction_panic(ira, (IrInstructionPanic *)instruction);
        case IrInstructionIdPtrCastSrc:
            return ir_analyze_instruction_ptr_cast(ira, (IrInstructionPtrCastSrc *)instruction);
        case IrInstructionIdBitCast:
            return ir_analyze_instruction_bit_cast(ira, (IrInstructionBitCast *)instruction);
        case IrInstructionIdIntToPtr:
            return ir_analyze_instruction_int_to_ptr(ira, (IrInstructionIntToPtr *)instruction);
        case IrInstructionIdPtrToInt:
            return ir_analyze_instruction_ptr_to_int(ira, (IrInstructionPtrToInt *)instruction);
        case IrInstructionIdTagName:
            return ir_analyze_instruction_enum_tag_name(ira, (IrInstructionTagName *)instruction);
        case IrInstructionIdFieldParentPtr:
            return ir_analyze_instruction_field_parent_ptr(ira, (IrInstructionFieldParentPtr *)instruction);
        case IrInstructionIdByteOffsetOf:
            return ir_analyze_instruction_byte_offset_of(ira, (IrInstructionByteOffsetOf *)instruction);
        case IrInstructionIdBitOffsetOf:
            return ir_analyze_instruction_bit_offset_of(ira, (IrInstructionBitOffsetOf *)instruction);
        case IrInstructionIdTypeInfo:
            return ir_analyze_instruction_type_info(ira, (IrInstructionTypeInfo *) instruction);
        case IrInstructionIdTypeId:
            return ir_analyze_instruction_type_id(ira, (IrInstructionTypeId *)instruction);
        case IrInstructionIdSetEvalBranchQuota:
            return ir_analyze_instruction_set_eval_branch_quota(ira, (IrInstructionSetEvalBranchQuota *)instruction);
        case IrInstructionIdPtrType:
            return ir_analyze_instruction_ptr_type(ira, (IrInstructionPtrType *)instruction);
        case IrInstructionIdAlignCast:
            return ir_analyze_instruction_align_cast(ira, (IrInstructionAlignCast *)instruction);
        case IrInstructionIdOpaqueType:
            return ir_analyze_instruction_opaque_type(ira, (IrInstructionOpaqueType *)instruction);
        case IrInstructionIdSetAlignStack:
            return ir_analyze_instruction_set_align_stack(ira, (IrInstructionSetAlignStack *)instruction);
        case IrInstructionIdArgType:
            return ir_analyze_instruction_arg_type(ira, (IrInstructionArgType *)instruction);
        case IrInstructionIdTagType:
            return ir_analyze_instruction_tag_type(ira, (IrInstructionTagType *)instruction);
        case IrInstructionIdExport:
            return ir_analyze_instruction_export(ira, (IrInstructionExport *)instruction);
        case IrInstructionIdErrorReturnTrace:
            return ir_analyze_instruction_error_return_trace(ira, (IrInstructionErrorReturnTrace *)instruction);
        case IrInstructionIdErrorUnion:
            return ir_analyze_instruction_error_union(ira, (IrInstructionErrorUnion *)instruction);
        case IrInstructionIdCancel:
            return ir_analyze_instruction_cancel(ira, (IrInstructionCancel *)instruction);
        case IrInstructionIdCoroId:
            return ir_analyze_instruction_coro_id(ira, (IrInstructionCoroId *)instruction);
        case IrInstructionIdCoroAlloc:
            return ir_analyze_instruction_coro_alloc(ira, (IrInstructionCoroAlloc *)instruction);
        case IrInstructionIdCoroSize:
            return ir_analyze_instruction_coro_size(ira, (IrInstructionCoroSize *)instruction);
        case IrInstructionIdCoroBegin:
            return ir_analyze_instruction_coro_begin(ira, (IrInstructionCoroBegin *)instruction);
        case IrInstructionIdGetImplicitAllocator:
            return ir_analyze_instruction_get_implicit_allocator(ira, (IrInstructionGetImplicitAllocator *)instruction);
        case IrInstructionIdCoroAllocFail:
            return ir_analyze_instruction_coro_alloc_fail(ira, (IrInstructionCoroAllocFail *)instruction);
        case IrInstructionIdCoroSuspend:
            return ir_analyze_instruction_coro_suspend(ira, (IrInstructionCoroSuspend *)instruction);
        case IrInstructionIdCoroEnd:
            return ir_analyze_instruction_coro_end(ira, (IrInstructionCoroEnd *)instruction);
        case IrInstructionIdCoroFree:
            return ir_analyze_instruction_coro_free(ira, (IrInstructionCoroFree *)instruction);
        case IrInstructionIdCoroResume:
            return ir_analyze_instruction_coro_resume(ira, (IrInstructionCoroResume *)instruction);
        case IrInstructionIdCoroSave:
            return ir_analyze_instruction_coro_save(ira, (IrInstructionCoroSave *)instruction);
        case IrInstructionIdCoroPromise:
            return ir_analyze_instruction_coro_promise(ira, (IrInstructionCoroPromise *)instruction);
        case IrInstructionIdCoroAllocHelper:
            return ir_analyze_instruction_coro_alloc_helper(ira, (IrInstructionCoroAllocHelper *)instruction);
        case IrInstructionIdAtomicRmw:
            return ir_analyze_instruction_atomic_rmw(ira, (IrInstructionAtomicRmw *)instruction);
        case IrInstructionIdAtomicLoad:
            return ir_analyze_instruction_atomic_load(ira, (IrInstructionAtomicLoad *)instruction);
        case IrInstructionIdPromiseResultType:
            return ir_analyze_instruction_promise_result_type(ira, (IrInstructionPromiseResultType *)instruction);
        case IrInstructionIdAwaitBookkeeping:
            return ir_analyze_instruction_await_bookkeeping(ira, (IrInstructionAwaitBookkeeping *)instruction);
        case IrInstructionIdSaveErrRetAddr:
            return ir_analyze_instruction_save_err_ret_addr(ira, (IrInstructionSaveErrRetAddr *)instruction);
        case IrInstructionIdAddImplicitReturnType:
            return ir_analyze_instruction_add_implicit_return_type(ira, (IrInstructionAddImplicitReturnType *)instruction);
        case IrInstructionIdMergeErrRetTraces:
            return ir_analyze_instruction_merge_err_ret_traces(ira, (IrInstructionMergeErrRetTraces *)instruction);
        case IrInstructionIdMarkErrRetTracePtr:
            return ir_analyze_instruction_mark_err_ret_trace_ptr(ira, (IrInstructionMarkErrRetTracePtr *)instruction);
        case IrInstructionIdSqrt:
            return ir_analyze_instruction_sqrt(ira, (IrInstructionSqrt *)instruction);
        case IrInstructionIdBswap:
            return ir_analyze_instruction_bswap(ira, (IrInstructionBswap *)instruction);
        case IrInstructionIdBitReverse:
            return ir_analyze_instruction_bit_reverse(ira, (IrInstructionBitReverse *)instruction);
        case IrInstructionIdIntToErr:
            return ir_analyze_instruction_int_to_err(ira, (IrInstructionIntToErr *)instruction);
        case IrInstructionIdErrToInt:
            return ir_analyze_instruction_err_to_int(ira, (IrInstructionErrToInt *)instruction);
        case IrInstructionIdIntToEnum:
            return ir_analyze_instruction_int_to_enum(ira, (IrInstructionIntToEnum *)instruction);
        case IrInstructionIdEnumToInt:
            return ir_analyze_instruction_enum_to_int(ira, (IrInstructionEnumToInt *)instruction);
        case IrInstructionIdCheckRuntimeScope:
            return ir_analyze_instruction_check_runtime_scope(ira, (IrInstructionCheckRuntimeScope *)instruction);
    }
    zig_unreachable();
}

static IrInstruction *ir_analyze_instruction(IrAnalyze *ira, IrInstruction *old_instruction) {
    IrInstruction *new_instruction = ir_analyze_instruction_nocast(ira, old_instruction);
    assert(new_instruction->value.type != nullptr);
    old_instruction->child = new_instruction;
    return new_instruction;
}

// This function attempts to evaluate IR code while doing type checking and other analysis.
// It emits a new IrExecutable which is partially evaluated IR code.
ZigType *ir_analyze(CodeGen *codegen, IrExecutable *old_exec, IrExecutable *new_exec,
        ZigType *expected_type, AstNode *expected_type_source_node)
{
    assert(!old_exec->invalid);
    assert(expected_type == nullptr || !type_is_invalid(expected_type));

    IrAnalyze *ira = allocate<IrAnalyze>(1);
    old_exec->analysis = ira;
    ira->codegen = codegen;

    ZigFn *fn_entry = exec_fn_entry(old_exec);
    bool is_async = fn_entry != nullptr && fn_entry->type_entry->data.fn.fn_type_id.cc == CallingConventionAsync;
    ira->explicit_return_type = is_async ? get_promise_type(codegen, expected_type) : expected_type;
    ira->explicit_return_type_source_node = expected_type_source_node;

    ira->old_irb.codegen = codegen;
    ira->old_irb.exec = old_exec;

    ira->new_irb.codegen = codegen;
    ira->new_irb.exec = new_exec;

    ConstExprValue *vals = create_const_vals(ira->old_irb.exec->mem_slot_count);
    ira->exec_context.mem_slot_list.resize(ira->old_irb.exec->mem_slot_count);
    for (size_t i = 0; i < ira->exec_context.mem_slot_list.length; i += 1) {
        ira->exec_context.mem_slot_list.items[i] = &vals[i];
    }

    IrBasicBlock *old_entry_bb = ira->old_irb.exec->basic_block_list.at(0);
    IrBasicBlock *new_entry_bb = ir_get_new_bb(ira, old_entry_bb, nullptr);
    ir_ref_bb(new_entry_bb);
    ira->new_irb.current_basic_block = new_entry_bb;
    ira->old_bb_index = 0;

    ir_start_bb(ira, old_entry_bb, nullptr);

    while (ira->old_bb_index < ira->old_irb.exec->basic_block_list.length) {
        IrInstruction *old_instruction = ira->old_irb.current_basic_block->instruction_list.at(ira->instruction_index);

        if (old_instruction->ref_count == 0 && !ir_has_side_effects(old_instruction)) {
            ira->instruction_index += 1;
            continue;
        }

        IrInstruction *new_instruction = ir_analyze_instruction(ira, old_instruction);
        if (type_is_invalid(new_instruction->value.type) && ir_should_inline(new_exec, old_instruction->scope)) {
            return ira->codegen->builtin_types.entry_invalid;
        }

        // unreachable instructions do their own control flow.
        if (new_instruction->value.type->id == ZigTypeIdUnreachable)
            continue;

        ira->instruction_index += 1;
    }

    if (new_exec->invalid) {
        return ira->codegen->builtin_types.entry_invalid;
    } else if (ira->src_implicit_return_type_list.length == 0) {
        return codegen->builtin_types.entry_unreachable;
    } else {
        return ir_resolve_peer_types(ira, expected_type_source_node, expected_type, ira->src_implicit_return_type_list.items,
                ira->src_implicit_return_type_list.length);
    }
}

bool ir_has_side_effects(IrInstruction *instruction) {
    switch (instruction->id) {
        case IrInstructionIdInvalid:
            zig_unreachable();
        case IrInstructionIdBr:
        case IrInstructionIdCondBr:
        case IrInstructionIdSwitchBr:
        case IrInstructionIdDeclVarSrc:
        case IrInstructionIdDeclVarGen:
        case IrInstructionIdStorePtr:
        case IrInstructionIdCall:
        case IrInstructionIdReturn:
        case IrInstructionIdUnreachable:
        case IrInstructionIdSetCold:
        case IrInstructionIdSetRuntimeSafety:
        case IrInstructionIdSetFloatMode:
        case IrInstructionIdImport:
        case IrInstructionIdCompileErr:
        case IrInstructionIdCompileLog:
        case IrInstructionIdCImport:
        case IrInstructionIdCInclude:
        case IrInstructionIdCDefine:
        case IrInstructionIdCUndef:
        case IrInstructionIdFence:
        case IrInstructionIdMemset:
        case IrInstructionIdMemcpy:
        case IrInstructionIdBreakpoint:
        case IrInstructionIdOverflowOp: // TODO when we support multiple returns this can be side effect free
        case IrInstructionIdCheckSwitchProngs:
        case IrInstructionIdCheckStatementIsVoid:
        case IrInstructionIdCheckRuntimeScope:
        case IrInstructionIdPanic:
        case IrInstructionIdSetEvalBranchQuota:
        case IrInstructionIdPtrType:
        case IrInstructionIdSetAlignStack:
        case IrInstructionIdExport:
        case IrInstructionIdCancel:
        case IrInstructionIdCoroId:
        case IrInstructionIdCoroBegin:
        case IrInstructionIdCoroAllocFail:
        case IrInstructionIdCoroEnd:
        case IrInstructionIdCoroResume:
        case IrInstructionIdCoroSave:
        case IrInstructionIdCoroAllocHelper:
        case IrInstructionIdAwaitBookkeeping:
        case IrInstructionIdSaveErrRetAddr:
        case IrInstructionIdAddImplicitReturnType:
        case IrInstructionIdMergeErrRetTraces:
        case IrInstructionIdMarkErrRetTracePtr:
        case IrInstructionIdAtomicRmw:
        case IrInstructionIdCmpxchgGen:
        case IrInstructionIdCmpxchgSrc:
        case IrInstructionIdAssertZero:
        case IrInstructionIdResizeSlice:
            return true;

        case IrInstructionIdPhi:
        case IrInstructionIdUnOp:
        case IrInstructionIdBinOp:
        case IrInstructionIdLoadPtr:
        case IrInstructionIdLoadPtrGen:
        case IrInstructionIdConst:
        case IrInstructionIdCast:
        case IrInstructionIdContainerInitList:
        case IrInstructionIdContainerInitFields:
        case IrInstructionIdStructInit:
        case IrInstructionIdUnionInit:
        case IrInstructionIdFieldPtr:
        case IrInstructionIdElemPtr:
        case IrInstructionIdVarPtr:
        case IrInstructionIdTypeOf:
        case IrInstructionIdToPtrType:
        case IrInstructionIdPtrTypeChild:
        case IrInstructionIdStructFieldPtr:
        case IrInstructionIdUnionFieldPtr:
        case IrInstructionIdArrayType:
        case IrInstructionIdPromiseType:
        case IrInstructionIdSliceType:
        case IrInstructionIdSizeOf:
        case IrInstructionIdTestNonNull:
        case IrInstructionIdOptionalUnwrapPtr:
        case IrInstructionIdClz:
        case IrInstructionIdCtz:
        case IrInstructionIdPopCount:
        case IrInstructionIdSwitchVar:
        case IrInstructionIdSwitchTarget:
        case IrInstructionIdUnionTag:
        case IrInstructionIdRef:
        case IrInstructionIdEmbedFile:
        case IrInstructionIdTruncate:
        case IrInstructionIdIntType:
        case IrInstructionIdVectorType:
        case IrInstructionIdBoolNot:
        case IrInstructionIdSlice:
        case IrInstructionIdMemberCount:
        case IrInstructionIdMemberType:
        case IrInstructionIdMemberName:
        case IrInstructionIdAlignOf:
        case IrInstructionIdReturnAddress:
        case IrInstructionIdFrameAddress:
        case IrInstructionIdHandle:
        case IrInstructionIdTestErr:
        case IrInstructionIdUnwrapErrCode:
        case IrInstructionIdOptionalWrap:
        case IrInstructionIdErrWrapCode:
        case IrInstructionIdErrWrapPayload:
        case IrInstructionIdFnProto:
        case IrInstructionIdTestComptime:
        case IrInstructionIdPtrCastSrc:
        case IrInstructionIdPtrCastGen:
        case IrInstructionIdBitCast:
        case IrInstructionIdBitCastGen:
        case IrInstructionIdWidenOrShorten:
        case IrInstructionIdPtrToInt:
        case IrInstructionIdIntToPtr:
        case IrInstructionIdIntToEnum:
        case IrInstructionIdIntToErr:
        case IrInstructionIdErrToInt:
        case IrInstructionIdDeclRef:
        case IrInstructionIdErrName:
        case IrInstructionIdTypeName:
        case IrInstructionIdTagName:
        case IrInstructionIdFieldParentPtr:
        case IrInstructionIdByteOffsetOf:
        case IrInstructionIdBitOffsetOf:
        case IrInstructionIdTypeInfo:
        case IrInstructionIdTypeId:
        case IrInstructionIdAlignCast:
        case IrInstructionIdOpaqueType:
        case IrInstructionIdArgType:
        case IrInstructionIdTagType:
        case IrInstructionIdErrorReturnTrace:
        case IrInstructionIdErrorUnion:
        case IrInstructionIdGetImplicitAllocator:
        case IrInstructionIdCoroAlloc:
        case IrInstructionIdCoroSize:
        case IrInstructionIdCoroSuspend:
        case IrInstructionIdCoroFree:
        case IrInstructionIdCoroPromise:
        case IrInstructionIdPromiseResultType:
        case IrInstructionIdSqrt:
        case IrInstructionIdBswap:
        case IrInstructionIdBitReverse:
        case IrInstructionIdAtomicLoad:
        case IrInstructionIdIntCast:
        case IrInstructionIdFloatCast:
        case IrInstructionIdErrSetCast:
        case IrInstructionIdIntToFloat:
        case IrInstructionIdFloatToInt:
        case IrInstructionIdBoolToInt:
        case IrInstructionIdFromBytes:
        case IrInstructionIdToBytes:
        case IrInstructionIdEnumToInt:
        case IrInstructionIdVectorToArray:
        case IrInstructionIdArrayToVector:
            return false;

        case IrInstructionIdAsm:
            {
                IrInstructionAsm *asm_instruction = (IrInstructionAsm *)instruction;
                return asm_instruction->has_side_effects;
            }
        case IrInstructionIdUnwrapErrPayload:
            {
                IrInstructionUnwrapErrPayload *unwrap_err_payload_instruction =
                    (IrInstructionUnwrapErrPayload *)instruction;
                return unwrap_err_payload_instruction->safety_check_on;
            }
    }
    zig_unreachable();
}
