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

#include "astgen.hpp"
#include "analyze.hpp"
#include "error.hpp"
#include "ir.hpp"
#include "ir_print.hpp"
#include "os.hpp"
#include "range_set.hpp"
#include "softfloat.hpp"
#include "softfloat_ext.hpp"
#include "util.hpp"
#include "mem_list.hpp"
#include "all_types.hpp"
#include "zigendian.h"

#include <errno.h>
#include <math.h>

struct IrBuilderGen {
    CodeGen *codegen;
    Stage1Air *exec;
    Stage1AirBasicBlock *current_basic_block;

    // track for immediate post-analysis destruction
    mem::List<Stage1AirInstConst *> constants;
};

struct IrAnalyze {
    CodeGen *codegen;
    Stage1Zir *zir;
    Stage1ZirBasicBlock *zir_current_basic_block;
    IrBuilderGen new_irb;
    size_t old_bb_index;
    size_t instruction_index;
    ZigType *explicit_return_type;
    AstNode *explicit_return_type_source_node;
    ZigList<Stage1AirInst *> src_implicit_return_type_list;
    ZigList<IrSuspendPosition> resume_stack;
    Stage1ZirBasicBlock *const_predecessor_bb;
    size_t ref_count;
    size_t break_debug_id; // for debugging purposes
    Stage1AirInst *return_ptr;
    Stage1Air *parent_exec;
    size_t *backward_branch_count;
    size_t *backward_branch_quota;
    ZigFn *fn;
    Stage1ZirInst *suspend_source_instr;

    // For the purpose of using in a debugger
    void dump();
};

enum ConstCastResultId {
    ConstCastResultIdOk,
    ConstCastResultIdInvalid,
    ConstCastResultIdErrSet,
    ConstCastResultIdErrSetGlobal,
    ConstCastResultIdPointerChild,
    ConstCastResultIdSliceChild,
    ConstCastResultIdOptionalChild,
    ConstCastResultIdOptionalShape,
    ConstCastResultIdErrorUnionPayload,
    ConstCastResultIdErrorUnionErrorSet,
    ConstCastResultIdFnAlign,
    ConstCastResultIdFnCC,
    ConstCastResultIdFnVarArgs,
    ConstCastResultIdFnIsGeneric,
    ConstCastResultIdFnReturnType,
    ConstCastResultIdFnArgCount,
    ConstCastResultIdFnGenericArgCount,
    ConstCastResultIdFnArg,
    ConstCastResultIdFnArgNoAlias,
    ConstCastResultIdType,
    ConstCastResultIdUnresolvedInferredErrSet,
    ConstCastResultIdAsyncAllocatorType,
    ConstCastResultIdBadAllowsZero,
    ConstCastResultIdArrayChild,
    ConstCastResultIdSentinelArrays,
    ConstCastResultIdPtrLens,
    ConstCastResultIdCV,
    ConstCastResultIdPtrSentinel,
    ConstCastResultIdIntShorten,
    ConstCastResultIdVectorLength,
    ConstCastResultIdVectorChild,
};

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 ConstCastArrayMismatch;
struct ConstCastBadAllowsZero;
struct ConstCastBadNullTermArrays;
struct ConstCastBadCV;
struct ConstCastPtrSentinel;
struct ConstCastIntShorten;

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;
        ConstCastArrayMismatch *array_mismatch;
        ConstCastOnly *return_type;
        ConstCastOnly *null_wrap_ptr_child;
        ConstCastArg fn_arg;
        ConstCastArgNoAlias arg_no_alias;
        ConstCastBadAllowsZero *bad_allows_zero;
        ConstCastBadNullTermArrays *sentinel_arrays;
        ConstCastBadCV *bad_cv;
        ConstCastPtrSentinel *bad_ptr_sentinel;
        ConstCastIntShorten *int_shorten;
    } 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 ConstCastArrayMismatch {
    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;
};

struct ConstCastBadNullTermArrays {
    ConstCastOnly child;
    ZigType *wanted_type;
    ZigType *actual_type;
};

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

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

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

// for debugging purposes
struct DbgIrBreakPoint {
    const char *src_file;
    uint32_t line;
};

static Stage1AirInst *ir_implicit_cast(IrAnalyze *ira, Stage1AirInst *value, ZigType *expected_type);
static Stage1AirInst *ir_implicit_cast2(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        Stage1AirInst *value, ZigType *expected_type);
static Stage1AirInst *ir_get_deref(IrAnalyze *ira, Scope *scope, AstNode *source_node, Stage1AirInst *ptr,
        ResultLoc *result_loc);
static Stage1AirInst *ir_analyze_container_field_ptr(IrAnalyze *ira, Buf *field_name,
    Scope *scope, AstNode *source_node, Stage1AirInst *container_ptr, AstNode *container_ptr_src,
    ZigType *container_type, bool initializing);
static Stage1AirInst *ir_get_var_ptr(IrAnalyze *ira, Scope *scope, AstNode *source_node, ZigVar *var);
static ZigType *ir_resolve_atomic_operand_type(IrAnalyze *ira, Stage1AirInst *op);
static ZigType *adjust_ptr_align(CodeGen *g, ZigType *ptr_type, uint32_t new_align);
static ZigType *adjust_ptr_const(CodeGen *g, ZigType *ptr_type, bool is_const);
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, ZigValue *val);
static void buf_write_value_bytes(CodeGen *codegen, uint8_t *buf, ZigValue *val);
static Error ir_read_const_ptr(IrAnalyze *ira, CodeGen *codegen, AstNode *source_node,
        ZigValue *out_val, ZigValue *ptr_val);
static Stage1AirInst *ir_analyze_ptr_cast(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        Stage1AirInst *ptr, AstNode *ptr_src, ZigType *dest_type, AstNode *dest_type_src,
        bool safety_check_on, bool keep_bigger_alignment);
static ZigValue *ir_resolve_const(IrAnalyze *ira, Stage1AirInst *value, UndefAllowed undef_allowed);
static Error resolve_ptr_align(IrAnalyze *ira, ZigType *ty, uint32_t *result_align);
static Stage1AirInst *ir_analyze_int_to_ptr(IrAnalyze *ira, Scope *scope, AstNode *source_node, Stage1AirInst *target,
        ZigType *ptr_type);
static Stage1AirInst *ir_analyze_bit_cast(IrAnalyze *ira, Scope *scope, AstNode *source_node, Stage1AirInst *value,
        ZigType *dest_type);
static Stage1AirInst *ir_resolve_result_raw(IrAnalyze *ira, Stage1ZirInst *suspend_source_instr,
        ResultLoc *result_loc, ZigType *value_type, Stage1AirInst *value, bool force_runtime, bool allow_discard);
static Stage1AirInst *ir_resolve_result(IrAnalyze *ira, Stage1ZirInst *suspend_source_instr,
        ResultLoc *result_loc, ZigType *value_type, Stage1AirInst *value, bool force_runtime, bool allow_discard);
static Stage1AirInst *ir_analyze_unwrap_optional_payload(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        Stage1AirInst *base_ptr, bool safety_check_on, bool initializing);
static Stage1AirInst *ir_analyze_unwrap_error_payload(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        Stage1AirInst *base_ptr, bool safety_check_on, bool initializing);
static Stage1AirInst *ir_analyze_unwrap_err_code(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        Stage1AirInst *base_ptr, bool initializing);
static Stage1AirInst *ir_analyze_store_ptr(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        Stage1AirInst *ptr, Stage1AirInst *uncasted_value, bool allow_write_through_const);
static void ir_reset_result(ResultLoc *result_loc);
static Stage1AirInst *ir_analyze_struct_field_ptr(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        TypeStructField *field, Stage1AirInst *struct_ptr, ZigType *struct_type, bool initializing);
static Stage1AirInst *ir_analyze_inferred_field_ptr(IrAnalyze *ira, Buf *field_name,
    Scope *scope, AstNode *source_node, Stage1AirInst *container_ptr, ZigType *container_type);
static Stage1AirInst *ir_analyze_test_non_null(IrAnalyze *ira, Scope *scope, AstNode *source_node, Stage1AirInst *value);
static Stage1AirInst *ir_error_dependency_loop(IrAnalyze *ira, AstNode *source_node);
static Stage1AirInst *ir_const_undef(IrAnalyze *ira, Scope *scope, AstNode *source_node, ZigType *ty);
static Stage1AirInst *ir_analyze_union_init(IrAnalyze *ira, Scope *scope, AstNode *source_node,
    AstNode *field_source_node, ZigType *union_type, Buf *field_name, Stage1AirInst *field_result_loc,
    Stage1AirInst *result_loc);
static Stage1AirInst *ir_analyze_struct_value_field_value(IrAnalyze *ira, Scope *scope, AstNode *source_node,
    Stage1AirInst *struct_operand, TypeStructField *field);
static bool value_cmp_numeric_val_any(ZigValue *left, Cmp predicate, ZigValue *right);
static bool value_cmp_numeric_val_all(ZigValue *left, Cmp predicate, ZigValue *right);
static void memoize_field_init_val(CodeGen *codegen, ZigType *container_type, TypeStructField *field);
static void value_to_bigfloat(BigFloat *out, ZigValue *val);

static Error ir_resolve_lazy_recurse(AstNode *source_node, ZigValue *val);
static Error ir_resolve_lazy_recurse_array(AstNode *source_node, ZigValue *val, size_t len);


static void ir_assert_impl(bool ok, Stage1AirInst *source_instruction, char const *file, unsigned int line) {
    if (ok) return;
    src_assert_impl(ok, source_instruction->source_node, file, line);
}

#define ir_assert(OK, SOURCE_INSTRUCTION) ir_assert_impl((OK), (SOURCE_INSTRUCTION), __FILE__, __LINE__)

void destroy_instruction_gen(Stage1AirInst *inst) {
    switch (inst->id) {
        case Stage1AirInstIdInvalid:
            zig_unreachable();
        case Stage1AirInstIdReturn:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstReturn *>(inst));
        case Stage1AirInstIdConst:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstConst *>(inst));
        case Stage1AirInstIdBinOp:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstBinOp *>(inst));
        case Stage1AirInstIdCast:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstCast *>(inst));
        case Stage1AirInstIdCall:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstCall *>(inst));
        case Stage1AirInstIdCondBr:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstCondBr *>(inst));
        case Stage1AirInstIdBr:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstBr *>(inst));
        case Stage1AirInstIdPhi:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstPhi *>(inst));
        case Stage1AirInstIdUnreachable:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstUnreachable *>(inst));
        case Stage1AirInstIdElemPtr:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstElemPtr *>(inst));
        case Stage1AirInstIdVarPtr:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstVarPtr *>(inst));
        case Stage1AirInstIdReturnPtr:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstReturnPtr *>(inst));
        case Stage1AirInstIdLoadPtr:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstLoadPtr *>(inst));
        case Stage1AirInstIdStorePtr:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstStorePtr *>(inst));
        case Stage1AirInstIdVectorStoreElem:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstVectorStoreElem *>(inst));
        case Stage1AirInstIdStructFieldPtr:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstStructFieldPtr *>(inst));
        case Stage1AirInstIdUnionFieldPtr:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstUnionFieldPtr *>(inst));
        case Stage1AirInstIdAsm:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstAsm *>(inst));
        case Stage1AirInstIdTestNonNull:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstTestNonNull *>(inst));
        case Stage1AirInstIdOptionalUnwrapPtr:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstOptionalUnwrapPtr *>(inst));
        case Stage1AirInstIdPopCount:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstPopCount *>(inst));
        case Stage1AirInstIdClz:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstClz *>(inst));
        case Stage1AirInstIdCtz:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstCtz *>(inst));
        case Stage1AirInstIdBswap:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstBswap *>(inst));
        case Stage1AirInstIdBitReverse:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstBitReverse *>(inst));
        case Stage1AirInstIdSwitchBr:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstSwitchBr *>(inst));
        case Stage1AirInstIdUnionTag:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstUnionTag *>(inst));
        case Stage1AirInstIdRef:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstRef *>(inst));
        case Stage1AirInstIdErrName:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstErrName *>(inst));
        case Stage1AirInstIdCmpxchg:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstCmpxchg *>(inst));
        case Stage1AirInstIdFence:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstFence *>(inst));
        case Stage1AirInstIdReduce:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstReduce *>(inst));
        case Stage1AirInstIdTruncate:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstTruncate *>(inst));
        case Stage1AirInstIdShuffleVector:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstShuffleVector *>(inst));
        case Stage1AirInstIdSelect:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstSelect *>(inst));
        case Stage1AirInstIdSplat:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstSplat *>(inst));
        case Stage1AirInstIdBoolNot:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstBoolNot *>(inst));
        case Stage1AirInstIdMemset:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstMemset *>(inst));
        case Stage1AirInstIdMemcpy:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstMemcpy *>(inst));
        case Stage1AirInstIdSlice:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstSlice *>(inst));
        case Stage1AirInstIdBreakpoint:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstBreakpoint *>(inst));
        case Stage1AirInstIdReturnAddress:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstReturnAddress *>(inst));
        case Stage1AirInstIdFrameAddress:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstFrameAddress *>(inst));
        case Stage1AirInstIdFrameHandle:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstFrameHandle *>(inst));
        case Stage1AirInstIdFrameSize:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstFrameSize *>(inst));
        case Stage1AirInstIdOverflowOp:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstOverflowOp *>(inst));
        case Stage1AirInstIdTestErr:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstTestErr *>(inst));
        case Stage1AirInstIdUnwrapErrCode:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstUnwrapErrCode *>(inst));
        case Stage1AirInstIdUnwrapErrPayload:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstUnwrapErrPayload *>(inst));
        case Stage1AirInstIdOptionalWrap:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstOptionalWrap *>(inst));
        case Stage1AirInstIdErrWrapCode:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstErrWrapCode *>(inst));
        case Stage1AirInstIdErrWrapPayload:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstErrWrapPayload *>(inst));
        case Stage1AirInstIdPtrCast:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstPtrCast *>(inst));
        case Stage1AirInstIdBitCast:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstBitCast *>(inst));
        case Stage1AirInstIdWidenOrShorten:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstWidenOrShorten *>(inst));
        case Stage1AirInstIdPtrToInt:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstPtrToInt *>(inst));
        case Stage1AirInstIdIntToPtr:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstIntToPtr *>(inst));
        case Stage1AirInstIdIntToEnum:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstIntToEnum *>(inst));
        case Stage1AirInstIdIntToErr:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstIntToErr *>(inst));
        case Stage1AirInstIdErrToInt:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstErrToInt *>(inst));
        case Stage1AirInstIdTagName:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstTagName *>(inst));
        case Stage1AirInstIdPanic:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstPanic *>(inst));
        case Stage1AirInstIdFieldParentPtr:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstFieldParentPtr *>(inst));
        case Stage1AirInstIdAlignCast:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstAlignCast *>(inst));
        case Stage1AirInstIdErrorReturnTrace:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstErrorReturnTrace *>(inst));
        case Stage1AirInstIdAtomicRmw:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstAtomicRmw *>(inst));
        case Stage1AirInstIdSaveErrRetAddr:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstSaveErrRetAddr *>(inst));
        case Stage1AirInstIdFloatOp:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstFloatOp *>(inst));
        case Stage1AirInstIdMulAdd:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstMulAdd *>(inst));
        case Stage1AirInstIdAtomicLoad:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstAtomicLoad *>(inst));
        case Stage1AirInstIdAtomicStore:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstAtomicStore *>(inst));
        case Stage1AirInstIdDeclVar:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstDeclVar *>(inst));
        case Stage1AirInstIdArrayToVector:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstArrayToVector *>(inst));
        case Stage1AirInstIdVectorToArray:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstVectorToArray *>(inst));
        case Stage1AirInstIdPtrOfArrayToSlice:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstPtrOfArrayToSlice *>(inst));
        case Stage1AirInstIdAssertZero:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstAssertZero *>(inst));
        case Stage1AirInstIdAssertNonNull:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstAssertNonNull *>(inst));
        case Stage1AirInstIdAlloca:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstAlloca *>(inst));
        case Stage1AirInstIdSuspendBegin:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstSuspendBegin *>(inst));
        case Stage1AirInstIdSuspendFinish:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstSuspendFinish *>(inst));
        case Stage1AirInstIdResume:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstResume *>(inst));
        case Stage1AirInstIdAwait:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstAwait *>(inst));
        case Stage1AirInstIdSpillBegin:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstSpillBegin *>(inst));
        case Stage1AirInstIdSpillEnd:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstSpillEnd *>(inst));
        case Stage1AirInstIdVectorExtractElem:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstVectorExtractElem *>(inst));
        case Stage1AirInstIdBinaryNot:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstBinaryNot *>(inst));
        case Stage1AirInstIdNegation:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstNegation *>(inst));
        case Stage1AirInstIdWasmMemorySize:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstWasmMemorySize *>(inst));
        case Stage1AirInstIdWasmMemoryGrow:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstWasmMemoryGrow *>(inst));
        case Stage1AirInstIdExtern:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstExtern *>(inst));
        case Stage1AirInstIdPrefetch:
            return heap::c_allocator.destroy(reinterpret_cast<Stage1AirInstPrefetch *>(inst));
    }
    zig_unreachable();
}

static void ira_ref(IrAnalyze *ira) {
    ira->ref_count += 1;
}
static void ira_deref(IrAnalyze *ira) {
    if (ira->ref_count > 1) {
        ira->ref_count -= 1;

        // immediate destruction of dangling Stage1AirInstConst is not possible
        // free tracking memory because it will never be used
        ira->new_irb.constants.deinit(&heap::c_allocator);
        return;
    }
    assert(ira->ref_count != 0);

    for (size_t bb_i = 0; bb_i < ira->zir->basic_block_list.length; bb_i += 1) {
        Stage1ZirBasicBlock *pass1_bb = ira->zir->basic_block_list.items[bb_i];
        for (size_t inst_i = 0; inst_i < pass1_bb->instruction_list.length; inst_i += 1) {
            Stage1ZirInst *pass1_inst = pass1_bb->instruction_list.items[inst_i];
            destroy_instruction_src(pass1_inst);
        }
        heap::c_allocator.destroy(pass1_bb);
    }
    ira->zir->basic_block_list.deinit();
    ira->zir->tld_list.deinit();
    heap::c_allocator.destroy(ira->zir);
    ira->src_implicit_return_type_list.deinit();
    ira->resume_stack.deinit();

    // destroy dangling Stage1AirInstConst
    for (size_t i = 0; i < ira->new_irb.constants.length; i += 1) {
        auto constant = ira->new_irb.constants.items[i];
        if (constant->base.ref_count == 0 && !ir_inst_gen_has_side_effects(&constant->base))
            destroy_instruction_gen(&constant->base);
    }
    ira->new_irb.constants.deinit(&heap::c_allocator);

    heap::c_allocator.destroy(ira);
}

static ZigValue *const_ptr_pointee_unchecked_no_isf(CodeGen *g, ZigValue *const_val) {
    assert(get_src_ptr_type(const_val->type) != nullptr);
    assert(const_val->special == ConstValSpecialStatic);

    switch (type_has_one_possible_value(g, const_val->type->data.pointer.child_type)) {
        case OnePossibleValueInvalid:
            return nullptr;
        case OnePossibleValueYes:
            return get_the_one_possible_value(g, const_val->type->data.pointer.child_type);
        case OnePossibleValueNo:
            break;
    }

    ZigValue *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: {
            ZigValue *array_val = const_val->data.x_ptr.data.base_array.array_val;
            size_t elem_index = const_val->data.x_ptr.data.base_array.elem_index;
            if (elem_index == array_val->type->data.array.len) {
                result = array_val->type->data.array.sentinel;
            } else {
                expand_undef_array(g, array_val);
                result = &array_val->data.x_array.data.s_none.elements[elem_index];
            }
            break;
        }
        case ConstPtrSpecialSubArray: {
            ZigValue *array_val = const_val->data.x_ptr.data.base_array.array_val;
            size_t elem_index = const_val->data.x_ptr.data.base_array.elem_index;

            expand_undef_array(g, array_val);
            result = g->pass1_arena->create<ZigValue>();
            result->special = array_val->special;
            result->type = get_array_type(g, array_val->type->data.array.child_type,
                    array_val->type->data.array.len - elem_index, array_val->type->data.array.sentinel);
            result->data.x_array.special = ConstArraySpecialNone;
            result->data.x_array.data.s_none.elements = &array_val->data.x_array.data.s_none.elements[elem_index];
            result->parent.id = ConstParentIdArray;
            result->parent.data.p_array.array_val = array_val;
            result->parent.data.p_array.elem_index = elem_index;
            break;
        }
        case ConstPtrSpecialBaseStruct: {
            ZigValue *struct_val = const_val->data.x_ptr.data.base_struct.struct_val;
            expand_undef_struct(g, struct_val);
            size_t field_index = const_val->data.x_ptr.data.base_struct.field_index;
            assert(struct_val->type->id == ZigTypeIdStruct);
            assert(!struct_val->type->data.structure.fields[field_index]->is_comptime);
            result = struct_val->data.x_struct.fields[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 ZigValue *const_ptr_pointee_unchecked(CodeGen *g, ZigValue *const_val) {
    assert(get_src_ptr_type(const_val->type) != nullptr);
    assert(const_val->special == ConstValSpecialStatic);

    InferredStructField *isf = const_val->type->data.pointer.inferred_struct_field;
    if (isf != nullptr) {
        TypeStructField *field = find_struct_type_field(isf->inferred_struct_type, isf->field_name);
        assert(field != nullptr);
        if (field->is_comptime) {
            assert(field->init_val != nullptr);
            return field->init_val;
        }
        ZigValue *struct_val = const_ptr_pointee_unchecked_no_isf(g, const_val);
        assert(struct_val->type->id == ZigTypeIdStruct);
        return struct_val->data.x_struct.fields[field->src_index];
    }

    return const_ptr_pointee_unchecked_no_isf(g, const_val);
}

static bool is_tuple(ZigType *type) {
    return type->id == ZigTypeIdStruct && type->data.structure.special == StructSpecialInferredTuple;
}

static bool is_slice(ZigType *type) {
    return type->id == ZigTypeIdStruct && type->data.structure.special == StructSpecialSlice;
}

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

    if (get_src_ptr_type(expected) != nullptr && get_src_ptr_type(actual) != nullptr)
        return true;

    if (is_opt_err_set(expected) && is_opt_err_set(actual))
        return true;

    // XXX: Vectors and arrays are interchangeable at comptime
    if (expected->id != actual->id)
        return false;

    switch (expected->id) {
        case ZigTypeIdInvalid:
        case ZigTypeIdUnreachable:
            zig_unreachable();
        case ZigTypeIdMetaType:
        case ZigTypeIdVoid:
        case ZigTypeIdBool:
        case ZigTypeIdComptimeFloat:
        case ZigTypeIdComptimeInt:
        case ZigTypeIdEnumLiteral:
        case ZigTypeIdUndefined:
        case ZigTypeIdNull:
        case ZigTypeIdBoundFn:
        case ZigTypeIdErrorSet:
        case ZigTypeIdOpaque:
        case ZigTypeIdAnyFrame:
        case ZigTypeIdFn:
            return true;
        case ZigTypeIdPointer:
            return expected->data.pointer.inferred_struct_field == actual->data.pointer.inferred_struct_field;
        case ZigTypeIdFloat:
            return expected->data.floating.bit_count == actual->data.floating.bit_count;
        case ZigTypeIdInt:
            return expected->data.integral.is_signed == actual->data.integral.is_signed;
        case ZigTypeIdStruct:
            return is_slice(expected) && is_slice(actual);
        case ZigTypeIdOptional:
        case ZigTypeIdErrorUnion:
        case ZigTypeIdEnum:
        case ZigTypeIdUnion:
        case ZigTypeIdFnFrame:
            return false;
        case ZigTypeIdVector:
            return expected->data.vector.len == actual->data.vector.len &&
                    types_have_same_zig_comptime_repr(codegen, expected->data.vector.elem_type, actual->data.vector.elem_type);
        case ZigTypeIdArray:
            return expected->data.array.len == actual->data.array.len &&
                expected->data.array.child_type == actual->data.array.child_type &&
                (expected->data.array.sentinel == nullptr || (actual->data.array.sentinel != nullptr &&
                     const_values_equal(codegen, expected->data.array.sentinel, actual->data.array.sentinel)));
    }
    zig_unreachable();
}

static void ir_inst_gen_append(Stage1AirBasicBlock *basic_block, Stage1AirInst *instruction) {
    assert(basic_block);
    assert(instruction);
    basic_block->instruction_list.append(instruction);
}

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

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

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

static void ir_ref_inst_gen(Stage1AirInst *instruction) {
    assert(instruction->id != Stage1AirInstIdInvalid);
    instruction->ref_count += 1;
}

static void create_result_ptr(CodeGen *codegen, ZigType *expected_type,
        ZigValue **out_result, ZigValue **out_result_ptr)
{
    ZigValue *result = codegen->pass1_arena->create<ZigValue>();
    ZigValue *result_ptr = codegen->pass1_arena->create<ZigValue>();
    result->special = ConstValSpecialUndef;
    result->type = expected_type;
    result_ptr->special = ConstValSpecialStatic;
    result_ptr->type = get_pointer_to_type(codegen, result->type, false);
    result_ptr->data.x_ptr.mut = ConstPtrMutComptimeVar;
    result_ptr->data.x_ptr.special = ConstPtrSpecialRef;
    result_ptr->data.x_ptr.data.ref.pointee = result;

    *out_result = result;
    *out_result_ptr = result_ptr;
}

ZigType *ir_analyze_type_expr(IrAnalyze *ira, Scope *scope, AstNode *node) {
    Error err;

    ZigValue *result;
    ZigValue *result_ptr;
    create_result_ptr(ira->codegen, ira->codegen->builtin_types.entry_type, &result, &result_ptr);

    if ((err = ir_eval_const_value(ira->codegen, scope, node, result_ptr,
            ira->backward_branch_count, ira->backward_branch_quota,
            nullptr, nullptr, node, nullptr, ira->new_irb.exec, nullptr, UndefBad)))
    {
        return ira->codegen->builtin_types.entry_invalid;
    }
    if (type_is_invalid(result->type))
        return ira->codegen->builtin_types.entry_invalid;

    assert(result->special != ConstValSpecialRuntime);
    ZigType *res_type = result->data.x_type;

    return res_type;
}

static Stage1AirBasicBlock *ir_create_basic_block_gen(IrAnalyze *ira, Scope *scope, const char *name_hint) {
    Stage1AirBasicBlock *result = heap::c_allocator.create<Stage1AirBasicBlock>();
    result->scope = scope;
    result->name_hint = name_hint;
    result->debug_id = exec_next_debug_id_gen(ira->new_irb.exec);
    return result;
}

static Stage1AirBasicBlock *ir_build_bb_from(IrAnalyze *ira, Stage1ZirBasicBlock *other_bb) {
    Stage1AirBasicBlock *new_bb = ir_create_basic_block_gen(ira, other_bb->scope, other_bb->name_hint);
    other_bb->child = new_bb;
    return new_bb;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstDeclVar *) {
    return Stage1AirInstIdDeclVar;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstBr *) {
    return Stage1AirInstIdBr;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstCondBr *) {
    return Stage1AirInstIdCondBr;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstSwitchBr *) {
    return Stage1AirInstIdSwitchBr;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstPhi *) {
    return Stage1AirInstIdPhi;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstBinaryNot *) {
    return Stage1AirInstIdBinaryNot;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstNegation *) {
    return Stage1AirInstIdNegation;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstBinOp *) {
    return Stage1AirInstIdBinOp;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstLoadPtr *) {
    return Stage1AirInstIdLoadPtr;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstStorePtr *) {
    return Stage1AirInstIdStorePtr;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstVectorStoreElem *) {
    return Stage1AirInstIdVectorStoreElem;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstStructFieldPtr *) {
    return Stage1AirInstIdStructFieldPtr;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstUnionFieldPtr *) {
    return Stage1AirInstIdUnionFieldPtr;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstElemPtr *) {
    return Stage1AirInstIdElemPtr;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstVarPtr *) {
    return Stage1AirInstIdVarPtr;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstReturnPtr *) {
    return Stage1AirInstIdReturnPtr;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstCall *) {
    return Stage1AirInstIdCall;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstReturn *) {
    return Stage1AirInstIdReturn;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstCast *) {
    return Stage1AirInstIdCast;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstUnreachable *) {
    return Stage1AirInstIdUnreachable;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstAsm *) {
    return Stage1AirInstIdAsm;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstTestNonNull *) {
    return Stage1AirInstIdTestNonNull;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstOptionalUnwrapPtr *) {
    return Stage1AirInstIdOptionalUnwrapPtr;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstOptionalWrap *) {
    return Stage1AirInstIdOptionalWrap;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstUnionTag *) {
    return Stage1AirInstIdUnionTag;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstClz *) {
    return Stage1AirInstIdClz;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstCtz *) {
    return Stage1AirInstIdCtz;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstPopCount *) {
    return Stage1AirInstIdPopCount;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstBswap *) {
    return Stage1AirInstIdBswap;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstBitReverse *) {
    return Stage1AirInstIdBitReverse;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstRef *) {
    return Stage1AirInstIdRef;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstErrName *) {
    return Stage1AirInstIdErrName;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstCmpxchg *) {
    return Stage1AirInstIdCmpxchg;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstFence *) {
    return Stage1AirInstIdFence;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstReduce *) {
    return Stage1AirInstIdReduce;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstTruncate *) {
    return Stage1AirInstIdTruncate;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstShuffleVector *) {
    return Stage1AirInstIdShuffleVector;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstSelect *) {
    return Stage1AirInstIdSelect;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstSplat *) {
    return Stage1AirInstIdSplat;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstBoolNot *) {
    return Stage1AirInstIdBoolNot;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstMemset *) {
    return Stage1AirInstIdMemset;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstMemcpy *) {
    return Stage1AirInstIdMemcpy;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstSlice *) {
    return Stage1AirInstIdSlice;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstBreakpoint *) {
    return Stage1AirInstIdBreakpoint;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstReturnAddress *) {
    return Stage1AirInstIdReturnAddress;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstFrameAddress *) {
    return Stage1AirInstIdFrameAddress;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstFrameHandle *) {
    return Stage1AirInstIdFrameHandle;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstFrameSize *) {
    return Stage1AirInstIdFrameSize;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstOverflowOp *) {
    return Stage1AirInstIdOverflowOp;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstTestErr *) {
    return Stage1AirInstIdTestErr;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstMulAdd *) {
    return Stage1AirInstIdMulAdd;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstFloatOp *) {
    return Stage1AirInstIdFloatOp;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstUnwrapErrCode *) {
    return Stage1AirInstIdUnwrapErrCode;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstUnwrapErrPayload *) {
    return Stage1AirInstIdUnwrapErrPayload;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstErrWrapCode *) {
    return Stage1AirInstIdErrWrapCode;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstErrWrapPayload *) {
    return Stage1AirInstIdErrWrapPayload;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstPtrCast *) {
    return Stage1AirInstIdPtrCast;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstBitCast *) {
    return Stage1AirInstIdBitCast;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstWidenOrShorten *) {
    return Stage1AirInstIdWidenOrShorten;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstIntToPtr *) {
    return Stage1AirInstIdIntToPtr;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstPtrToInt *) {
    return Stage1AirInstIdPtrToInt;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstIntToEnum *) {
    return Stage1AirInstIdIntToEnum;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstIntToErr *) {
    return Stage1AirInstIdIntToErr;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstErrToInt *) {
    return Stage1AirInstIdErrToInt;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstPanic *) {
    return Stage1AirInstIdPanic;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstTagName *) {
    return Stage1AirInstIdTagName;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstFieldParentPtr *) {
    return Stage1AirInstIdFieldParentPtr;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstAlignCast *) {
    return Stage1AirInstIdAlignCast;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstErrorReturnTrace *) {
    return Stage1AirInstIdErrorReturnTrace;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstAtomicRmw *) {
    return Stage1AirInstIdAtomicRmw;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstAtomicLoad *) {
    return Stage1AirInstIdAtomicLoad;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstAtomicStore *) {
    return Stage1AirInstIdAtomicStore;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstSaveErrRetAddr *) {
    return Stage1AirInstIdSaveErrRetAddr;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstVectorToArray *) {
    return Stage1AirInstIdVectorToArray;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstArrayToVector *) {
    return Stage1AirInstIdArrayToVector;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstAssertZero *) {
    return Stage1AirInstIdAssertZero;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstAssertNonNull *) {
    return Stage1AirInstIdAssertNonNull;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstPtrOfArrayToSlice *) {
    return Stage1AirInstIdPtrOfArrayToSlice;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstSuspendBegin *) {
    return Stage1AirInstIdSuspendBegin;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstSuspendFinish *) {
    return Stage1AirInstIdSuspendFinish;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstAwait *) {
    return Stage1AirInstIdAwait;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstResume *) {
    return Stage1AirInstIdResume;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstSpillBegin *) {
    return Stage1AirInstIdSpillBegin;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstSpillEnd *) {
    return Stage1AirInstIdSpillEnd;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstVectorExtractElem *) {
    return Stage1AirInstIdVectorExtractElem;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstAlloca *) {
    return Stage1AirInstIdAlloca;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstConst *) {
    return Stage1AirInstIdConst;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstWasmMemorySize *) {
  return Stage1AirInstIdWasmMemorySize;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstWasmMemoryGrow *) {
  return Stage1AirInstIdWasmMemoryGrow;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstExtern *) {
    return Stage1AirInstIdExtern;
}

static constexpr Stage1AirInstId ir_inst_id(Stage1AirInstPrefetch *) {
    return Stage1AirInstIdPrefetch;
}

template<typename T>
static T *ir_create_inst_gen(IrBuilderGen *irb, Scope *scope, AstNode *source_node) {
    T *special_instruction = heap::c_allocator.create<T>();
    special_instruction->base.id = ir_inst_id(special_instruction);
    special_instruction->base.scope = scope;
    special_instruction->base.source_node = source_node;
    special_instruction->base.debug_id = exec_next_debug_id_gen(irb->exec);
    special_instruction->base.value = irb->codegen->pass1_arena->create<ZigValue>();
    return special_instruction;
}

template<typename T>
static T *ir_create_inst_noval(IrBuilderGen *irb, Scope *scope, AstNode *source_node) {
    T *special_instruction = heap::c_allocator.create<T>();
    special_instruction->base.id = ir_inst_id(special_instruction);
    special_instruction->base.scope = scope;
    special_instruction->base.source_node = source_node;
    special_instruction->base.debug_id = exec_next_debug_id_gen(irb->exec);
    return special_instruction;
}

template<typename T>
static T *ir_build_inst_gen(IrBuilderGen *irb, Scope *scope, AstNode *source_node) {
    T *special_instruction = ir_create_inst_gen<T>(irb, scope, source_node);
    ir_inst_gen_append(irb->current_basic_block, &special_instruction->base);
    return special_instruction;
}

template<typename T>
static T *ir_build_inst_noreturn(IrBuilderGen *irb, Scope *scope, AstNode *source_node) {
    T *special_instruction = ir_create_inst_noval<T>(irb, scope, source_node);
    special_instruction->base.value = irb->codegen->intern.for_unreachable();
    ir_inst_gen_append(irb->current_basic_block, &special_instruction->base);
    return special_instruction;
}

template<typename T>
static T *ir_build_inst_void(IrBuilderGen *irb, Scope *scope, AstNode *source_node) {
    T *special_instruction = ir_create_inst_noval<T>(irb, scope, source_node);
    special_instruction->base.value = irb->codegen->intern.for_void();
    ir_inst_gen_append(irb->current_basic_block, &special_instruction->base);
    return special_instruction;
}

Stage1AirInst *ir_create_alloca(CodeGen *g, Scope *scope, AstNode *source_node, ZigFn *fn,
        ZigType *var_type, const char *name_hint)
{
    Stage1AirInstAlloca *alloca_gen = heap::c_allocator.create<Stage1AirInstAlloca>();
    alloca_gen->base.id = Stage1AirInstIdAlloca;
    alloca_gen->base.source_node = source_node;
    alloca_gen->base.scope = scope;
    alloca_gen->base.value = g->pass1_arena->create<ZigValue>();
    alloca_gen->base.value->type = get_pointer_to_type(g, var_type, false);
    alloca_gen->base.ref_count = 1;
    alloca_gen->name_hint = name_hint;
    fn->alloca_gen_list.append(alloca_gen);
    return &alloca_gen->base;
}

static Stage1AirInst *ir_build_cast(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        ZigType *dest_type, Stage1AirInst *value, CastOp cast_op)
{
    Stage1AirInstCast *inst = ir_build_inst_gen<Stage1AirInstCast>(&ira->new_irb, scope, source_node);
    inst->base.value->type = dest_type;
    inst->value = value;
    inst->cast_op = cast_op;

    ir_ref_inst_gen(value);

    return &inst->base;
}

static Stage1AirInst *ir_build_cond_br_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node, Stage1AirInst *condition,
        Stage1AirBasicBlock *then_block, Stage1AirBasicBlock *else_block)
{
    Stage1AirInstCondBr *inst = ir_build_inst_noreturn<Stage1AirInstCondBr>(&ira->new_irb, scope, source_node);
    inst->condition = condition;
    inst->then_block = then_block;
    inst->else_block = else_block;

    ir_ref_inst_gen(condition);

    return &inst->base;
}

static Stage1AirInst *ir_build_return_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node, Stage1AirInst *operand) {
    Stage1AirInstReturn *inst = ir_build_inst_noreturn<Stage1AirInstReturn>(&ira->new_irb, scope, source_node);
    inst->operand = operand;

    if (operand != nullptr) ir_ref_inst_gen(operand);

    return &inst->base;
}

static Stage1AirInst *ir_build_bin_op_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node, ZigType *res_type,
        IrBinOp op_id, Stage1AirInst *op1, Stage1AirInst *op2, bool safety_check_on)
{
    Stage1AirInstBinOp *inst = ir_build_inst_gen<Stage1AirInstBinOp>(&ira->new_irb,
            scope, source_node);
    inst->base.value->type = res_type;
    inst->op_id = op_id;
    inst->op1 = op1;
    inst->op2 = op2;
    inst->safety_check_on = safety_check_on;

    ir_ref_inst_gen(op1);
    ir_ref_inst_gen(op2);

    return &inst->base;
}


static Stage1AirInst *ir_build_var_ptr_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node, ZigVar *var) {
    Stage1AirInstVarPtr *instruction = ir_build_inst_gen<Stage1AirInstVarPtr>(&ira->new_irb, scope, source_node);
    instruction->var = var;

    var->ref_count += 1;

    return &instruction->base;
}

static Stage1AirInst *ir_build_return_ptr(IrAnalyze *ira, Scope *scope, AstNode *source_node, ZigType *ty) {
    Stage1AirInstReturnPtr *instruction = ir_build_inst_gen<Stage1AirInstReturnPtr>(&ira->new_irb, scope, source_node);
    instruction->base.value->type = ty;
    return &instruction->base;
}

static Stage1AirInst *ir_build_elem_ptr_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        Stage1AirInst *array_ptr, Stage1AirInst *elem_index, bool safety_check_on, ZigType *return_type)
{
    Stage1AirInstElemPtr *instruction = ir_build_inst_gen<Stage1AirInstElemPtr>(&ira->new_irb, scope, source_node);
    instruction->base.value->type = return_type;
    instruction->array_ptr = array_ptr;
    instruction->elem_index = elem_index;
    instruction->safety_check_on = safety_check_on;

    ir_ref_inst_gen(array_ptr);
    ir_ref_inst_gen(elem_index);

    return &instruction->base;
}

static Stage1AirInst *ir_build_struct_field_ptr(IrAnalyze *ira, Scope *scope, AstNode *source_node,
    Stage1AirInst *struct_ptr, TypeStructField *field, ZigType *ptr_type)
{
    Stage1AirInstStructFieldPtr *inst = ir_build_inst_gen<Stage1AirInstStructFieldPtr>(&ira->new_irb, scope, source_node);
    inst->base.value->type = ptr_type;
    inst->struct_ptr = struct_ptr;
    inst->field = field;

    ir_ref_inst_gen(struct_ptr);

    return &inst->base;
}

static Stage1AirInst *ir_build_union_field_ptr(IrAnalyze *ira, Scope *scope, AstNode *source_node,
    Stage1AirInst *union_ptr, TypeUnionField *field, bool safety_check_on, bool initializing, ZigType *ptr_type)
{
    Stage1AirInstUnionFieldPtr *inst = ir_build_inst_gen<Stage1AirInstUnionFieldPtr>(&ira->new_irb,
            scope, source_node);
    inst->base.value->type = ptr_type;
    inst->initializing = initializing;
    inst->safety_check_on = safety_check_on;
    inst->union_ptr = union_ptr;
    inst->field = field;

    ir_ref_inst_gen(union_ptr);

    return &inst->base;
}

static Stage1AirInstCall *ir_build_call_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        ZigFn *fn_entry, Stage1AirInst *fn_ref, size_t arg_count, Stage1AirInst **args,
        CallModifier modifier, Stage1AirInst *new_stack, bool is_async_call_builtin,
        Stage1AirInst *result_loc, ZigType *return_type)
{
    Stage1AirInstCall *call_instruction = ir_build_inst_gen<Stage1AirInstCall>(&ira->new_irb,
            scope, source_node);
    call_instruction->base.value->type = return_type;
    call_instruction->fn_entry = fn_entry;
    call_instruction->fn_ref = fn_ref;
    call_instruction->args = args;
    call_instruction->arg_count = arg_count;
    call_instruction->modifier = modifier;
    call_instruction->is_async_call_builtin = is_async_call_builtin;
    call_instruction->new_stack = new_stack;
    call_instruction->result_loc = result_loc;

    if (fn_ref != nullptr) ir_ref_inst_gen(fn_ref);
    for (size_t i = 0; i < arg_count; i += 1)
        ir_ref_inst_gen(args[i]);
    if (new_stack != nullptr) ir_ref_inst_gen(new_stack);
    if (result_loc != nullptr) ir_ref_inst_gen(result_loc);

    return call_instruction;
}

static Stage1AirInst *ir_build_phi_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node, size_t incoming_count,
        Stage1AirBasicBlock **incoming_blocks, Stage1AirInst **incoming_values, ZigType *result_type)
{
    assert(incoming_count != 0);
    assert(incoming_count != SIZE_MAX);

    Stage1AirInstPhi *phi_instruction = ir_build_inst_gen<Stage1AirInstPhi>(&ira->new_irb,
            scope, source_node);
    phi_instruction->base.value->type = result_type;
    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_inst_gen(incoming_values[i]);
    }

    return &phi_instruction->base;
}

static Stage1AirInst *ir_build_br_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node, Stage1AirBasicBlock *dest_block) {
    Stage1AirInstBr *inst = ir_build_inst_noreturn<Stage1AirInstBr>(&ira->new_irb, scope, source_node);
    inst->dest_block = dest_block;

    return &inst->base;
}

static Stage1AirInst *ir_build_negation(IrAnalyze *ira, Scope *scope, AstNode *source_node, Stage1AirInst *operand, ZigType *expr_type, bool wrapping) {
    Stage1AirInstNegation *instruction = ir_build_inst_gen<Stage1AirInstNegation>(&ira->new_irb,
            scope, source_node);
    instruction->base.value->type = expr_type;
    instruction->operand = operand;
    instruction->wrapping = wrapping;

    ir_ref_inst_gen(operand);

    return &instruction->base;
}

static Stage1AirInst *ir_build_binary_not(IrAnalyze *ira, Scope *scope, AstNode *source_node, Stage1AirInst *operand,
        ZigType *expr_type)
{
    Stage1AirInstBinaryNot *instruction = ir_build_inst_gen<Stage1AirInstBinaryNot>(&ira->new_irb,
            scope, source_node);
    instruction->base.value->type = expr_type;
    instruction->operand = operand;

    ir_ref_inst_gen(operand);

    return &instruction->base;
}

static Stage1AirInst *ir_build_unreachable_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node) {
    Stage1AirInstUnreachable *inst = ir_build_inst_noreturn<Stage1AirInstUnreachable>(&ira->new_irb, scope, source_node);
    return &inst->base;
}

static Stage1AirInst *ir_build_store_ptr_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node, Stage1AirInst *ptr, Stage1AirInst *value) {
    Stage1AirInstStorePtr *instruction = ir_build_inst_void<Stage1AirInstStorePtr>(&ira->new_irb,
            scope, source_node);
    instruction->ptr = ptr;
    instruction->value = value;

    ir_ref_inst_gen(ptr);
    ir_ref_inst_gen(value);

    return &instruction->base;
}

static Stage1AirInst *ir_build_vector_store_elem(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        Stage1AirInst *vector_ptr, Stage1AirInst *index, Stage1AirInst *value)
{
    Stage1AirInstVectorStoreElem *inst = ir_build_inst_void<Stage1AirInstVectorStoreElem>(
            &ira->new_irb, scope, source_node);
    inst->vector_ptr = vector_ptr;
    inst->index = index;
    inst->value = value;

    ir_ref_inst_gen(vector_ptr);
    ir_ref_inst_gen(index);
    ir_ref_inst_gen(value);

    return &inst->base;
}

static Stage1AirInst *ir_build_var_decl_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        ZigVar *var, Stage1AirInst *var_ptr)
{
    Stage1AirInstDeclVar *inst = ir_build_inst_gen<Stage1AirInstDeclVar>(&ira->new_irb,
            scope, source_node);
    inst->base.value->special = ConstValSpecialStatic;
    inst->base.value->type = ira->codegen->builtin_types.entry_void;
    inst->var = var;
    inst->var_ptr = var_ptr;

    ir_ref_inst_gen(var_ptr);

    return &inst->base;
}

static Stage1AirInst *ir_build_extern_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node, Buf *name,
        GlobalLinkageId linkage, bool is_thread_local, ZigType *expr_type)
{
    Stage1AirInstExtern *instruction = ir_build_inst_gen<Stage1AirInstExtern>(&ira->new_irb,
            scope, source_node);
    instruction->base.value->type = expr_type;
    instruction->name = name;
    instruction->linkage = linkage;
    instruction->is_thread_local = is_thread_local;

    return &instruction->base;
}

static Stage1AirInst *ir_build_load_ptr_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        Stage1AirInst *ptr, ZigType *ty, Stage1AirInst *result_loc)
{
    Stage1AirInstLoadPtr *instruction = ir_build_inst_gen<Stage1AirInstLoadPtr>(
            &ira->new_irb, scope, source_node);
    instruction->base.value->type = ty;
    instruction->ptr = ptr;
    instruction->result_loc = result_loc;

    ir_ref_inst_gen(ptr);
    if (result_loc != nullptr) ir_ref_inst_gen(result_loc);

    return &instruction->base;
}

static Stage1AirInst *ir_build_asm_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        Buf *asm_template, AsmToken *token_list, size_t token_list_len,
        Stage1AirInst **input_list, Stage1AirInst **output_types, ZigVar **output_vars, size_t return_count,
        bool has_side_effects, ZigType *return_type)
{
    Stage1AirInstAsm *instruction = ir_build_inst_gen<Stage1AirInstAsm>(&ira->new_irb, scope, source_node);
    instruction->base.value->type = return_type;
    instruction->asm_template = asm_template;
    instruction->token_list = token_list;
    instruction->token_list_len = token_list_len;
    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) {
        Stage1AirInst *output_type = output_types[i];
        if (output_type) ir_ref_inst_gen(output_type);
    }

    for (size_t i = 0; i < source_node->data.asm_expr.input_list.length; i += 1) {
        Stage1AirInst *input_value = input_list[i];
        ir_ref_inst_gen(input_value);
    }

    return &instruction->base;
}

static Stage1AirInst *ir_build_test_non_null_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node, Stage1AirInst *value) {
    Stage1AirInstTestNonNull *inst = ir_build_inst_gen<Stage1AirInstTestNonNull>(&ira->new_irb,
            scope, source_node);
    inst->base.value->type = ira->codegen->builtin_types.entry_bool;
    inst->value = value;

    ir_ref_inst_gen(value);

    return &inst->base;
}

static Stage1AirInst *ir_build_optional_unwrap_ptr_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        Stage1AirInst *base_ptr, bool safety_check_on, bool initializing, ZigType *result_type)
{
    Stage1AirInstOptionalUnwrapPtr *inst = ir_build_inst_gen<Stage1AirInstOptionalUnwrapPtr>(&ira->new_irb,
            scope, source_node);
    inst->base.value->type = result_type;
    inst->base_ptr = base_ptr;
    inst->safety_check_on = safety_check_on;
    inst->initializing = initializing;

    ir_ref_inst_gen(base_ptr);

    return &inst->base;
}

static Stage1AirInst *ir_build_optional_wrap(IrAnalyze *ira, Scope *scope, AstNode *source_node, ZigType *result_ty,
        Stage1AirInst *operand, Stage1AirInst *result_loc)
{
    Stage1AirInstOptionalWrap *instruction = ir_build_inst_gen<Stage1AirInstOptionalWrap>(
            &ira->new_irb, scope, source_node);
    instruction->base.value->type = result_ty;
    instruction->operand = operand;
    instruction->result_loc = result_loc;

    ir_ref_inst_gen(operand);
    if (result_loc != nullptr) ir_ref_inst_gen(result_loc);

    return &instruction->base;
}

static Stage1AirInst *ir_build_err_wrap_payload(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        ZigType *result_type, Stage1AirInst *operand, Stage1AirInst *result_loc)
{
    Stage1AirInstErrWrapPayload *instruction = ir_build_inst_gen<Stage1AirInstErrWrapPayload>(
            &ira->new_irb, scope, source_node);
    instruction->base.value->type = result_type;
    instruction->operand = operand;
    instruction->result_loc = result_loc;

    ir_ref_inst_gen(operand);
    if (result_loc != nullptr) ir_ref_inst_gen(result_loc);

    return &instruction->base;
}

static Stage1AirInst *ir_build_err_wrap_code(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        ZigType *result_type, Stage1AirInst *operand, Stage1AirInst *result_loc)
{
    Stage1AirInstErrWrapCode *instruction = ir_build_inst_gen<Stage1AirInstErrWrapCode>(
            &ira->new_irb, scope, source_node);
    instruction->base.value->type = result_type;
    instruction->operand = operand;
    instruction->result_loc = result_loc;

    ir_ref_inst_gen(operand);
    if (result_loc != nullptr) ir_ref_inst_gen(result_loc);

    return &instruction->base;
}

static Stage1AirInst *ir_build_clz_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node, ZigType *result_type, Stage1AirInst *op) {
    Stage1AirInstClz *instruction = ir_build_inst_gen<Stage1AirInstClz>(&ira->new_irb,
            scope, source_node);
    instruction->base.value->type = result_type;
    instruction->op = op;

    ir_ref_inst_gen(op);

    return &instruction->base;
}

static Stage1AirInst *ir_build_ctz_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node, ZigType *result_type, Stage1AirInst *op) {
    Stage1AirInstCtz *instruction = ir_build_inst_gen<Stage1AirInstCtz>(&ira->new_irb,
            scope, source_node);
    instruction->base.value->type = result_type;
    instruction->op = op;

    ir_ref_inst_gen(op);

    return &instruction->base;
}

static Stage1AirInst *ir_build_pop_count_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node, ZigType *result_type,
        Stage1AirInst *op)
{
    Stage1AirInstPopCount *instruction = ir_build_inst_gen<Stage1AirInstPopCount>(&ira->new_irb,
            scope, source_node);
    instruction->base.value->type = result_type;
    instruction->op = op;

    ir_ref_inst_gen(op);

    return &instruction->base;
}

static Stage1AirInst *ir_build_bswap_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node, ZigType *op_type,
        Stage1AirInst *op)
{
    Stage1AirInstBswap *instruction = ir_build_inst_gen<Stage1AirInstBswap>(&ira->new_irb,
            scope, source_node);
    instruction->base.value->type = op_type;
    instruction->op = op;

    ir_ref_inst_gen(op);

    return &instruction->base;
}

static Stage1AirInst *ir_build_bit_reverse_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node, ZigType *int_type,
        Stage1AirInst *op)
{
    Stage1AirInstBitReverse *instruction = ir_build_inst_gen<Stage1AirInstBitReverse>(&ira->new_irb,
            scope, source_node);
    instruction->base.value->type = int_type;
    instruction->op = op;

    ir_ref_inst_gen(op);

    return &instruction->base;
}

static Stage1AirInstSwitchBr *ir_build_switch_br_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        Stage1AirInst *target_value, Stage1AirBasicBlock *else_block, size_t case_count, Stage1AirInstSwitchBrCase *cases)
{
    Stage1AirInstSwitchBr *instruction = ir_build_inst_noreturn<Stage1AirInstSwitchBr>(&ira->new_irb,
            scope, source_node);
    instruction->target_value = target_value;
    instruction->else_block = else_block;
    instruction->case_count = case_count;
    instruction->cases = cases;

    ir_ref_inst_gen(target_value);

    for (size_t i = 0; i < case_count; i += 1) {
        ir_ref_inst_gen(cases[i].value);
    }

    return instruction;
}

static Stage1AirInst *ir_build_union_tag(IrAnalyze *ira, Scope *scope, AstNode *source_node, Stage1AirInst *value,
        ZigType *tag_type)
{
    Stage1AirInstUnionTag *instruction = ir_build_inst_gen<Stage1AirInstUnionTag>(&ira->new_irb,
            scope, source_node);
    instruction->value = value;
    instruction->base.value->type = tag_type;

    ir_ref_inst_gen(value);

    return &instruction->base;
}

static Stage1AirInst *ir_build_ref_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node, ZigType *result_type,
        Stage1AirInst *operand, Stage1AirInst *result_loc)
{
    Stage1AirInstRef *instruction = ir_build_inst_gen<Stage1AirInstRef>(&ira->new_irb,
            scope, source_node);
    instruction->base.value->type = result_type;
    instruction->operand = operand;
    instruction->result_loc = result_loc;

    ir_ref_inst_gen(operand);
    if (result_loc != nullptr) ir_ref_inst_gen(result_loc);

    return &instruction->base;
}

static Stage1AirInst *ir_build_err_name_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node, Stage1AirInst *value,
        ZigType *str_type)
{
    Stage1AirInstErrName *instruction = ir_build_inst_gen<Stage1AirInstErrName>(&ira->new_irb,
            scope, source_node);
    instruction->base.value->type = str_type;
    instruction->value = value;

    ir_ref_inst_gen(value);

    return &instruction->base;
}

static Stage1AirInst *ir_build_cmpxchg_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node, ZigType *result_type,
    Stage1AirInst *ptr, Stage1AirInst *cmp_value, Stage1AirInst *new_value,
    AtomicOrder success_order, AtomicOrder failure_order, bool is_weak, Stage1AirInst *result_loc)
{
    Stage1AirInstCmpxchg *instruction = ir_build_inst_gen<Stage1AirInstCmpxchg>(&ira->new_irb,
            scope, source_node);
    instruction->base.value->type = result_type;
    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;
    instruction->result_loc = result_loc;

    ir_ref_inst_gen(ptr);
    ir_ref_inst_gen(cmp_value);
    ir_ref_inst_gen(new_value);
    if (result_loc != nullptr) ir_ref_inst_gen(result_loc);

    return &instruction->base;
}

static Stage1AirInst *ir_build_fence_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node, AtomicOrder order) {
    Stage1AirInstFence *instruction = ir_build_inst_void<Stage1AirInstFence>(&ira->new_irb,
            scope, source_node);
    instruction->order = order;

    return &instruction->base;
}

static Stage1AirInst *ir_build_reduce_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node, ReduceOp op, Stage1AirInst *value, ZigType *result_type) {
    Stage1AirInstReduce *instruction = ir_build_inst_gen<Stage1AirInstReduce>(&ira->new_irb,
            scope, source_node);
    instruction->base.value->type = result_type;
    instruction->op = op;
    instruction->value = value;

    ir_ref_inst_gen(value);

    return &instruction->base;
}

static void ir_set_cursor_at_end_gen(IrBuilderGen *irb, Stage1AirBasicBlock *basic_block) {
    assert(basic_block);
    irb->current_basic_block = basic_block;
}

static void ir_append_basic_block_gen(IrBuilderGen *irb, Stage1AirBasicBlock *bb) {
    assert(!bb->already_appended);
    bb->already_appended = true;
    irb->exec->basic_block_list.append(bb);
}

static void ir_set_cursor_at_end_and_append_block_gen(IrBuilderGen *irb, Stage1AirBasicBlock *basic_block) {
    ir_append_basic_block_gen(irb, basic_block);
    ir_set_cursor_at_end_gen(irb, basic_block);
}

static Stage1AirInst *ir_build_suspend_begin_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node) {
    Stage1AirInstSuspendBegin *inst = ir_build_inst_void<Stage1AirInstSuspendBegin>(&ira->new_irb,
            scope, source_node);
    return &inst->base;
}

static Stage1AirInst *ir_build_save_err_ret_addr_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node) {
    Stage1AirInstSaveErrRetAddr *inst = ir_build_inst_void<Stage1AirInstSaveErrRetAddr>(&ira->new_irb,
            scope, source_node);
    return &inst->base;
}

static Stage1AirInst *ir_build_truncate_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node, ZigType *dest_type,
        Stage1AirInst *target)
{
    Stage1AirInstTruncate *instruction = ir_build_inst_gen<Stage1AirInstTruncate>(&ira->new_irb,
            scope, source_node);
    instruction->base.value->type = dest_type;
    instruction->target = target;

    ir_ref_inst_gen(target);

    return &instruction->base;
}

static Stage1AirInst *ir_build_shuffle_vector_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        ZigType *result_type, Stage1AirInst *a, Stage1AirInst *b, Stage1AirInst *mask)
{
    Stage1AirInstShuffleVector *inst = ir_build_inst_gen<Stage1AirInstShuffleVector>(&ira->new_irb, scope, source_node);
    inst->base.value->type = result_type;
    inst->a = a;
    inst->b = b;
    inst->mask = mask;

    ir_ref_inst_gen(a);
    ir_ref_inst_gen(b);
    ir_ref_inst_gen(mask);

    return &inst->base;
}

static Stage1AirInst *ir_build_select_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        ZigType *result_type, Stage1AirInst *pred, Stage1AirInst *a, Stage1AirInst *b)
{
    Stage1AirInstSelect *inst = ir_build_inst_gen<Stage1AirInstSelect>(&ira->new_irb, scope, source_node);
    inst->base.value->type = result_type;
    inst->pred = pred;
    inst->a = a;
    inst->b = b;

    ir_ref_inst_gen(pred);
    ir_ref_inst_gen(a);
    ir_ref_inst_gen(b);

    return &inst->base;
}

static Stage1AirInst *ir_build_splat_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node, ZigType *result_type,
    Stage1AirInst *scalar)
{
    Stage1AirInstSplat *instruction = ir_build_inst_gen<Stage1AirInstSplat>(
            &ira->new_irb, scope, source_node);
    instruction->base.value->type = result_type;
    instruction->scalar = scalar;

    ir_ref_inst_gen(scalar);

    return &instruction->base;
}

static Stage1AirInst *ir_build_bool_not_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node, Stage1AirInst *value) {
    Stage1AirInstBoolNot *instruction = ir_build_inst_gen<Stage1AirInstBoolNot>(&ira->new_irb,
            scope, source_node);
    instruction->base.value->type = ira->codegen->builtin_types.entry_bool;
    instruction->value = value;

    ir_ref_inst_gen(value);

    return &instruction->base;
}

static Stage1AirInst *ir_build_memset_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node,
    Stage1AirInst *dest_ptr, Stage1AirInst *byte, Stage1AirInst *count)
{
    Stage1AirInstMemset *instruction = ir_build_inst_void<Stage1AirInstMemset>(&ira->new_irb,
            scope, source_node);
    instruction->dest_ptr = dest_ptr;
    instruction->byte = byte;
    instruction->count = count;

    ir_ref_inst_gen(dest_ptr);
    ir_ref_inst_gen(byte);
    ir_ref_inst_gen(count);

    return &instruction->base;
}

static Stage1AirInst *ir_build_memcpy_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node,
    Stage1AirInst *dest_ptr, Stage1AirInst *src_ptr, Stage1AirInst *count)
{
    Stage1AirInstMemcpy *instruction = ir_build_inst_void<Stage1AirInstMemcpy>(&ira->new_irb,
            scope, source_node);
    instruction->dest_ptr = dest_ptr;
    instruction->src_ptr = src_ptr;
    instruction->count = count;

    ir_ref_inst_gen(dest_ptr);
    ir_ref_inst_gen(src_ptr);
    ir_ref_inst_gen(count);

    return &instruction->base;
}

static Stage1AirInst *ir_build_slice_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node, ZigType *slice_type,
    Stage1AirInst *ptr, Stage1AirInst *start, Stage1AirInst *end, bool safety_check_on, Stage1AirInst *result_loc,
    ZigValue *sentinel)
{
    Stage1AirInstSlice *instruction = ir_build_inst_gen<Stage1AirInstSlice>(
            &ira->new_irb, scope, source_node);
    instruction->base.value->type = slice_type;
    instruction->ptr = ptr;
    instruction->start = start;
    instruction->end = end;
    instruction->safety_check_on = safety_check_on;
    instruction->result_loc = result_loc;
    instruction->sentinel = sentinel;

    ir_ref_inst_gen(ptr);
    ir_ref_inst_gen(start);
    if (end != nullptr) ir_ref_inst_gen(end);
    if (result_loc != nullptr) ir_ref_inst_gen(result_loc);

    return &instruction->base;
}

static Stage1AirInst *ir_build_breakpoint_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node) {
    Stage1AirInstBreakpoint *instruction = ir_build_inst_void<Stage1AirInstBreakpoint>(&ira->new_irb,
            scope, source_node);
    return &instruction->base;
}

static Stage1AirInst *ir_build_return_address_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node) {
    Stage1AirInstReturnAddress *inst = ir_build_inst_gen<Stage1AirInstReturnAddress>(&ira->new_irb, scope, source_node);
    inst->base.value->type = ira->codegen->builtin_types.entry_usize;
    return &inst->base;
}

static Stage1AirInst *ir_build_frame_address_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node) {
    Stage1AirInstFrameAddress *inst = ir_build_inst_gen<Stage1AirInstFrameAddress>(&ira->new_irb, scope, source_node);
    inst->base.value->type = ira->codegen->builtin_types.entry_usize;
    return &inst->base;
}

static Stage1AirInst *ir_build_handle_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node, ZigType *ty) {
    Stage1AirInstFrameHandle *inst = ir_build_inst_gen<Stage1AirInstFrameHandle>(&ira->new_irb, scope, source_node);
    inst->base.value->type = ty;
    return &inst->base;
}

static Stage1AirInst *ir_build_frame_size_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node, Stage1AirInst *fn)
{
    Stage1AirInstFrameSize *inst = ir_build_inst_gen<Stage1AirInstFrameSize>(&ira->new_irb, scope, source_node);
    inst->base.value->type = ira->codegen->builtin_types.entry_usize;
    inst->fn = fn;

    ir_ref_inst_gen(fn);

    return &inst->base;
}

static Stage1AirInst *ir_build_overflow_op_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        IrOverflowOp op, Stage1AirInst *op1, Stage1AirInst *op2, Stage1AirInst *result_ptr,
        ZigType *result_ptr_type)
{
    Stage1AirInstOverflowOp *instruction = ir_build_inst_gen<Stage1AirInstOverflowOp>(&ira->new_irb,
            scope, source_node);
    instruction->base.value->type = ira->codegen->builtin_types.entry_bool;
    instruction->op = op;
    instruction->op1 = op1;
    instruction->op2 = op2;
    instruction->result_ptr = result_ptr;
    instruction->result_ptr_type = result_ptr_type;

    ir_ref_inst_gen(op1);
    ir_ref_inst_gen(op2);
    ir_ref_inst_gen(result_ptr);

    return &instruction->base;
}

static Stage1AirInst *ir_build_float_op_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node, Stage1AirInst *operand,
        BuiltinFnId fn_id, ZigType *operand_type)
{
    Stage1AirInstFloatOp *instruction = ir_build_inst_gen<Stage1AirInstFloatOp>(&ira->new_irb,
            scope, source_node);
    instruction->base.value->type = operand_type;
    instruction->operand = operand;
    instruction->fn_id = fn_id;

    ir_ref_inst_gen(operand);

    return &instruction->base;
}

static Stage1AirInst *ir_build_mul_add_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node, Stage1AirInst *op1, Stage1AirInst *op2,
        Stage1AirInst *op3, ZigType *expr_type)
{
    Stage1AirInstMulAdd *instruction = ir_build_inst_gen<Stage1AirInstMulAdd>(&ira->new_irb,
            scope, source_node);
    instruction->base.value->type = expr_type;
    instruction->op1 = op1;
    instruction->op2 = op2;
    instruction->op3 = op3;

    ir_ref_inst_gen(op1);
    ir_ref_inst_gen(op2);
    ir_ref_inst_gen(op3);

    return &instruction->base;
}

static Stage1AirInst *ir_build_test_err_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node, Stage1AirInst *err_union) {
    Stage1AirInstTestErr *instruction = ir_build_inst_gen<Stage1AirInstTestErr>(
            &ira->new_irb, scope, source_node);
    instruction->base.value->type = ira->codegen->builtin_types.entry_bool;
    instruction->err_union = err_union;

    ir_ref_inst_gen(err_union);

    return &instruction->base;
}

static Stage1AirInst *ir_build_unwrap_err_code_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node,
    Stage1AirInst *err_union_ptr, ZigType *result_type)
{
    Stage1AirInstUnwrapErrCode *inst = ir_build_inst_gen<Stage1AirInstUnwrapErrCode>(&ira->new_irb, scope, source_node);
    inst->base.value->type = result_type;
    inst->err_union_ptr = err_union_ptr;

    ir_ref_inst_gen(err_union_ptr);

    return &inst->base;
}

static Stage1AirInst *ir_build_unwrap_err_payload_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node,
    Stage1AirInst *value, bool safety_check_on, bool initializing, ZigType *result_type)
{
    Stage1AirInstUnwrapErrPayload *inst = ir_build_inst_gen<Stage1AirInstUnwrapErrPayload>(&ira->new_irb, scope, source_node);
    inst->base.value->type = result_type;
    inst->value = value;
    inst->safety_check_on = safety_check_on;
    inst->initializing = initializing;

    ir_ref_inst_gen(value);

    return &inst->base;
}

static Stage1AirInst *ir_build_ptr_cast_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        ZigType *ptr_type, Stage1AirInst *ptr, bool safety_check_on)
{
    Stage1AirInstPtrCast *instruction = ir_build_inst_gen<Stage1AirInstPtrCast>(
            &ira->new_irb, scope, source_node);
    instruction->base.value->type = ptr_type;
    instruction->ptr = ptr;
    instruction->safety_check_on = safety_check_on;

    ir_ref_inst_gen(ptr);

    return &instruction->base;
}

static Stage1AirInst *ir_build_bit_cast_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        Stage1AirInst *operand, ZigType *ty)
{
    Stage1AirInstBitCast *instruction = ir_build_inst_gen<Stage1AirInstBitCast>(
            &ira->new_irb, scope, source_node);
    instruction->base.value->type = ty;
    instruction->operand = operand;

    ir_ref_inst_gen(operand);

    return &instruction->base;
}

static Stage1AirInst *ir_build_widen_or_shorten(IrAnalyze *ira, Scope *scope, AstNode *source_node, Stage1AirInst *target,
        ZigType *result_type)
{
    Stage1AirInstWidenOrShorten *inst = ir_build_inst_gen<Stage1AirInstWidenOrShorten>(&ira->new_irb, scope, source_node);
    inst->base.value->type = result_type;
    inst->target = target;

    ir_ref_inst_gen(target);

    return &inst->base;
}

static Stage1AirInst *ir_build_int_to_ptr_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        Stage1AirInst *target, ZigType *ptr_type)
{
    Stage1AirInstIntToPtr *instruction = ir_build_inst_gen<Stage1AirInstIntToPtr>(&ira->new_irb, scope, source_node);
    instruction->base.value->type = ptr_type;
    instruction->target = target;

    ir_ref_inst_gen(target);

    return &instruction->base;
}

static Stage1AirInst *ir_build_ptr_to_int_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node, Stage1AirInst *target) {
    Stage1AirInstPtrToInt *inst = ir_build_inst_gen<Stage1AirInstPtrToInt>(&ira->new_irb, scope, source_node);
    inst->base.value->type = ira->codegen->builtin_types.entry_usize;
    inst->target = target;

    ir_ref_inst_gen(target);

    return &inst->base;
}

static Stage1AirInst *ir_build_int_to_enum_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        ZigType *dest_type, Stage1AirInst *target)
{
    Stage1AirInstIntToEnum *instruction = ir_build_inst_gen<Stage1AirInstIntToEnum>(&ira->new_irb, scope, source_node);
    instruction->base.value->type = dest_type;
    instruction->target = target;

    ir_ref_inst_gen(target);

    return &instruction->base;
}

static Stage1AirInst *ir_build_int_to_err_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node, Stage1AirInst *target,
        ZigType *wanted_type)
{
    Stage1AirInstIntToErr *instruction = ir_build_inst_gen<Stage1AirInstIntToErr>(&ira->new_irb, scope, source_node);
    instruction->base.value->type = wanted_type;
    instruction->target = target;

    ir_ref_inst_gen(target);

    return &instruction->base;
}

static Stage1AirInst *ir_build_err_to_int_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node, Stage1AirInst *target,
        ZigType *wanted_type)
{
    Stage1AirInstErrToInt *instruction = ir_build_inst_gen<Stage1AirInstErrToInt>(&ira->new_irb, scope, source_node);
    instruction->base.value->type = wanted_type;
    instruction->target = target;

    ir_ref_inst_gen(target);

    return &instruction->base;
}

static Stage1AirInst *ir_build_panic_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node, Stage1AirInst *msg) {
    Stage1AirInstPanic *instruction = ir_build_inst_noreturn<Stage1AirInstPanic>(&ira->new_irb,
            scope, source_node);
    instruction->msg = msg;

    ir_ref_inst_gen(msg);

    return &instruction->base;
}

static Stage1AirInst *ir_build_tag_name_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node, Stage1AirInst *target,
        ZigType *result_type)
{
    Stage1AirInstTagName *instruction = ir_build_inst_gen<Stage1AirInstTagName>(&ira->new_irb,
            scope, source_node);
    instruction->base.value->type = result_type;
    instruction->target = target;

    ir_ref_inst_gen(target);

    return &instruction->base;
}

static Stage1AirInst *ir_build_field_parent_ptr_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        Stage1AirInst *field_ptr, TypeStructField *field, ZigType *result_type)
{
    Stage1AirInstFieldParentPtr *inst = ir_build_inst_gen<Stage1AirInstFieldParentPtr>(&ira->new_irb,
            scope, source_node);
    inst->base.value->type = result_type;
    inst->field_ptr = field_ptr;
    inst->field = field;

    ir_ref_inst_gen(field_ptr);

    return &inst->base;
}

static Stage1AirInst *ir_build_align_cast_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node, Stage1AirInst *target,
        ZigType *result_type)
{
    Stage1AirInstAlignCast *instruction = ir_build_inst_gen<Stage1AirInstAlignCast>(&ira->new_irb, scope, source_node);
    instruction->base.value->type = result_type;
    instruction->target = target;

    ir_ref_inst_gen(target);

    return &instruction->base;
}

static Stage1AirInst *ir_build_error_return_trace_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        IrInstErrorReturnTraceOptional optional, ZigType *result_type)
{
    Stage1AirInstErrorReturnTrace *inst = ir_build_inst_gen<Stage1AirInstErrorReturnTrace>(&ira->new_irb, scope, source_node);
    inst->base.value->type = result_type;
    inst->optional = optional;

    return &inst->base;
}

static Stage1AirInst *ir_build_atomic_rmw_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        Stage1AirInst *ptr, Stage1AirInst *operand, AtomicRmwOp op, AtomicOrder ordering, ZigType *operand_type)
{
    Stage1AirInstAtomicRmw *instruction = ir_build_inst_gen<Stage1AirInstAtomicRmw>(&ira->new_irb, scope, source_node);
    instruction->base.value->type = operand_type;
    instruction->ptr = ptr;
    instruction->op = op;
    instruction->operand = operand;
    instruction->ordering = ordering;

    ir_ref_inst_gen(ptr);
    ir_ref_inst_gen(operand);

    return &instruction->base;
}

static Stage1AirInst *ir_build_atomic_load_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        Stage1AirInst *ptr, AtomicOrder ordering, ZigType *operand_type)
{
    Stage1AirInstAtomicLoad *instruction = ir_build_inst_gen<Stage1AirInstAtomicLoad>(&ira->new_irb,
            scope, source_node);
    instruction->base.value->type = operand_type;
    instruction->ptr = ptr;
    instruction->ordering = ordering;

    ir_ref_inst_gen(ptr);

    return &instruction->base;
}

static Stage1AirInst *ir_build_atomic_store_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        Stage1AirInst *ptr, Stage1AirInst *value, AtomicOrder ordering)
{
    Stage1AirInstAtomicStore *instruction = ir_build_inst_void<Stage1AirInstAtomicStore>(&ira->new_irb,
            scope, source_node);
    instruction->ptr = ptr;
    instruction->value = value;
    instruction->ordering = ordering;

    ir_ref_inst_gen(ptr);
    ir_ref_inst_gen(value);

    return &instruction->base;
}


static Stage1AirInst *ir_build_vector_to_array(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        ZigType *result_type, Stage1AirInst *vector, Stage1AirInst *result_loc)
{
    Stage1AirInstVectorToArray *instruction = ir_build_inst_gen<Stage1AirInstVectorToArray>(&ira->new_irb,
        scope, source_node);
    instruction->base.value->type = result_type;
    instruction->vector = vector;
    instruction->result_loc = result_loc;

    ir_ref_inst_gen(vector);
    ir_ref_inst_gen(result_loc);

    return &instruction->base;
}

static Stage1AirInst *ir_build_ptr_of_array_to_slice(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        ZigType *result_type, Stage1AirInst *operand, Stage1AirInst *result_loc)
{
    Stage1AirInstPtrOfArrayToSlice *instruction = ir_build_inst_gen<Stage1AirInstPtrOfArrayToSlice>(&ira->new_irb,
        scope, source_node);
    instruction->base.value->type = result_type;
    instruction->operand = operand;
    instruction->result_loc = result_loc;

    ir_ref_inst_gen(operand);
    ir_ref_inst_gen(result_loc);

    return &instruction->base;
}

static Stage1AirInst *ir_build_array_to_vector(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        Stage1AirInst *array, ZigType *result_type)
{
    Stage1AirInstArrayToVector *instruction = ir_build_inst_gen<Stage1AirInstArrayToVector>(&ira->new_irb,
        scope, source_node);
    instruction->base.value->type = result_type;
    instruction->array = array;

    ir_ref_inst_gen(array);

    return &instruction->base;
}

static Stage1AirInst *ir_build_assert_zero(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        Stage1AirInst *target)
{
    Stage1AirInstAssertZero *instruction = ir_build_inst_gen<Stage1AirInstAssertZero>(&ira->new_irb,
        scope, source_node);
    instruction->base.value->type = ira->codegen->builtin_types.entry_void;
    instruction->target = target;

    ir_ref_inst_gen(target);

    return &instruction->base;
}

static Stage1AirInst *ir_build_assert_non_null(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        Stage1AirInst *target)
{
    Stage1AirInstAssertNonNull *instruction = ir_build_inst_gen<Stage1AirInstAssertNonNull>(&ira->new_irb,
        scope, source_node);
    instruction->base.value->type = ira->codegen->builtin_types.entry_void;
    instruction->target = target;

    ir_ref_inst_gen(target);

    return &instruction->base;
}

static Stage1AirInstAlloca *ir_build_alloca_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        uint32_t align, const char *name_hint)
{
    Stage1AirInstAlloca *instruction = ir_create_inst_gen<Stage1AirInstAlloca>(&ira->new_irb,
            scope, source_node);
    instruction->align = align;
    instruction->name_hint = name_hint;

    return instruction;
}

static Stage1AirInst *ir_build_suspend_finish_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node, Stage1AirInstSuspendBegin *begin) {
    Stage1AirInstSuspendFinish *inst = ir_build_inst_void<Stage1AirInstSuspendFinish>(&ira->new_irb,
            scope, source_node);
    inst->begin = begin;

    ir_ref_inst_gen(&begin->base);

    return &inst->base;
}

static Stage1AirInstAwait *ir_build_await_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        Stage1AirInst *frame, ZigType *result_type, Stage1AirInst *result_loc, bool is_nosuspend)
{
    Stage1AirInstAwait *instruction = ir_build_inst_gen<Stage1AirInstAwait>(&ira->new_irb,
            scope, source_node);
    instruction->base.value->type = result_type;
    instruction->frame = frame;
    instruction->result_loc = result_loc;
    instruction->is_nosuspend = is_nosuspend;

    ir_ref_inst_gen(frame);
    if (result_loc != nullptr) ir_ref_inst_gen(result_loc);

    return instruction;
}

static Stage1AirInst *ir_build_resume_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node, Stage1AirInst *frame) {
    Stage1AirInstResume *instruction = ir_build_inst_void<Stage1AirInstResume>(&ira->new_irb,
            scope, source_node);
    instruction->frame = frame;

    ir_ref_inst_gen(frame);

    return &instruction->base;
}

static Stage1AirInst *ir_build_spill_begin_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node, Stage1AirInst *operand,
        SpillId spill_id)
{
    Stage1AirInstSpillBegin *instruction = ir_build_inst_void<Stage1AirInstSpillBegin>(&ira->new_irb,
            scope, source_node);
    instruction->operand = operand;
    instruction->spill_id = spill_id;

    ir_ref_inst_gen(operand);

    return &instruction->base;
}

static Stage1AirInst *ir_build_spill_end_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node, Stage1AirInstSpillBegin *begin,
        ZigType *result_type)
{
    Stage1AirInstSpillEnd *instruction = ir_build_inst_gen<Stage1AirInstSpillEnd>(&ira->new_irb,
            scope, source_node);
    instruction->base.value->type = result_type;
    instruction->begin = begin;

    ir_ref_inst_gen(&begin->base);

    return &instruction->base;
}

static Stage1AirInst *ir_build_vector_extract_elem(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        Stage1AirInst *vector, Stage1AirInst *index)
{
    Stage1AirInstVectorExtractElem *instruction = ir_build_inst_gen<Stage1AirInstVectorExtractElem>(
            &ira->new_irb, scope, source_node);
    instruction->base.value->type = vector->value->type->data.vector.elem_type;
    instruction->vector = vector;
    instruction->index = index;

    ir_ref_inst_gen(vector);
    ir_ref_inst_gen(index);

    return &instruction->base;
}

static Stage1AirInst *ir_build_wasm_memory_size_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node, Stage1AirInst *index) {
    Stage1AirInstWasmMemorySize *instruction = ir_build_inst_gen<Stage1AirInstWasmMemorySize>(&ira->new_irb,
            scope, source_node);
    instruction->base.value->type = ira->codegen->builtin_types.entry_u32;
    instruction->index = index;

    ir_ref_inst_gen(index);

    return &instruction->base;
}

static Stage1AirInst *ir_build_wasm_memory_grow_gen(IrAnalyze *ira, Scope *scope, AstNode *source_node, Stage1AirInst *index, Stage1AirInst *delta) {
    Stage1AirInstWasmMemoryGrow *instruction = ir_build_inst_gen<Stage1AirInstWasmMemoryGrow>(&ira->new_irb,
            scope, source_node);
    instruction->base.value->type = ira->codegen->builtin_types.entry_i32;
    instruction->index = index;
    instruction->delta = delta;

    ir_ref_inst_gen(index);
    ir_ref_inst_gen(delta);

    return &instruction->base;
}

static Error parse_asm_template(IrAnalyze *ira, AstNode *source_node, Buf *asm_template,
        ZigList<AsmToken> *tok_list)
{
    // TODO Connect the errors in this function back up to the actual source location
    // rather than just the token. https://github.com/ziglang/zig/issues/2080
    enum State {
        StateStart,
        StatePercent,
        StateTemplate,
        StateVar,
    };

    assert(tok_list->length == 0);

    AsmToken *cur_tok = nullptr;

    enum State state = StateStart;

    for (size_t i = 0; i < buf_len(asm_template); i += 1) {
        uint8_t c = *((uint8_t*)buf_ptr(asm_template) + i);
        switch (state) {
            case StateStart:
                if (c == '%') {
                    tok_list->add_one();
                    cur_tok = &tok_list->last();
                    cur_tok->id = AsmTokenIdPercent;
                    cur_tok->start = i;
                    state = StatePercent;
                } else {
                    tok_list->add_one();
                    cur_tok = &tok_list->last();
                    cur_tok->id = AsmTokenIdTemplate;
                    cur_tok->start = i;
                    state = StateTemplate;
                }
                break;
            case StatePercent:
                if (c == '%') {
                    cur_tok->end = i;
                    state = StateStart;
                } else if (c == '[') {
                    cur_tok->id = AsmTokenIdVar;
                    state = StateVar;
                } else if (c == '=') {
                    cur_tok->id = AsmTokenIdUniqueId;
                    cur_tok->end = i;
                    state = StateStart;
                } else {
                    add_node_error(ira->codegen, source_node,
                        buf_create_from_str("expected a '%' or '['"));
                    return ErrorSemanticAnalyzeFail;
                }
                break;
            case StateTemplate:
                if (c == '%') {
                    cur_tok->end = i;
                    i -= 1;
                    cur_tok = nullptr;
                    state = StateStart;
                }
                break;
            case StateVar:
                if (c == ']') {
                    cur_tok->end = i;
                    state = StateStart;
                } else if ((c >= 'a' && c <= 'z') ||
                        (c >= '0' && c <= '9') ||
                        (c == '_'))
                {
                    // do nothing
                } else {
                    add_node_error(ira->codegen, source_node,
                        buf_sprintf("invalid substitution character: '%c'", c));
                    return ErrorSemanticAnalyzeFail;
                }
                break;
        }
    }

    switch (state) {
        case StateStart:
            break;
        case StatePercent:
        case StateVar:
            add_node_error(ira->codegen, source_node, buf_sprintf("unexpected end of assembly template"));
            return ErrorSemanticAnalyzeFail;
        case StateTemplate:
            cur_tok->end = buf_len(asm_template);
            break;
    }
    return ErrorNone;
}

// errors should be populated with set1's values
static ZigType *get_error_set_union(CodeGen *g, ErrorTableEntry **errors, ZigType *set1, ZigType *set2,
        Buf *type_name)
{
    assert(set1->id == ZigTypeIdErrorSet);
    assert(set2->id == ZigTypeIdErrorSet);

    ZigType *err_set_type = new_type_table_entry(ZigTypeIdErrorSet);
    err_set_type->size_in_bits = g->builtin_types.entry_global_error_set->size_in_bits;
    err_set_type->abi_align = g->builtin_types.entry_global_error_set->abi_align;
    err_set_type->abi_size = g->builtin_types.entry_global_error_set->abi_size;
    if (type_name == nullptr) {
        buf_resize(&err_set_type->name, 0);
        buf_appendf(&err_set_type->name, "error{");
    } else {
        buf_init_from_buf(&err_set_type->name, type_name);
    }

    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->data.error_set.err_count = count;
    err_set_type->data.error_set.errors = heap::c_allocator.allocate<ErrorTableEntry *>(count);

    bool need_comma = false;
    for (uint32_t i = 0; i < set1->data.error_set.err_count; i += 1) {
        ErrorTableEntry *error_entry = set1->data.error_set.errors[i];
        if (type_name == nullptr) {
            const char *comma = need_comma ? "," : "";
            need_comma = true;
            buf_appendf(&err_set_type->name, "%s%s", comma, 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;
            if (type_name == nullptr) {
                const char *comma = need_comma ? "," : "";
                need_comma = true;
                buf_appendf(&err_set_type->name, "%s%s", comma, buf_ptr(&error_entry->name));
            }
            err_set_type->data.error_set.errors[index] = error_entry;
            index += 1;
        }
    }
    assert(index == count);

    if (type_name == nullptr) {
        buf_appendf(&err_set_type->name, "}");
    }

    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->size_in_bits = g->builtin_types.entry_global_error_set->size_in_bits;
    err_set_type->abi_align = g->builtin_types.entry_global_error_set->abi_align;
    err_set_type->abi_size = g->builtin_types.entry_global_error_set->abi_size;
    err_set_type->data.error_set.err_count = 1;
    err_set_type->data.error_set.errors = heap::c_allocator.create<ErrorTableEntry *>();

    err_set_type->data.error_set.errors[0] = err_entry;

    return err_set_type;
}

static void invalidate_exec_gen(Stage1Air *exec, ErrorMsg *msg) {
    if (exec->first_err_trace_msg != nullptr)
        return;

    exec->first_err_trace_msg = msg;

    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, msg);
}


static ErrorMsg *exec_add_error_node_gen(CodeGen *codegen, Stage1Air *exec, AstNode *source_node, Buf *msg) {
    ErrorMsg *err_msg = add_node_error(codegen, source_node, msg);
    invalidate_exec_gen(exec, err_msg);
    if (exec->parent_exec) {
        ir_add_call_stack_errors_gen(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_gen(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_gen(codegen, ira->new_irb.exec, source_node, msg);
    else
        return add_node_error(codegen, source_node, msg);
}

static ErrorMsg *ir_add_error(IrAnalyze *ira, Stage1AirInst *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,
        ZigValue *ptr_val)
{
    Error err;
    assert(ptr_val->type->id == ZigTypeIdPointer);
    assert(ptr_val->special == ConstValSpecialStatic);
    ZigValue 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;
    ZigValue *child_val = const_ptr_pointee_unchecked(codegen, ptr_val);
    copy_const_val(codegen, child_val, &tmp);
    return ErrorNone;
}

ZigValue *const_ptr_pointee(IrAnalyze *ira, CodeGen *codegen, ZigValue *const_val,
        AstNode *source_node)
{
    Error err;
    ZigValue *val = const_ptr_pointee_unchecked(codegen, const_val);
    if (val == nullptr) return nullptr;
    assert(const_val->type->id == ZigTypeIdPointer);
    ZigType *expected_type = const_val->type->data.pointer.child_type;
    if (expected_type == codegen->builtin_types.entry_anytype) {
        return val;
    }
    switch (type_has_one_possible_value(codegen, expected_type)) {
        case OnePossibleValueInvalid:
            return nullptr;
        case OnePossibleValueNo:
            break;
        case OnePossibleValueYes:
            return get_the_one_possible_value(codegen, expected_type);
    }
    if (!types_have_same_zig_comptime_repr(codegen, expected_type, val->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 Error ir_exec_scan_for_side_effects(CodeGen *codegen, Stage1Air *exec) {
    Stage1AirBasicBlock *bb = exec->basic_block_list.at(0);
    for (size_t i = 0; i < bb->instruction_list.length; i += 1) {
        Stage1AirInst *instruction = bb->instruction_list.at(i);
        if (instruction->id == Stage1AirInstIdReturn) {
            return ErrorNone;
        } else if (ir_inst_gen_has_side_effects(instruction)) {
            if (instr_is_comptime(instruction)) {
                switch (instruction->id) {
                    case Stage1AirInstIdUnwrapErrPayload:
                    case Stage1AirInstIdOptionalUnwrapPtr:
                    case Stage1AirInstIdUnionFieldPtr:
                        continue;
                    default:
                        break;
                }
            }
            if (get_scope_typeof(instruction->scope) != nullptr) {
                // doesn't count, it's inside a @TypeOf()
                continue;
            }
            exec_add_error_node_gen(codegen, exec, instruction->source_node,
                    buf_sprintf("unable to evaluate constant expression"));
            return ErrorSemanticAnalyzeFail;
        }
    }
    zig_unreachable();
}

static bool ir_emit_global_runtime_side_effect(IrAnalyze *ira, Stage1ZirInst* source_instruction) {
    if (ir_should_inline(ira->zir, source_instruction->scope)) {
        ir_add_error_node(ira, source_instruction->source_node, buf_sprintf("unable to evaluate constant expression"));
        return false;
    }
    return true;
}

static bool const_val_fits_in_num_lit(ZigValue *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(ZigValue *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, ZigValue *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, ZigValue *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(ZigValue *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 80:
                zig_panic("TODO: float_init_bigfloat c_longdouble");
            case 128:
                dest_val->data.x_f128 = bigfloat_to_f128(bigfloat);
                break;
            default:
                zig_unreachable();
        }
    } else {
        zig_unreachable();
    }
}

static void float_init_f16(ZigValue *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(ZigValue *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(ZigValue *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(ZigValue *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(ZigValue *dest_val, ZigValue *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 bool float_is_nan(ZigValue *op) {
    if (op->type->id == ZigTypeIdComptimeFloat) {
        return bigfloat_is_nan(&op->data.x_bigfloat);
    } else if (op->type->id == ZigTypeIdFloat) {
        switch (op->type->data.floating.bit_count) {
            case 16:
                return zig_f16_isNaN(op->data.x_f16);
            case 32:
                return op->data.x_f32 != op->data.x_f32;
            case 64:
                return op->data.x_f64 != op->data.x_f64;
            case 128:
                return zig_f128_isNaN(&op->data.x_f128);
            default:
                zig_unreachable();
        }
    } else {
        zig_unreachable();
    }
}

static Cmp float_cmp(ZigValue *op1, ZigValue *op2) {
    if (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();
        }
    }
    BigFloat op1_big;
    BigFloat op2_big;
    value_to_bigfloat(&op1_big, op1);
    value_to_bigfloat(&op2_big, op2);
    return bigfloat_cmp(&op1_big, &op2_big);
}

// This function cannot handle NaN
static Cmp float_cmp_zero(ZigValue *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(ZigValue *out_val, ZigValue *op1, ZigValue *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(ZigValue *out_val, ZigValue *op1, ZigValue *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(ZigValue *out_val, ZigValue *op1, ZigValue *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(ZigValue *out_val, ZigValue *op1, ZigValue *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(ZigValue *out_val, ZigValue *op1, ZigValue *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(ZigValue *out_val, ZigValue *op1, ZigValue *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(ZigValue *out_val, ZigValue *op1, ZigValue *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(ZigValue *out_val, ZigValue *op1, ZigValue *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_max(ZigValue *out_val, ZigValue *op1, ZigValue *op2) {
    assert(op1->type == op2->type);
    out_val->type = op1->type;
    if (op1->type->id == ZigTypeIdComptimeFloat) {
        bigfloat_max(&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:
                if (zig_f16_isNaN(op1->data.x_f16)) {
                    out_val->data.x_f16 = op2->data.x_f16;
                } else if (zig_f16_isNaN(op2->data.x_f16)) {
                    out_val->data.x_f16 = op1->data.x_f16;
                } else {
                    out_val->data.x_f16 = f16_lt(op1->data.x_f16, op2->data.x_f16) ? op2->data.x_f16 : op1->data.x_f16;
                }
                return;
            case 32:
                if (op1->data.x_f32 != op1->data.x_f32) {
                    out_val->data.x_f32 = op2->data.x_f32;
                } else if (op2->data.x_f32 != op2->data.x_f32) {
                    out_val->data.x_f32 = op1->data.x_f32;
                } else {
                    out_val->data.x_f32 = op1->data.x_f32 > op2->data.x_f32 ? op1->data.x_f32 : op2->data.x_f32;
                }
                return;
            case 64:
                if (op1->data.x_f64 != op1->data.x_f64) {
                    out_val->data.x_f64 = op2->data.x_f64;
                } else if (op2->data.x_f64 != op2->data.x_f64) {
                    out_val->data.x_f64 = op1->data.x_f64;
                } else {
                    out_val->data.x_f64 = op1->data.x_f64 > op2->data.x_f64 ? op1->data.x_f64 : op2->data.x_f64;
                }
                return;
            case 128:
                if (zig_f128_isNaN(&op1->data.x_f128)) {
                    out_val->data.x_f128 = op2->data.x_f128;
                } else if (zig_f128_isNaN(&op2->data.x_f128)) {
                    out_val->data.x_f128 = op1->data.x_f128;
                } else {
                    out_val->data.x_f128 = f128M_lt(&op1->data.x_f128, &op2->data.x_f128) ? op2->data.x_f128 : op1->data.x_f128;
                }
                return;
            default:
                zig_unreachable();
        }
    } else {
        zig_unreachable();
    }
}

static void float_min(ZigValue *out_val, ZigValue *op1, ZigValue *op2) {
    assert(op1->type == op2->type);
    out_val->type = op1->type;
    if (op1->type->id == ZigTypeIdComptimeFloat) {
        bigfloat_min(&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:
                if (zig_f16_isNaN(op1->data.x_f16)) {
                    out_val->data.x_f16 = op2->data.x_f16;
                } else if (zig_f16_isNaN(op2->data.x_f16)) {
                    out_val->data.x_f16 = op1->data.x_f16;
                } else {
                    out_val->data.x_f16 = f16_lt(op1->data.x_f16, op2->data.x_f16) ? op1->data.x_f16 : op2->data.x_f16;
                }
                return;
            case 32:
                if (op1->data.x_f32 != op1->data.x_f32) {
                    out_val->data.x_f32 = op2->data.x_f32;
                } else if (op2->data.x_f32 != op2->data.x_f32) {
                    out_val->data.x_f32 = op1->data.x_f32;
                } else {
                    out_val->data.x_f32 = op1->data.x_f32 < op2->data.x_f32 ? op1->data.x_f32 : op2->data.x_f32;
                }
                return;
            case 64:
                if (op1->data.x_f64 != op1->data.x_f64) {
                    out_val->data.x_f64 = op2->data.x_f64;
                } else if (op2->data.x_f64 != op2->data.x_f64) {
                    out_val->data.x_f64 = op1->data.x_f64;
                } else {
                    out_val->data.x_f64 = op1->data.x_f32 < op2->data.x_f64 ? op1->data.x_f64 : op2->data.x_f64;
                }
                return;
            case 128:
                if (zig_f128_isNaN(&op1->data.x_f128)) {
                    out_val->data.x_f128 = op2->data.x_f128;
                } else if (zig_f128_isNaN(&op2->data.x_f128)) {
                    out_val->data.x_f128 = op1->data.x_f128;
                } else {
                    out_val->data.x_f128 = f128M_lt(&op1->data.x_f128, &op2->data.x_f128) ? op1->data.x_f128 : op2->data.x_f128;
                }
                return;
            default:
                zig_unreachable();
        }
    } else {
        zig_unreachable();
    }
}

static void float_negate(ZigValue *out_val, ZigValue *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:
                out_val->data.x_f16 = f16_neg(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:
                f128M_neg(&op->data.x_f128, &out_val->data.x_f128);
                return;
            default:
                zig_unreachable();
        }
    } else {
        zig_unreachable();
    }
}

void float_write_ieee597(ZigValue *op, uint8_t *buf, bool target_is_big_endian) {
    if (op->type->id != ZigTypeIdFloat)
        zig_unreachable();

    const unsigned n = op->type->data.floating.bit_count / 8;
    assert(n <= 16);

    switch (op->type->data.floating.bit_count) {
        case 16:
            memcpy(buf, &op->data.x_f16, 2);
            break;
        case 32:
            memcpy(buf, &op->data.x_f32, 4);
            break;
        case 64:
            memcpy(buf, &op->data.x_f64, 8);
            break;
        case 128:
            memcpy(buf, &op->data.x_f128, 16);
            break;
        default:
            zig_unreachable();
    }

    // Byteswap if system endianness != target endianness
    if (native_is_big_endian != target_is_big_endian) {
        for (size_t i = 0; i < n / 2; i++) {
            uint8_t u = buf[i];
            buf[i] = buf[n - 1 - i];
            buf[n - 1 - i] = u;
        }
    }
}

void float_read_ieee597(ZigValue *val, uint8_t *buf, bool target_is_big_endian) {
    if (val->type->id != ZigTypeIdFloat)
        zig_unreachable();

    const unsigned n = val->type->data.floating.bit_count / 8;
    assert(n <= 16);

    uint8_t tmp[16];
    uint8_t *ptr = buf;

    // Byteswap if system endianness != target endianness
    if (native_is_big_endian != target_is_big_endian) {
        memcpy(tmp, buf, n);
        for (size_t i = 0; i < n / 2; i++) {
            uint8_t u = tmp[i];
            tmp[i] = tmp[n - 1 - i];
            tmp[n - 1 - i] = u;
        }

        ptr = tmp;
    }

    switch (val->type->data.floating.bit_count) {
        case 16:
            memcpy(&val->data.x_f16, ptr, 2);
            return;
        case 32:
            memcpy(&val->data.x_f32, ptr, 4);
            return;
        case 64:
            memcpy(&val->data.x_f64, ptr, 8);
            return;
        case 128:
            memcpy(&val->data.x_f128, ptr, 16);
            return;
        default:
            zig_unreachable();
    }
}

static void value_to_bigfloat(BigFloat *out, ZigValue *val) {
    switch (val->type->id) {
        case ZigTypeIdInt:
        case ZigTypeIdComptimeInt:
            bigfloat_init_bigint(out, &val->data.x_bigint);
            return;
        case ZigTypeIdComptimeFloat:
            *out = val->data.x_bigfloat;
            return;
        case ZigTypeIdFloat: switch (val->type->data.floating.bit_count) {
            case 16:
                bigfloat_init_16(out, val->data.x_f16);
                return;
            case 32:
                bigfloat_init_32(out, val->data.x_f32);
                return;
            case 64:
                bigfloat_init_64(out, val->data.x_f64);
                return;
            case 80:
                zig_panic("TODO: value_to_bigfloat c_longdouble");
            case 128:
                bigfloat_init_128(out, val->data.x_f128);
                return;
            default:
                zig_unreachable();
        }
        default:
            zig_unreachable();
    }
}

static bool ir_num_lit_fits_in_other_type(IrAnalyze *ira, Stage1AirInst *instruction, ZigType *other_type,
        bool explicit_cast)
{
    if (type_is_invalid(other_type)) {
        return false;
    }

    ZigValue *const_val = ir_resolve_const(ira, instruction, LazyOkNoUndef);
    if (const_val == nullptr)
        return false;

    if (const_val->special == ConstValSpecialLazy) {
        switch (const_val->data.x_lazy->id) {
            case LazyValueIdAlignOf: {
                // This is guaranteed to fit into a u29
                if (other_type->id == ZigTypeIdComptimeInt)
                    return true;
                size_t align_bits = get_align_amt_type(ira->codegen)->data.integral.bit_count;
                if (other_type->id == ZigTypeIdInt && !other_type->data.integral.is_signed &&
                    other_type->data.integral.bit_count >= align_bits)
                {
                    return true;
                }
                break;
            }
            case LazyValueIdSizeOf: {
                // This is guaranteed to fit into a usize
                if (other_type->id == ZigTypeIdComptimeInt)
                    return true;
                size_t usize_bits = ira->codegen->builtin_types.entry_usize->data.integral.bit_count;
                if (other_type->id == ZigTypeIdInt && !other_type->data.integral.is_signed &&
                    other_type->data.integral.bit_count >= usize_bits)
                {
                    return true;
                }
                break;
            }
            default:
                break;
        }
    }

    const_val = ir_resolve_const(ira, instruction, UndefBad);
    if (const_val == nullptr)
        return false;

    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 (const_val_is_int && other_type->id == ZigTypeIdComptimeFloat) {
        return true;
    }
    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: ir_num_lit_fits_in_other_type c_longdouble");
                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_node(ira, instruction->source_node,
                buf_sprintf("type %s cannot represent integer value %s",
                    buf_ptr(&other_type->name),
                    buf_ptr(val_buf)));
            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: ir_num_lit_fits_in_other_type c_longdouble");
                    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: ir_num_lit_fits_in_other_type c_longdouble");
                    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: ir_num_lit_fits_in_other_type c_longdouble");
                    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: ir_num_lit_fits_in_other_type c_longdouble");
            case 128:
                return true;
            default:
                zig_unreachable();
        }
        Buf *val_buf = buf_alloc();
        float_append_buf(val_buf, const_val);
        ir_add_error_node(ira, instruction->source_node,
            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_node(ira, instruction->source_node,
                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_node(ira, instruction->source_node,
                    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_node(ira, instruction->source_node,
                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_node(ira, instruction->source_node,
        buf_sprintf("%s value %s cannot be coerced to type '%s'",
            num_lit_str,
            buf_ptr(val_buf),
            buf_ptr(&other_type->name)));
    return false;
}

static bool is_tagged_union(ZigType *type) {
    if (type->id != ZigTypeIdUnion)
        return false;
    return (type->data.unionation.decl_node->data.container_decl.auto_enum ||
        type->data.unionation.decl_node->data.container_decl.init_arg_expr != nullptr);
}

static void populate_error_set_table(ErrorTableEntry **errors, ZigType *set) {
    assert(set->id == ZigTypeIdErrorSet);
    for (uint32_t i = 0; i < set->data.error_set.err_count; i += 1) {
        ErrorTableEntry *error_entry = set->data.error_set.errors[i];
        assert(errors[error_entry->value] == nullptr);
        errors[error_entry->value] = error_entry;
    }
}

static ErrorTableEntry *better_documented_error(ErrorTableEntry *preferred, ErrorTableEntry *other) {
    if (preferred->decl_node->type == NodeTypeErrorSetField)
        return preferred;
    if (other->decl_node->type == NodeTypeErrorSetField)
        return other;
    return preferred;
}

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;
    }
    size_t errors_count = ira->codegen->errors_by_index.length;
    ErrorTableEntry **errors = heap::c_allocator.allocate<ErrorTableEntry *>(errors_count);
    populate_error_set_table(errors, set1);
    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{");

    bool need_comma = false;
    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) {
            // prefer the one with docs
            const char *comma = need_comma ? "," : "";
            need_comma = true;
            ErrorTableEntry *existing_entry_with_docs = better_documented_error(existing_entry, error_entry);
            intersection_list.append(existing_entry_with_docs);
            buf_appendf(&err_set_type->name, "%s%s", comma, buf_ptr(&existing_entry_with_docs->name));
        }
    }
    heap::c_allocator.deallocate(errors, errors_count);

    err_set_type->data.error_set.err_count = intersection_list.length;
    err_set_type->data.error_set.errors = intersection_list.items;
    err_set_type->size_in_bits = ira->codegen->builtin_types.entry_global_error_set->size_in_bits;
    err_set_type->abi_align = ira->codegen->builtin_types.entry_global_error_set->abi_align;
    err_set_type->abi_size = ira->codegen->builtin_types.entry_global_error_set->abi_size;

    buf_appendf(&err_set_type->name, "}");

    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
    // sentinel-terminated pointers can coerce into PtrLenUnknown
    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_ptr_type->id == ZigTypeIdPointer;
    bool actual_opt_or_ptr = actual_ptr_type != nullptr && actual_ptr_type->id == ZigTypeIdPointer;
    if (wanted_opt_or_ptr && actual_opt_or_ptr) {
        bool ok_null_term_ptrs =
            wanted_ptr_type->data.pointer.sentinel == nullptr ||
            (actual_ptr_type->data.pointer.sentinel != nullptr &&
             const_values_equal(ira->codegen, wanted_ptr_type->data.pointer.sentinel,
                 actual_ptr_type->data.pointer.sentinel)) ||
            actual_ptr_type->data.pointer.ptr_len == PtrLenC;
        if (!ok_null_term_ptrs) {
            result.id = ConstCastResultIdPtrSentinel;
            result.data.bad_ptr_sentinel = heap::c_allocator.allocate_nonzero<ConstCastPtrSentinel>(1);
            result.data.bad_ptr_sentinel->wanted_type = wanted_ptr_type;
            result.data.bad_ptr_sentinel->actual_type = actual_ptr_type;
            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)) {
            result.id = ConstCastResultIdPtrLens;
            return result;
        }

        bool ok_cv_qualifiers =
            (!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);
        if (!ok_cv_qualifiers) {
            result.id = ConstCastResultIdCV;
            result.data.bad_cv = heap::c_allocator.allocate_nonzero<ConstCastBadCV>(1);
            result.data.bad_cv->wanted_type = wanted_ptr_type;
            result.data.bad_cv->actual_type = actual_ptr_type;
            return result;
        }

        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 = heap::c_allocator.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 = heap::c_allocator.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;
        }
        if ((err = type_resolve(g, wanted_type, ResolveStatusZeroBitsKnown))) {
            result.id = ConstCastResultIdInvalid;
            return result;
        }
        if ((err = type_resolve(g, actual_type, ResolveStatusZeroBitsKnown))) {
            result.id = ConstCastResultIdInvalid;
            return result;
        }
        if (type_has_bits(g, wanted_type) == type_has_bits(g, actual_type) &&
            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;
        }
    }

    // arrays
    if (wanted_type->id == ZigTypeIdArray && actual_type->id == ZigTypeIdArray &&
        wanted_type->data.array.len == actual_type->data.array.len)
    {
        ConstCastOnly child = types_match_const_cast_only(ira, wanted_type->data.array.child_type,
                actual_type->data.array.child_type, source_node, wanted_is_mutable);
        if (child.id == ConstCastResultIdInvalid)
            return child;
        if (child.id != ConstCastResultIdOk) {
            result.id = ConstCastResultIdArrayChild;
            result.data.array_mismatch = heap::c_allocator.allocate_nonzero<ConstCastArrayMismatch>(1);
            result.data.array_mismatch->child = child;
            result.data.array_mismatch->wanted_child = wanted_type->data.array.child_type;
            result.data.array_mismatch->actual_child = actual_type->data.array.child_type;
            return result;
        }
        bool ok_null_terminated = (wanted_type->data.array.sentinel == nullptr) ||
            (actual_type->data.array.sentinel != nullptr &&
            const_values_equal(ira->codegen, wanted_type->data.array.sentinel, actual_type->data.array.sentinel));
        if (!ok_null_terminated) {
            result.id = ConstCastResultIdSentinelArrays;
            result.data.sentinel_arrays = heap::c_allocator.allocate_nonzero<ConstCastBadNullTermArrays>(1);
            result.data.sentinel_arrays->child = child;
            result.data.sentinel_arrays->wanted_type = wanted_type;
            result.data.sentinel_arrays->actual_type = actual_type;
            return result;
        }
        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;
        }
        bool ok_sentinels =
            wanted_ptr_type->data.pointer.sentinel == nullptr ||
            (actual_ptr_type->data.pointer.sentinel != nullptr &&
             const_values_equal(ira->codegen, wanted_ptr_type->data.pointer.sentinel,
                 actual_ptr_type->data.pointer.sentinel));
        if (!ok_sentinels) {
            result.id = ConstCastResultIdPtrSentinel;
            result.data.bad_ptr_sentinel = heap::c_allocator.allocate_nonzero<ConstCastPtrSentinel>(1);
            result.data.bad_ptr_sentinel->wanted_type = wanted_ptr_type;
            result.data.bad_ptr_sentinel->actual_type = actual_ptr_type;
            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 = heap::c_allocator.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;
        }
    }

    // optional types
    if (wanted_type->id == ZigTypeIdOptional && actual_type->id == ZigTypeIdOptional) {
        // Consider the case where the wanted type is ??[*]T and the actual one
        // is ?[*]T, we cannot turn the former into the latter even though the
        // child types are compatible (?[*]T and [*]T are both represented as a
        // pointer). The extra level of indirection in ??[*]T means it's
        // represented as a regular, fat, optional type and, as a consequence,
        // has a different shape than the one of ?[*]T.
        if ((wanted_ptr_type != nullptr) != (actual_ptr_type != nullptr)) {
            // The use of type_mismatch is intentional
            result.id = ConstCastResultIdOptionalShape;
            result.data.type_mismatch = heap::c_allocator.allocate_nonzero<ConstCastTypeMismatch>(1);
            result.data.type_mismatch->wanted_type = wanted_type;
            result.data.type_mismatch->actual_type = actual_type;
            return result;
        }
        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 = heap::c_allocator.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 = heap::c_allocator.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 = heap::c_allocator.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 && container_set->data.error_set.incomplete) {
            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;
        }

        size_t errors_count = g->errors_by_index.length;
        ErrorTableEntry **errors = heap::c_allocator.allocate<ErrorTableEntry *>(errors_count);
        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 = heap::c_allocator.create<ConstCastErrSetMismatch>();
                }
                result.data.error_set_mismatch->missing_errors.append(contained_error_entry);
            }
        }
        heap::c_allocator.deallocate(errors, errors_count);
        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.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 = heap::c_allocator.allocate_nonzero<ConstCastOnly>(1);
                *result.data.return_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.param_count; 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 = heap::c_allocator.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;
            }
        }
        if (wanted_type->data.fn.fn_type_id.cc != actual_type->data.fn.fn_type_id.cc) {
            // ConstCastResultIdFnCC is guaranteed to be the last one reported, meaning everything else is ok.
            result.id = ConstCastResultIdFnCC;
            return result;
        }
        return result;
    }

    if (wanted_type->id == ZigTypeIdInt && actual_type->id == ZigTypeIdInt) {
        if (wanted_type->data.integral.is_signed != actual_type->data.integral.is_signed ||
            wanted_type->data.integral.bit_count != actual_type->data.integral.bit_count)
        {
            result.id = ConstCastResultIdIntShorten;
            result.data.int_shorten = heap::c_allocator.allocate_nonzero<ConstCastIntShorten>(1);
            result.data.int_shorten->wanted_type = wanted_type;
            result.data.int_shorten->actual_type = actual_type;
            return result;
        }
        return result;
    }

    if (wanted_type->id == ZigTypeIdVector && actual_type->id == ZigTypeIdVector) {
        if (actual_type->data.vector.len != wanted_type->data.vector.len) {
            result.id = ConstCastResultIdVectorLength;
            return result;
        }

        ConstCastOnly child = types_match_const_cast_only(ira, wanted_type->data.vector.elem_type,
                actual_type->data.vector.elem_type, source_node, false);
        if (child.id == ConstCastResultIdInvalid)
            return child;
        if (child.id != ConstCastResultIdOk) {
            result.id = ConstCastResultIdVectorChild;
            return result;
        }

        return result;
    }

    result.id = ConstCastResultIdType;
    result.data.type_mismatch = heap::c_allocator.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 = heap::c_allocator.reallocate(*errors, old_errors_count, *errors_count);
}

static ZigType *ir_resolve_peer_types(IrAnalyze *ira, AstNode *source_node, ZigType *expected_type,
        Stage1AirInst **instructions, size_t instruction_count)
{
    Error err;
    assert(instruction_count >= 1);
    Stage1AirInst *prev_inst;
    size_t i = 0;
    for (;;) {
        prev_inst = instructions[i];
        if (type_is_invalid(prev_inst->value->type)) {
            return ira->codegen->builtin_types.entry_invalid;
        }
        if (prev_inst->value->type->id == ZigTypeIdUnreachable) {
            i += 1;
            if (i == instruction_count) {
                return prev_inst->value->type;
            }
            continue;
        }
        break;
    }
    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;
    bool make_the_slice_const = false;
    bool make_the_pointer_const = false;
    for (; i < instruction_count; i += 1) {
        Stage1AirInst *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 == cur_type) {
            continue;
        }

        if (prev_type->id == ZigTypeIdUnreachable) {
            prev_inst = cur_inst;
            continue;
        }

        if (cur_type->id == ZigTypeIdUnreachable) {
            continue;
        }

        if (prev_type->id == ZigTypeIdErrorSet) {
            ir_assert(err_set_type != nullptr, prev_inst);
            if (cur_type->id == ZigTypeIdErrorSet) {
                if (type_is_global_error_set(err_set_type)) {
                    continue;
                }
                bool allow_infer = cur_type->data.error_set.infer_fn != nullptr &&
                    cur_type->data.error_set.infer_fn == ira->fn;
                if (!allow_infer && !resolve_inferred_error_set(ira->codegen, cur_type, cur_inst->source_node)) {
                    return ira->codegen->builtin_types.entry_invalid;
                }
                if (!allow_infer && 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, nullptr);
                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;
                bool allow_infer = cur_err_set_type->data.error_set.infer_fn != nullptr &&
                    cur_err_set_type->data.error_set.infer_fn == ira->fn;
                if (!allow_infer && !resolve_inferred_error_set(ira->codegen, cur_err_set_type, cur_inst->source_node)) {
                    return ira->codegen->builtin_types.entry_invalid;
                }
                if (!allow_infer && 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, nullptr);
                prev_inst = cur_inst;
                assert(errors != nullptr);
                continue;
            } else {
                prev_inst = cur_inst;
                continue;
            }
        }

        if (cur_type->id == ZigTypeIdErrorSet) {
            bool allow_infer = cur_type->data.error_set.infer_fn != nullptr &&
                    cur_type->data.error_set.infer_fn == ira->fn;
            if (!allow_infer && !resolve_inferred_error_set(ira->codegen, cur_type, cur_inst->source_node)) {
                return ira->codegen->builtin_types.entry_invalid;
            }
            if (!allow_infer && 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) {
                bool allow_infer = false;
                if (prev_type->id == ZigTypeIdErrorUnion) {
                    err_set_type = prev_type->data.error_union.err_set_type;
                    allow_infer = err_set_type->data.error_set.infer_fn != nullptr &&
                        err_set_type->data.error_set.infer_fn == ira->fn;
                } else {
                    err_set_type = cur_type;
                }

                if (!allow_infer && !resolve_inferred_error_set(ira->codegen, err_set_type, cur_inst->source_node)) {
                    return ira->codegen->builtin_types.entry_invalid;
                }

                if (!allow_infer && type_is_global_error_set(err_set_type)) {
                    err_set_type = ira->codegen->builtin_types.entry_global_error_set;
                    continue;
                }

                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;
                }
                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, nullptr);
            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 (prev_err_set_type == cur_err_set_type)
                    continue;

                bool allow_infer_prev = prev_err_set_type->data.error_set.infer_fn != nullptr &&
                        prev_err_set_type->data.error_set.infer_fn == ira->fn;
                bool allow_infer_cur = cur_err_set_type->data.error_set.infer_fn != nullptr &&
                        cur_err_set_type->data.error_set.infer_fn == ira->fn;

                if (!allow_infer_prev && !resolve_inferred_error_set(ira->codegen, prev_err_set_type, cur_inst->source_node)) {
                    return ira->codegen->builtin_types.entry_invalid;
                }

                if (!allow_infer_cur && !resolve_inferred_error_set(ira->codegen, cur_err_set_type, cur_inst->source_node)) {
                    return ira->codegen->builtin_types.entry_invalid;
                }

                if ((!allow_infer_prev && type_is_global_error_set(prev_err_set_type)) ||
                    (!allow_infer_cur && 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, nullptr);
                continue;
            }
        }

        if (prev_type->id == ZigTypeIdNull) {
            prev_inst = cur_inst;
            any_are_null = true;
            continue;
        }

        if (cur_type->id == ZigTypeIdNull) {
            any_are_null = true;
            continue;
        }

        if (prev_type->id == ZigTypeIdEnum && cur_type->id == ZigTypeIdEnumLiteral) {
            TypeEnumField *field = find_enum_type_field(prev_type, cur_inst->value->data.x_enum_literal);
            if (field != nullptr) {
                continue;
            }
        }
        if (is_tagged_union(prev_type) && cur_type->id == ZigTypeIdEnumLiteral) {
            TypeUnionField *field = find_union_type_field(prev_type, cur_inst->value->data.x_enum_literal);
            if (field != nullptr) {
                continue;
            }
        }

        if (cur_type->id == ZigTypeIdEnum && prev_type->id == ZigTypeIdEnumLiteral) {
            TypeEnumField *field = find_enum_type_field(cur_type, prev_inst->value->data.x_enum_literal);
            if (field != nullptr) {
                prev_inst = cur_inst;
                continue;
            }
        }

        if (is_tagged_union(cur_type) && prev_type->id == ZigTypeIdEnumLiteral) {
            TypeUnionField *field = find_union_type_field(cur_type, prev_inst->value->data.x_enum_literal);
            if (field != nullptr) {
                prev_inst = cur_inst;
                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)
        {
            if ((prev_type->data.integral.is_signed == cur_type->data.integral.is_signed) ||
                (cur_type->data.integral.is_signed && !prev_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;
                bool allow_infer = cur_err_set_type->data.error_set.infer_fn != nullptr &&
                    cur_err_set_type->data.error_set.infer_fn == ira->fn;
                if (!allow_infer && !resolve_inferred_error_set(ira->codegen, cur_err_set_type, cur_inst->source_node)) {
                    return ira->codegen->builtin_types.entry_invalid;
                }
                if ((!allow_infer && 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, nullptr);
            }
            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 (prev_type->id == ZigTypeIdOptional &&
            types_match_const_cast_only(ira, cur_type, prev_type->data.maybe.child_type,
                source_node, false).id == ConstCastResultIdOk)
        {
            prev_inst = cur_inst;
            any_are_null = true;
            continue;
        }

        if (cur_type->id == ZigTypeIdOptional &&
            types_match_const_cast_only(ira, prev_type, cur_type->data.maybe.child_type,
                source_node, false).id == ConstCastResultIdOk)
        {
            any_are_null = true;
            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;
            }
        }

        // *[N]T to [*]T
        if (prev_type->id == ZigTypeIdPointer &&
            prev_type->data.pointer.ptr_len == PtrLenSingle &&
            prev_type->data.pointer.child_type->id == ZigTypeIdArray &&
            ((cur_type->id == ZigTypeIdPointer && cur_type->data.pointer.ptr_len == PtrLenUnknown)))
        {
            convert_to_const_slice = false;
            prev_inst = cur_inst;

            if (prev_type->data.pointer.is_const && !cur_type->data.pointer.is_const) {
                // const array pointer and non-const unknown pointer
                make_the_pointer_const = true;
            }
            continue;
        }

        // *[N]T to [*]T
        if (cur_type->id == ZigTypeIdPointer &&
            cur_type->data.pointer.ptr_len == PtrLenSingle &&
            cur_type->data.pointer.child_type->id == ZigTypeIdArray &&
            ((prev_type->id == ZigTypeIdPointer && prev_type->data.pointer.ptr_len == PtrLenUnknown)))
        {
            if (cur_type->data.pointer.is_const && !prev_type->data.pointer.is_const) {
                // const array pointer and non-const unknown pointer
                make_the_pointer_const = true;
            }
            continue;
        }

        // *[N]T to []T
        // *[N]T to E![]T
        if (cur_type->id == ZigTypeIdPointer &&
            cur_type->data.pointer.ptr_len == PtrLenSingle &&
            cur_type->data.pointer.child_type->id == ZigTypeIdArray &&
            ((prev_type->id == ZigTypeIdErrorUnion && is_slice(prev_type->data.error_union.payload_type)) ||
                is_slice(prev_type)))
        {
            ZigType *array_type = cur_type->data.pointer.child_type;
            ZigType *slice_type = (prev_type->id == ZigTypeIdErrorUnion) ?
                prev_type->data.error_union.payload_type : prev_type;
            ZigType *slice_ptr_type = slice_type->data.structure.fields[slice_ptr_index]->type_entry;
            if (types_match_const_cast_only(ira, slice_ptr_type->data.pointer.child_type,
                    array_type->data.array.child_type, source_node, false).id == ConstCastResultIdOk)
            {
                bool const_ok = (slice_ptr_type->data.pointer.is_const || array_type->data.array.len == 0 ||
                        !cur_type->data.pointer.is_const);
                if (!const_ok) make_the_slice_const = true;
                convert_to_const_slice = false;
                continue;
            }
        }

        // *[N]T to []T
        // *[N]T to E![]T
        if (prev_type->id == ZigTypeIdPointer &&
            prev_type->data.pointer.child_type->id == ZigTypeIdArray &&
            prev_type->data.pointer.ptr_len == PtrLenSingle &&
            ((cur_type->id == ZigTypeIdErrorUnion && is_slice(cur_type->data.error_union.payload_type)) ||
             (cur_type->id == ZigTypeIdOptional && is_slice(cur_type->data.maybe.child_type)) ||
             is_slice(cur_type)))
        {
            ZigType *array_type = prev_type->data.pointer.child_type;
            ZigType *slice_type;
            switch (cur_type->id) {
                case ZigTypeIdErrorUnion:
                    slice_type = cur_type->data.error_union.payload_type;
                    break;
                case ZigTypeIdOptional:
                    slice_type = cur_type->data.maybe.child_type;
                    break;
                default:
                    slice_type = cur_type;
                    break;
            }
            ZigType *slice_ptr_type = slice_type->data.structure.fields[slice_ptr_index]->type_entry;
            if (types_match_const_cast_only(ira, slice_ptr_type->data.pointer.child_type,
                    array_type->data.array.child_type, source_node, false).id == ConstCastResultIdOk)
            {
                bool const_ok = (slice_ptr_type->data.pointer.is_const || array_type->data.array.len == 0 ||
                        !prev_type->data.pointer.is_const);
                if (!const_ok) make_the_slice_const = true;
                prev_inst = cur_inst;
                convert_to_const_slice = false;
                continue;
            }
        }

        // *[N]T and *[M]T
        if (cur_type->id == ZigTypeIdPointer && cur_type->data.pointer.ptr_len == PtrLenSingle &&
                cur_type->data.pointer.child_type->id == ZigTypeIdArray &&
            prev_type->id == ZigTypeIdPointer && prev_type->data.pointer.ptr_len == PtrLenSingle &&
                prev_type->data.pointer.child_type->id == ZigTypeIdArray &&
            (
                prev_type->data.pointer.child_type->data.array.sentinel == nullptr ||
                (cur_type->data.pointer.child_type->data.array.sentinel != nullptr &&
                const_values_equal(ira->codegen, prev_type->data.pointer.child_type->data.array.sentinel,
                    cur_type->data.pointer.child_type->data.array.sentinel))
            ) &&
            types_match_const_cast_only(ira,
                cur_type->data.pointer.child_type->data.array.child_type,
                prev_type->data.pointer.child_type->data.array.child_type,
                source_node, !cur_type->data.pointer.is_const).id == ConstCastResultIdOk)
        {
            bool const_ok = (cur_type->data.pointer.is_const || !prev_type->data.pointer.is_const ||
                prev_type->data.pointer.child_type->data.array.len == 0);
            if (!const_ok) make_the_slice_const = true;
            prev_inst = cur_inst;
            convert_to_const_slice = true;
            continue;
        }
        if (prev_type->id == ZigTypeIdPointer && prev_type->data.pointer.ptr_len == PtrLenSingle &&
                prev_type->data.pointer.child_type->id == ZigTypeIdArray &&
            cur_type->id == ZigTypeIdPointer && cur_type->data.pointer.ptr_len == PtrLenSingle &&
                cur_type->data.pointer.child_type->id == ZigTypeIdArray &&
            (
                cur_type->data.pointer.child_type->data.array.sentinel == nullptr ||
                (prev_type->data.pointer.child_type->data.array.sentinel != nullptr &&
                const_values_equal(ira->codegen, cur_type->data.pointer.child_type->data.array.sentinel,
                    prev_type->data.pointer.child_type->data.array.sentinel))
            ) &&
            types_match_const_cast_only(ira,
                prev_type->data.pointer.child_type->data.array.child_type,
                cur_type->data.pointer.child_type->data.array.child_type,
                source_node, !prev_type->data.pointer.is_const).id == ConstCastResultIdOk)
        {
            bool const_ok = (prev_type->data.pointer.is_const || !cur_type->data.pointer.is_const ||
                cur_type->data.pointer.child_type->data.array.len == 0);
            if (!const_ok) make_the_slice_const = true;
            convert_to_const_slice = true;
            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;
    }

    heap::c_allocator.deallocate(errors, errors_count);

    if (convert_to_const_slice) {
        if (prev_inst->value->type->id == ZigTypeIdPointer) {
            ZigType *array_type = prev_inst->value->type->data.pointer.child_type;
            src_assert(array_type->id == ZigTypeIdArray, source_node);
            ZigType *ptr_type = get_pointer_to_type_extra2(
                    ira->codegen, array_type->data.array.child_type,
                    prev_inst->value->type->data.pointer.is_const || make_the_slice_const, false,
                    PtrLenUnknown,
                    0, 0, 0, false,
                    VECTOR_INDEX_NONE, nullptr, array_type->data.array.sentinel);
            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 {
            zig_unreachable();
        }
    } 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 == ZigTypeIdOptional) {
            return prev_inst->value->type;
        } else {
            if ((err = type_resolve(ira->codegen, prev_inst->value->type, ResolveStatusSizeKnown)))
                return ira->codegen->builtin_types.entry_invalid;
            return get_optional_type(ira->codegen, prev_inst->value->type);
        }
    } else if (make_the_slice_const) {
        ZigType *slice_type;
        if (prev_inst->value->type->id == ZigTypeIdErrorUnion) {
            slice_type = prev_inst->value->type->data.error_union.payload_type;
        } else if (is_slice(prev_inst->value->type)) {
            slice_type = prev_inst->value->type;
        } else {
            zig_unreachable();
        }
        ZigType *slice_ptr_type = slice_type->data.structure.fields[slice_ptr_index]->type_entry;
        ZigType *adjusted_ptr_type = adjust_ptr_const(ira->codegen, slice_ptr_type, make_the_slice_const);
        ZigType *adjusted_slice_type = get_slice_type(ira->codegen, adjusted_ptr_type);
        if (prev_inst->value->type->id == ZigTypeIdErrorUnion) {
            return get_error_union_type(ira->codegen, prev_inst->value->type->data.error_union.err_set_type,
                    adjusted_slice_type);
        } else if (is_slice(prev_inst->value->type)) {
            return adjusted_slice_type;
        } else {
            zig_unreachable();
        }
    } else if (make_the_pointer_const) {
        return adjust_ptr_const(ira->codegen, prev_inst->value->type, make_the_pointer_const);
    } else {
        return prev_inst->value->type;
    }
}

static bool eval_const_expr_implicit_cast(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        CastOp cast_op,
        ZigValue *other_val, ZigType *other_type,
        ZigValue *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:
            zig_panic("TODO: eval_const_expr_implicit_cast CastOpErrSet, CastOpBitCast");
        case CastOpNoop: {
            copy_const_val(ira->codegen, const_val, other_val);
            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 80:
                        zig_panic("TODO: eval_const_expr_implicit_cast c_longdouble");
                    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:
            if (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 80:
                        zig_panic("TODO: eval_const_expr_implicit_cast c_longdouble");
                    case 128:
                        const_val->data.x_f128 = bigfloat_to_f128(&bigfloat);
                        break;
                    default:
                        zig_unreachable();
                }
            } else if (new_type->id == ZigTypeIdComptimeFloat) {
                bigfloat_init_bigint(&const_val->data.x_bigfloat, &other_val->data.x_bigint);
            } else {
                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_node(ira, source_node,
                        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 Stage1AirInst *ir_const(IrAnalyze *ira, Scope *scope, AstNode *source_node, ZigType *ty) {
    Stage1AirInstConst *const_instruction = ir_create_inst_gen<Stage1AirInstConst>(&ira->new_irb,
            scope, source_node);
    Stage1AirInst *new_instruction = &const_instruction->base;
    new_instruction->value->type = ty;
    new_instruction->value->special = ConstValSpecialStatic;
    ira->new_irb.constants.append(&heap::c_allocator, const_instruction);
    return new_instruction;
}

static Stage1AirInst *ir_const_noval(IrAnalyze *ira, Scope *scope, AstNode *source_node) {
    Stage1AirInstConst *const_instruction = ir_create_inst_noval<Stage1AirInstConst>(&ira->new_irb,
            scope, source_node);
    ira->new_irb.constants.append(&heap::c_allocator, const_instruction);
    return &const_instruction->base;
}

// This function initializes the new Stage1AirInst with the provided ZigValue,
// rather than creating a new one.
static Stage1AirInst *ir_const_move(IrAnalyze *ira, Scope *scope, AstNode *source_node, ZigValue *val) {
    Stage1AirInst *result = ir_const_noval(ira, scope, source_node);
    result->value = val;
    return result;
}

static Stage1AirInst *ir_resolve_cast(IrAnalyze *ira, Scope *scope, AstNode *source_node, Stage1AirInst *value,
        ZigType *wanted_type, CastOp cast_op)
{
    if (instr_is_comptime(value) || !type_has_bits(ira->codegen, wanted_type)) {
        Stage1AirInst *result = ir_const(ira, scope, source_node, wanted_type);
        ZigValue *val = ir_resolve_const(ira, value, UndefBad);
        if (val == nullptr)
            return ira->codegen->invalid_inst_gen;

        if (!eval_const_expr_implicit_cast(ira, scope, source_node, cast_op, val, val->type,
            result->value, wanted_type))
        {
            return ira->codegen->invalid_inst_gen;
        }
        return result;
    } else {
        return ir_build_cast(ira, scope, source_node,
                wanted_type, value, cast_op);
    }
}

static Stage1AirInst *ir_resolve_ptr_of_array_to_unknown_len_ptr(IrAnalyze *ira,
        Scope *scope, AstNode *source_node, Stage1AirInst *value, ZigType *wanted_type)
{
    src_assert(value->value->type->id == ZigTypeIdPointer, source_node);

    Error err;

    if ((err = type_resolve(ira->codegen, value->value->type->data.pointer.child_type,
                    ResolveStatusAlignmentKnown)))
    {
        return ira->codegen->invalid_inst_gen;
    }

    wanted_type = adjust_ptr_align(ira->codegen, wanted_type, get_ptr_align(ira->codegen, value->value->type));

    if (instr_is_comptime(value)) {
        ZigValue *val = ir_resolve_const(ira, value, UndefOk);
        if (val == nullptr)
            return ira->codegen->invalid_inst_gen;
        if (val->special == ConstValSpecialUndef)
            return ir_const_undef(ira, scope, source_node, wanted_type);

        ZigValue *pointee = const_ptr_pointee(ira, ira->codegen, val, source_node);
        if (pointee == nullptr)
            return ira->codegen->invalid_inst_gen;
        if (pointee->special != ConstValSpecialRuntime) {
            Stage1AirInst *result = ir_const(ira, scope, source_node, wanted_type);
            result->value->data.x_ptr.special = ConstPtrSpecialBaseArray;
            result->value->data.x_ptr.mut = val->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;
            return result;
        }
    }

    return ir_build_cast(ira, scope, source_node,
            wanted_type, value, CastOpBitCast);
}

static Stage1AirInst *ir_resolve_ptr_of_array_to_slice(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        Stage1AirInst *array_ptr, ZigType *wanted_type, ResultLoc *result_loc)
{
    Error err;

    assert(array_ptr->value->type->id == ZigTypeIdPointer);
    assert(array_ptr->value->type->data.pointer.child_type->id == ZigTypeIdArray);

    ZigType *array_type = array_ptr->value->type->data.pointer.child_type;
    size_t array_len = array_type->data.array.len;

    // A zero-sized array can be casted regardless of the destination alignment, or
    // whether the pointer is undefined, and the result is always comptime known.
    // TODO However, this is exposing a result location bug that I failed to solve on the first try.
    // If you want to try to fix the bug, uncomment this block and get the tests passing.
    //if (array_len == 0 && array_type->data.array.sentinel == nullptr) {
    //    ZigValue *undef_array = ira->codegen->pass1_arena->create<ZigValue>();
    //    undef_array->special = ConstValSpecialUndef;
    //    undef_array->type = array_type;

    //    Stage1AirInst *result = ir_const(ira, scope, source_node, wanted_type);
    //    init_const_slice(ira->codegen, result->value, undef_array, 0, 0, false, nullptr);
    //    result->value->data.x_struct.fields[slice_ptr_index]->data.x_ptr.mut = ConstPtrMutComptimeConst;
    //    result->value->type = wanted_type;
    //    return result;
    //}

    if ((err = type_resolve(ira->codegen, array_ptr->value->type, ResolveStatusAlignmentKnown))) {
        return ira->codegen->invalid_inst_gen;
    }

    if (array_len != 0) {
        wanted_type = adjust_slice_align(ira->codegen, wanted_type,
            get_ptr_align(ira->codegen, array_ptr->value->type));
    }

    if (instr_is_comptime(array_ptr)) {
        UndefAllowed undef_allowed = (array_len == 0) ? UndefOk : UndefBad;
        ZigValue *array_ptr_val = ir_resolve_const(ira, array_ptr, undef_allowed);
        if (array_ptr_val == nullptr)
            return ira->codegen->invalid_inst_gen;
        src_assert(is_slice(wanted_type), source_node);
        if (array_ptr_val->special == ConstValSpecialUndef) {
            ZigValue *undef_array = ira->codegen->pass1_arena->create<ZigValue>();
            undef_array->special = ConstValSpecialUndef;
            undef_array->type = array_type;

            Stage1AirInst *result = ir_const(ira, scope, source_node, wanted_type);
            init_const_slice(ira->codegen, result->value, undef_array, 0, 0, false, nullptr);
            result->value->data.x_struct.fields[slice_ptr_index]->data.x_ptr.mut = ConstPtrMutComptimeConst;
            result->value->type = wanted_type;
            return result;
        }
        bool wanted_const = wanted_type->data.structure.fields[slice_ptr_index]->type_entry->data.pointer.is_const;
        // Optimization to avoid creating unnecessary ZigValue in const_ptr_pointee
        if (array_ptr_val->data.x_ptr.special == ConstPtrSpecialSubArray) {
            ZigValue *array_val = array_ptr_val->data.x_ptr.data.base_array.array_val;
            if (array_val->special != ConstValSpecialRuntime) {
                Stage1AirInst *result = ir_const(ira, scope, source_node, wanted_type);
                init_const_slice(ira->codegen, result->value, array_val,
                        array_ptr_val->data.x_ptr.data.base_array.elem_index,
                        array_type->data.array.len, wanted_const, nullptr);
                result->value->data.x_struct.fields[slice_ptr_index]->data.x_ptr.mut = array_ptr_val->data.x_ptr.mut;
                result->value->type = wanted_type;
                return result;
            }
        } else if (array_ptr_val->data.x_ptr.special != ConstPtrSpecialHardCodedAddr) {
            ZigValue *pointee = const_ptr_pointee(ira, ira->codegen, array_ptr_val, source_node);
            if (pointee == nullptr)
                return ira->codegen->invalid_inst_gen;
            if (pointee->special != ConstValSpecialRuntime) {
                assert(array_ptr_val->type->id == ZigTypeIdPointer);

                Stage1AirInst *result = ir_const(ira, scope, source_node, wanted_type);
                init_const_slice(ira->codegen, result->value, pointee, 0, array_type->data.array.len, wanted_const, nullptr);
                result->value->data.x_struct.fields[slice_ptr_index]->data.x_ptr.mut = array_ptr_val->data.x_ptr.mut;
                result->value->type = wanted_type;
                return result;
            }
        }
    }

    if (result_loc == nullptr) result_loc = no_result_loc();
    Stage1AirInst *result_loc_inst = ir_resolve_result(ira, ira->suspend_source_instr,
            result_loc, wanted_type, nullptr, true, true);
    if (type_is_invalid(result_loc_inst->value->type) ||
        result_loc_inst->value->type->id == ZigTypeIdUnreachable)
    {
        return result_loc_inst;
    }
    return ir_build_ptr_of_array_to_slice(ira, scope, source_node, wanted_type, array_ptr, result_loc_inst);
}

static Stage1AirBasicBlock *ir_get_new_bb(IrAnalyze *ira, Stage1ZirBasicBlock *old_bb, Stage1ZirInst *ref_old_instruction) {
    assert(old_bb);

    if (old_bb->child) {
        if (ref_old_instruction == nullptr || old_bb->child->ref_instruction != ref_old_instruction) {
            return old_bb->child;
        }
    }

    Stage1AirBasicBlock *new_bb = ir_build_bb_from(ira, old_bb);
    new_bb->ref_instruction = ref_old_instruction;

    return new_bb;
}

static Stage1AirBasicBlock *ir_get_new_bb_runtime(IrAnalyze *ira, Stage1ZirBasicBlock *old_bb, Stage1ZirInst *ref_old_instruction) {
    assert(ref_old_instruction != nullptr);
    Stage1AirBasicBlock *new_bb = ir_get_new_bb(ira, old_bb, ref_old_instruction);
    if (new_bb->must_be_comptime_source_node != nullptr) {
        ErrorMsg *msg = ir_add_error_node(ira, ref_old_instruction->source_node,
            buf_sprintf("control flow attempts to use compile-time variable at runtime"));
        add_error_note(ira->codegen, msg, new_bb->must_be_comptime_source_node,
                buf_sprintf("compile-time variable assigned here"));
        return nullptr;
    }
    return new_bb;
}

static void ir_start_bb(IrAnalyze *ira, Stage1ZirBasicBlock *old_bb, Stage1ZirBasicBlock *const_predecessor_bb) {
    src_assert(!old_bb->suspended,
            (old_bb->instruction_list.length != 0) ?
            old_bb->instruction_list.at(0)->source_node : nullptr);
    ira->instruction_index = 0;
    ira->zir_current_basic_block = old_bb;
    ira->const_predecessor_bb = const_predecessor_bb;
    ira->old_bb_index = old_bb->index;
}

static Stage1AirInst *ira_suspend(IrAnalyze *ira, Stage1ZirInst *old_instruction, Stage1ZirBasicBlock *next_bb,
        IrSuspendPosition *suspend_pos)
{
    if (ira->codegen->verbose_ir) {
        fprintf(stderr, "suspend %s_%" PRIu32 " %s_%" PRIu32 " #%" PRIu32 " (%zu,%zu)\n",
                ira->zir_current_basic_block->name_hint,
                ira->zir_current_basic_block->debug_id,
                ira->zir->basic_block_list.at(ira->old_bb_index)->name_hint,
                ira->zir->basic_block_list.at(ira->old_bb_index)->debug_id,
                ira->zir_current_basic_block->instruction_list.at(ira->instruction_index)->debug_id,
                ira->old_bb_index, ira->instruction_index);
    }
    suspend_pos->basic_block_index = ira->old_bb_index;
    suspend_pos->instruction_index = ira->instruction_index;

    ira->zir_current_basic_block->suspended = true;

    // null next_bb means that the caller plans to call ira_resume before returning
    if (next_bb != nullptr) {
        ira->old_bb_index = next_bb->index;
        ira->zir_current_basic_block = ira->zir->basic_block_list.at(ira->old_bb_index);
        assert(ira->zir_current_basic_block == next_bb);
        ira->instruction_index = 0;
        ira->const_predecessor_bb = nullptr;
        next_bb->child = ir_get_new_bb_runtime(ira, next_bb, old_instruction);
        ira->new_irb.current_basic_block = next_bb->child;
    }
    return ira->codegen->unreach_instruction;
}

static Stage1AirInst *ira_resume(IrAnalyze *ira) {
    IrSuspendPosition pos = ira->resume_stack.pop();
    if (ira->codegen->verbose_ir) {
        fprintf(stderr, "resume (%zu,%zu) ", pos.basic_block_index, pos.instruction_index);
    }
    ira->old_bb_index = pos.basic_block_index;
    ira->zir_current_basic_block = ira->zir->basic_block_list.at(ira->old_bb_index);
    assert(ira->zir_current_basic_block->in_resume_stack);
    ira->zir_current_basic_block->in_resume_stack = false;
    ira->zir_current_basic_block->suspended = false;
    ira->instruction_index = pos.instruction_index;
    assert(pos.instruction_index < ira->zir_current_basic_block->instruction_list.length);
    if (ira->codegen->verbose_ir) {
        fprintf(stderr, "%s_%" PRIu32 " #%" PRIu32 "\n", ira->zir_current_basic_block->name_hint,
                ira->zir_current_basic_block->debug_id,
                ira->zir_current_basic_block->instruction_list.at(pos.instruction_index)->debug_id);
    }
    ira->const_predecessor_bb = nullptr;
    ira->new_irb.current_basic_block = ira->zir_current_basic_block->child;
    assert(ira->new_irb.current_basic_block != nullptr);
    return ira->codegen->unreach_instruction;
}

static void ir_start_next_bb(IrAnalyze *ira) {
    ira->old_bb_index += 1;

    bool need_repeat = true;
    for (;;) {
        while (ira->old_bb_index < ira->zir->basic_block_list.length) {
            Stage1ZirBasicBlock *old_bb = ira->zir->basic_block_list.at(ira->old_bb_index);
            if (old_bb->child == nullptr && old_bb->suspend_instruction_ref == nullptr) {
                ira->old_bb_index += 1;
                continue;
            }
            // if it's already started, or
            // if it's a suspended block,
            // then skip it
            if (old_bb->suspended ||
                (old_bb->child != nullptr && old_bb->child->instruction_list.length != 0) ||
                (old_bb->child != nullptr && old_bb->child->already_appended))
            {
                ira->old_bb_index += 1;
                continue;
            }

            // if there is a resume_stack, pop one from there rather than moving on.
            // the last item of the resume stack will be a basic block that will
            // move on to the next one below
            if (ira->resume_stack.length != 0) {
                ira_resume(ira);
                return;
            }

            if (old_bb->child == nullptr) {
                old_bb->child = ir_get_new_bb_runtime(ira, old_bb, old_bb->suspend_instruction_ref);
            }
            ira->new_irb.current_basic_block = old_bb->child;
            ir_start_bb(ira, old_bb, nullptr);
            return;
        }
        if (!need_repeat) {
            if (ira->resume_stack.length != 0) {
                ira_resume(ira);
            }
            return;
        }
        need_repeat = false;
        ira->old_bb_index = 0;
        continue;
    }
}

static void ir_finish_bb(IrAnalyze *ira) {
    if (!ira->new_irb.current_basic_block->already_appended) {
        ir_append_basic_block_gen(&ira->new_irb, ira->new_irb.current_basic_block);
        if (ira->codegen->verbose_ir) {
            fprintf(stderr, "append new bb %s_%" PRIu32 "\n", ira->new_irb.current_basic_block->name_hint,
                    ira->new_irb.current_basic_block->debug_id);
        }
    }
    ir_start_next_bb(ira);
}

static Stage1AirInst *ir_unreach_error(IrAnalyze *ira) {
    ira->old_bb_index = SIZE_MAX;
    if (ira->new_irb.exec->first_err_trace_msg == nullptr) {
        ira->new_irb.exec->first_err_trace_msg = ira->codegen->trace_err;
    }
    return ira->codegen->unreach_instruction;
}

static bool ir_emit_backward_branch(IrAnalyze *ira, AstNode* source_node) {
    size_t *bbc = ira->backward_branch_count;
    size_t *quota = ira->backward_branch_quota;

    // If we're already over quota, we've already given an error message for this.
    if (*bbc > *quota) {
        assert(ira->codegen->errors.length > 0);
        return false;
    }

    *bbc += 1;
    if (*bbc > *quota) {
        ir_add_error_node(ira, source_node,
                buf_sprintf("evaluation exceeded %" ZIG_PRI_usize " backwards branches", *quota));
        return false;
    }
    return true;
}

static Stage1AirInst *ir_inline_bb(IrAnalyze *ira, AstNode* source_node, Stage1ZirBasicBlock *old_bb) {
    if (old_bb->debug_id <= ira->zir_current_basic_block->debug_id) {
        if (!ir_emit_backward_branch(ira, source_node))
            return ir_unreach_error(ira);
    }

    old_bb->child = ira->zir_current_basic_block->child;
    ir_start_bb(ira, old_bb, ira->zir_current_basic_block);
    return ira->codegen->unreach_instruction;
}

static Stage1AirInst *ir_finish_anal(IrAnalyze *ira, Stage1AirInst *instruction) {
    if (instruction->value->type->id == ZigTypeIdUnreachable)
        ir_finish_bb(ira);
    return instruction;
}

static Stage1AirInst *ir_const_fn(IrAnalyze *ira, Scope *scope, AstNode *source_node, ZigFn *fn_entry) {
    Stage1AirInst *result = ir_const(ira, scope, source_node, fn_entry->type_entry);
    result->value->special = ConstValSpecialStatic;
    result->value->data.x_ptr.data.fn.fn_entry = fn_entry;
    result->value->data.x_ptr.mut = ConstPtrMutComptimeConst;
    result->value->data.x_ptr.special = ConstPtrSpecialFunction;
    return result;
}

static Stage1AirInst *ir_const_bound_fn(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        ZigFn *fn_entry, Stage1AirInst *first_arg, AstNode *first_arg_src)
{
    // This is unfortunately required to avoid improperly freeing first_arg_src
    ira_ref(ira);

    Stage1AirInst *result = ir_const(ira, scope, source_node, get_bound_fn_type(ira->codegen, fn_entry));
    result->value->data.x_bound_fn.fn = fn_entry;
    result->value->data.x_bound_fn.first_arg = first_arg;
    result->value->data.x_bound_fn.first_arg_src = first_arg_src;
    return result;
}

static Stage1AirInst *ir_const_type(IrAnalyze *ira, Scope *scope, AstNode *source_node, ZigType *ty) {
    Stage1AirInst *result = ir_const(ira, scope, source_node, ira->codegen->builtin_types.entry_type);
    result->value->data.x_type = ty;
    return result;
}

static Stage1AirInst *ir_const_bool(IrAnalyze *ira, Scope *scope, AstNode *source_node, bool value) {
    Stage1AirInst *result = ir_const(ira, scope, source_node, ira->codegen->builtin_types.entry_bool);
    result->value->data.x_bool = value;
    return result;
}

static Stage1AirInst *ir_const_undef(IrAnalyze *ira, Scope *scope, AstNode *source_node, ZigType *ty) {
    Stage1AirInst *result = ir_const(ira, scope, source_node, ty);
    result->value->special = ConstValSpecialUndef;
    return result;
}

static Stage1AirInst *ir_const_unreachable(IrAnalyze *ira, Scope *scope, AstNode *source_node) {
    Stage1AirInst *result = ir_const_noval(ira, scope, source_node);
    result->value = ira->codegen->intern.for_unreachable();
    return result;
}

static Stage1AirInst *ir_const_void(IrAnalyze *ira, Scope *scope, AstNode *source_node) {
    Stage1AirInst *result = ir_const_noval(ira, scope, source_node);
    result->value = ira->codegen->intern.for_void();
    return result;
}

static Stage1AirInst *ir_const_unsigned(IrAnalyze *ira, Scope *scope, AstNode *source_node, uint64_t value) {
    Stage1AirInst *result = ir_const(ira, scope, source_node, ira->codegen->builtin_types.entry_num_lit_int);
    bigint_init_unsigned(&result->value->data.x_bigint, value);
    return result;
}

static Stage1AirInst *ir_get_const_ptr(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        ZigValue *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, false);
    Stage1AirInst *const_instr = ir_const(ira, scope, source_node, ptr_type);
    ZigValue *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 Error ir_resolve_const_val(CodeGen *codegen, Stage1Air *exec, AstNode *source_node,
        ZigValue *val, UndefAllowed undef_allowed)
{
    Error err;
    for (;;) {
        switch (val->special) {
            case ConstValSpecialStatic:
                return ErrorNone;
            case ConstValSpecialRuntime:
                if (!type_has_bits(codegen, val->type))
                    return ErrorNone;

                exec_add_error_node_gen(codegen, exec, source_node,
                        buf_sprintf("unable to evaluate constant expression"));
                return ErrorSemanticAnalyzeFail;
            case ConstValSpecialUndef:
                if (undef_allowed == UndefOk || undef_allowed == LazyOk)
                    return ErrorNone;

                exec_add_error_node_gen(codegen, exec, source_node,
                        buf_sprintf("use of undefined value here causes undefined behavior"));
                return ErrorSemanticAnalyzeFail;
            case ConstValSpecialLazy:
                if (undef_allowed == LazyOk || undef_allowed == LazyOkNoUndef)
                    return ErrorNone;

                if ((err = ir_resolve_lazy(codegen, source_node, val)))
                    return err;

                continue;
        }
    }
}

static ZigValue *ir_resolve_const(IrAnalyze *ira, Stage1AirInst *value, UndefAllowed undef_allowed) {
    Error err;
    if ((err = ir_resolve_const_val(ira->codegen, ira->new_irb.exec, value->source_node,
                    value->value, undef_allowed)))
    {
        return nullptr;
    }
    return value->value;
}

Error ir_eval_const_value(CodeGen *codegen, Scope *scope, AstNode *node,
        ZigValue *return_ptr, size_t *backward_branch_count, size_t *backward_branch_quota,
        ZigFn *fn_entry, Buf *c_import_buf, AstNode *source_node, Buf *exec_name,
        Stage1Air *parent_exec, AstNode *expected_type_source_node, UndefAllowed undef_allowed)
{
    Error err;

    src_assert(return_ptr->type->id == ZigTypeIdPointer, source_node);

    if (type_is_invalid(return_ptr->type))
        return ErrorSemanticAnalyzeFail;

    Stage1Zir *stage1_zir = heap::c_allocator.create<Stage1Zir>();
    stage1_zir->name = exec_name;
    stage1_zir->is_inline = true;
    stage1_zir->begin_scope = scope;

    bool in_c_import_scope = c_import_buf != nullptr;

    if (!stage1_astgen(codegen, node, scope, stage1_zir, fn_entry, in_c_import_scope))
        return ErrorSemanticAnalyzeFail;

    if (stage1_zir->first_err_trace_msg != nullptr) {
        codegen->trace_err = stage1_zir->first_err_trace_msg;
        return ErrorSemanticAnalyzeFail;
    }

    if (codegen->verbose_ir) {
        fprintf(stderr, "\n{ // (IR)\n");
        ir_print_src(codegen, stderr, stage1_zir, 2);
        fprintf(stderr, "}\n");
    }
    Stage1Air *analyzed_executable = heap::c_allocator.create<Stage1Air>();
    analyzed_executable->source_node = source_node;
    analyzed_executable->parent_exec = parent_exec;
    analyzed_executable->source_exec = stage1_zir;
    analyzed_executable->name = exec_name;
    analyzed_executable->is_inline = true;
    analyzed_executable->c_import_buf = c_import_buf;
    analyzed_executable->begin_scope = scope;
    ZigType *result_type = ir_analyze(codegen, stage1_zir, analyzed_executable,
            backward_branch_count, backward_branch_quota,
            return_ptr->type->data.pointer.child_type, expected_type_source_node, return_ptr,
            fn_entry);
    if (type_is_invalid(result_type)) {
        return ErrorSemanticAnalyzeFail;
    }

    if (codegen->verbose_ir) {
        fprintf(stderr, "{ // (analyzed)\n");
        ir_print_gen(codegen, stderr, analyzed_executable, 2);
        fprintf(stderr, "}\n");
    }

    if ((err = ir_exec_scan_for_side_effects(codegen, analyzed_executable)))
        return err;

    ZigValue *result = const_ptr_pointee(nullptr, codegen, return_ptr, source_node);
    if (result == nullptr)
        return ErrorSemanticAnalyzeFail;
    if ((err = ir_resolve_const_val(codegen, analyzed_executable, node, result, undef_allowed)))
        return err;

    return ErrorNone;
}

static ErrorTableEntry *ir_resolve_error(IrAnalyze *ira, Stage1AirInst *err_value) {
    if (type_is_invalid(err_value->value->type))
        return nullptr;

    if (err_value->value->type->id != ZigTypeIdErrorSet) {
        ir_add_error_node(ira, err_value->source_node,
                buf_sprintf("expected error, found '%s'", buf_ptr(&err_value->value->type->name)));
        return nullptr;
    }

    ZigValue *const_val = ir_resolve_const(ira, err_value, UndefBad);
    if (!const_val)
        return nullptr;

    assert(const_val->data.x_err_set != nullptr);
    return const_val->data.x_err_set;
}

static ZigType *ir_resolve_const_type(CodeGen *codegen, Stage1Air *exec, AstNode *source_node,
        ZigValue *val)
{
    Error err;
    if ((err = ir_resolve_const_val(codegen, exec, source_node, val, UndefBad)))
        return codegen->builtin_types.entry_invalid;

    assert(val->data.x_type != nullptr);
    return val->data.x_type;
}

static ZigValue *ir_resolve_type_lazy(IrAnalyze *ira, Stage1AirInst *type_value) {
    if (type_is_invalid(type_value->value->type))
        return nullptr;

    if (type_value->value->type->id != ZigTypeIdMetaType) {
        ir_add_error_node(ira, type_value->source_node,
                buf_sprintf("expected type 'type', found '%s'", buf_ptr(&type_value->value->type->name)));
        return nullptr;
    }

    Error err;
    if ((err = ir_resolve_const_val(ira->codegen, ira->new_irb.exec, type_value->source_node,
                    type_value->value, LazyOkNoUndef)))
    {
        return nullptr;
    }

    return type_value->value;
}

static ZigType *ir_resolve_type(IrAnalyze *ira, Stage1AirInst *type_value) {
    ZigValue *val = ir_resolve_type_lazy(ira, type_value);
    if (val == nullptr)
        return ira->codegen->builtin_types.entry_invalid;

    return ir_resolve_const_type(ira->codegen, ira->new_irb.exec, type_value->source_node, val);
}

static Error ir_validate_vector_elem_type(IrAnalyze *ira, AstNode *source_node, ZigType *elem_type) {
    Error err;
    bool is_valid;
    if ((err = is_valid_vector_elem_type(ira->codegen, elem_type, &is_valid)))
        return err;
    if (!is_valid) {
        ir_add_error_node(ira, source_node,
            buf_sprintf("vector element type must be integer, float, bool, or pointer; '%s' is invalid",
                buf_ptr(&elem_type->name)));
        return ErrorSemanticAnalyzeFail;
    }
    return ErrorNone;
}

static ZigType *ir_resolve_vector_elem_type(IrAnalyze *ira, Stage1AirInst *elem_type_value) {
    Error err;
    ZigType *elem_type = ir_resolve_type(ira, elem_type_value);
    if (type_is_invalid(elem_type))
        return ira->codegen->builtin_types.entry_invalid;
    if ((err = ir_validate_vector_elem_type(ira, elem_type_value->source_node, elem_type)))
        return ira->codegen->builtin_types.entry_invalid;
    return elem_type;
}

static ZigType *ir_resolve_int_type(IrAnalyze *ira, Stage1AirInst *type_value) {
    ZigType *ty = ir_resolve_type(ira, type_value);
    if (type_is_invalid(ty))
        return ira->codegen->builtin_types.entry_invalid;

    if (ty->id != ZigTypeIdInt) {
        ErrorMsg *msg = ir_add_error_node(ira, type_value->source_node,
            buf_sprintf("expected integer type, found '%s'", buf_ptr(&ty->name)));
        if (ty->id == ZigTypeIdVector &&
            ty->data.vector.elem_type->id == ZigTypeIdInt)
        {
            add_error_note(ira->codegen, msg, type_value->source_node,
                buf_sprintf("represent vectors with their element types, i.e. '%s'",
                    buf_ptr(&ty->data.vector.elem_type->name)));
        }
        return ira->codegen->builtin_types.entry_invalid;
    }

    return ty;
}

static ZigType *ir_resolve_error_set_type(IrAnalyze *ira, AstNode *op_source, Stage1AirInst *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_node(ira, type_value->source_node,
                buf_sprintf("expected error set type, found '%s'", buf_ptr(&type_value->value->type->name)));
        add_error_note(ira->codegen, msg, op_source,
                buf_sprintf("`||` merges error sets; `or` performs boolean OR"));
        return ira->codegen->builtin_types.entry_invalid;
    }

    ZigValue *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_node(ira, type_value->source_node,
                buf_sprintf("expected error set type, found type '%s'", buf_ptr(&result_type->name)));
        add_error_note(ira->codegen, msg, op_source,
                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, Stage1AirInst *fn_value) {
    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;
    }

    ZigValue *const_val = ir_resolve_const(ira, fn_value, UndefBad);
    if (!const_val)
        return nullptr;

    // May be a ConstPtrSpecialHardCodedAddr
    if (const_val->data.x_ptr.special != ConstPtrSpecialFunction)
        return nullptr;

    return const_val->data.x_ptr.data.fn.fn_entry;
}

static Stage1AirInst *ir_analyze_optional_wrap(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        Stage1AirInst *value, ZigType *wanted_type, ResultLoc *result_loc)
{
    assert(wanted_type->id == ZigTypeIdOptional);

    if (instr_is_comptime(value)) {
        ZigType *payload_type = wanted_type->data.maybe.child_type;
        Stage1AirInst *casted_payload = ir_implicit_cast(ira, value, payload_type);
        if (type_is_invalid(casted_payload->value->type))
            return ira->codegen->invalid_inst_gen;

        ZigValue *val = ir_resolve_const(ira, casted_payload, UndefOk);
        if (!val)
            return ira->codegen->invalid_inst_gen;

        Stage1AirInstConst *const_instruction = ir_create_inst_gen<Stage1AirInstConst>(&ira->new_irb,
                scope, source_node);
        const_instruction->base.value->special = ConstValSpecialStatic;
        if (types_have_same_zig_comptime_repr(ira->codegen, wanted_type, payload_type)) {
            copy_const_val(ira->codegen, const_instruction->base.value, val);
        } else {
            const_instruction->base.value->data.x_optional = val;
        }
        const_instruction->base.value->type = wanted_type;
        return &const_instruction->base;
    }

    if (result_loc == nullptr && handle_is_ptr(ira->codegen, wanted_type)) {
        result_loc = no_result_loc();
    }
    Stage1AirInst *result_loc_inst = nullptr;
    if (result_loc != nullptr) {
        result_loc_inst = ir_resolve_result(ira, ira->suspend_source_instr, result_loc, wanted_type, nullptr, true, true);
        if (type_is_invalid(result_loc_inst->value->type) ||
            result_loc_inst->value->type->id == ZigTypeIdUnreachable)
        {
            return result_loc_inst;
        }
    }
    Stage1AirInst *result = ir_build_optional_wrap(ira, scope, source_node, wanted_type, value, result_loc_inst);
    result->value->data.rh_maybe = RuntimeHintOptionalNonNull;
    return result;
}

static Stage1AirInst *ir_analyze_err_wrap_payload(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        Stage1AirInst *value, ZigType *wanted_type, ResultLoc *result_loc)
{
    assert(wanted_type->id == ZigTypeIdErrorUnion);

    ZigType *payload_type = wanted_type->data.error_union.payload_type;
    ZigType *err_set_type = wanted_type->data.error_union.err_set_type;
    if (instr_is_comptime(value)) {
        Stage1AirInst *casted_payload = ir_implicit_cast(ira, value, payload_type);
        if (type_is_invalid(casted_payload->value->type))
            return ira->codegen->invalid_inst_gen;

        ZigValue *val = ir_resolve_const(ira, casted_payload, UndefOk);
        if (val == nullptr)
            return ira->codegen->invalid_inst_gen;

        ZigValue *err_set_val = ira->codegen->pass1_arena->create<ZigValue>();
        err_set_val->type = err_set_type;
        err_set_val->special = ConstValSpecialStatic;
        err_set_val->data.x_err_set = nullptr;

        Stage1AirInstConst *const_instruction = ir_create_inst_gen<Stage1AirInstConst>(&ira->new_irb,
                scope, 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;
    }

    Stage1AirInst *result_loc_inst;
    if (handle_is_ptr(ira->codegen, wanted_type)) {
        if (result_loc == nullptr) result_loc = no_result_loc();
        result_loc_inst = ir_resolve_result(ira, ira->suspend_source_instr, result_loc, wanted_type, nullptr, true, true);
        if (type_is_invalid(result_loc_inst->value->type) ||
            result_loc_inst->value->type->id == ZigTypeIdUnreachable) {
            return result_loc_inst;
        }
    } else {
        result_loc_inst = nullptr;
    }

    Stage1AirInst *result = ir_build_err_wrap_payload(ira, scope, source_node, wanted_type, value, result_loc_inst);
    result->value->data.rh_error_union = RuntimeHintErrorUnionNonError;
    return result;
}

static Stage1AirInst *ir_analyze_err_set_cast(IrAnalyze *ira, Scope *scope, AstNode *source_node, Stage1AirInst *value,
        ZigType *wanted_type)
{
    assert(value->value->type->id == ZigTypeIdErrorSet);
    assert(wanted_type->id == ZigTypeIdErrorSet);

    if (instr_is_comptime(value)) {
        ZigValue *val = ir_resolve_const(ira, value, UndefBad);
        if (!val)
            return ira->codegen->invalid_inst_gen;

        if (!resolve_inferred_error_set(ira->codegen, wanted_type, source_node)) {
            return ira->codegen->invalid_inst_gen;
        }
        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_node(ira, source_node,
                    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_inst_gen;
            }
        }

        Stage1AirInstConst *const_instruction = ir_create_inst_gen<Stage1AirInstConst>(&ira->new_irb,
                scope, 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;
    }

    return ir_build_cast(ira, scope, source_node, wanted_type, value, CastOpErrSet);
}

static Stage1AirInst *ir_analyze_frame_ptr_to_anyframe(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        Stage1AirInst *frame_ptr, ZigType *wanted_type)
{
    if (instr_is_comptime(frame_ptr)) {
        ZigValue *ptr_val = ir_resolve_const(ira, frame_ptr, UndefBad);
        if (ptr_val == nullptr)
            return ira->codegen->invalid_inst_gen;

        src_assert(ptr_val->type->id == ZigTypeIdPointer, source_node );
        if (ptr_val->data.x_ptr.mut != ConstPtrMutRuntimeVar) {
            zig_panic("TODO comptime frame pointer");
        }
    }

    return ir_build_cast(ira, scope, source_node, wanted_type, frame_ptr, CastOpBitCast);
}

static Stage1AirInst *ir_analyze_anyframe_to_anyframe(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        Stage1AirInst *value, ZigType *wanted_type)
{
    if (instr_is_comptime(value)) {
        zig_panic("TODO comptime anyframe->T to anyframe");
    }

    return ir_build_cast(ira, scope, source_node, wanted_type, value, CastOpBitCast);
}


static Stage1AirInst *ir_analyze_err_wrap_code(IrAnalyze *ira, Scope *scope, AstNode *source_node, Stage1AirInst *value,
        ZigType *wanted_type, ResultLoc *result_loc)
{
    assert(wanted_type->id == ZigTypeIdErrorUnion);

    Stage1AirInst *casted_value = ir_implicit_cast(ira, value, wanted_type->data.error_union.err_set_type);

    if (instr_is_comptime(casted_value)) {
        ZigValue *val = ir_resolve_const(ira, casted_value, UndefBad);
        if (!val)
            return ira->codegen->invalid_inst_gen;

        ZigValue *err_set_val = ira->codegen->pass1_arena->create<ZigValue>();
        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;

        Stage1AirInstConst *const_instruction = ir_create_inst_gen<Stage1AirInstConst>(&ira->new_irb,
                scope, 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;
    }

    Stage1AirInst *result_loc_inst;
    if (handle_is_ptr(ira->codegen, wanted_type)) {
        if (result_loc == nullptr) result_loc = no_result_loc();
        result_loc_inst = ir_resolve_result(ira, ira->suspend_source_instr, result_loc, wanted_type, nullptr, true, true);
        if (type_is_invalid(result_loc_inst->value->type) ||
            result_loc_inst->value->type->id == ZigTypeIdUnreachable)
        {
            return result_loc_inst;
        }
    } else {
        result_loc_inst = nullptr;
    }


    Stage1AirInst *result = ir_build_err_wrap_code(ira, scope, source_node, wanted_type, value, result_loc_inst);
    result->value->data.rh_error_union = RuntimeHintErrorUnionError;
    return result;
}

static Stage1AirInst *ir_analyze_null_to_maybe(IrAnalyze *ira, Scope *scope, AstNode *source_node, Stage1AirInst *value, ZigType *wanted_type) {
    assert(wanted_type->id == ZigTypeIdOptional);
    assert(instr_is_comptime(value));

    ZigValue *val = ir_resolve_const(ira, value, UndefBad);
    assert(val != nullptr);

    Stage1AirInst *result = ir_const(ira, scope, source_node, wanted_type);
    result->value->special = ConstValSpecialStatic;

    if (get_src_ptr_type(wanted_type) != nullptr) {
        result->value->data.x_ptr.special = ConstPtrSpecialNull;
    } else if (is_opt_err_set(wanted_type)) {
        result->value->data.x_err_set = nullptr;
    } else {
        result->value->data.x_optional = nullptr;
    }
    return result;
}

static Stage1AirInst *ir_analyze_null_to_c_pointer(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        Stage1AirInst *value, ZigType *wanted_type)
{
    assert(wanted_type->id == ZigTypeIdPointer);
    assert(wanted_type->data.pointer.ptr_len == PtrLenC);
    assert(instr_is_comptime(value));

    ZigValue *val = ir_resolve_const(ira, value, UndefBad);
    assert(val != nullptr);

    Stage1AirInst *result = ir_const(ira, scope, source_node, wanted_type);
    result->value->data.x_ptr.special = ConstPtrSpecialNull;
    result->value->data.x_ptr.mut = ConstPtrMutComptimeConst;
    return result;
}

static Stage1AirInst *ir_get_ref2(IrAnalyze *ira, Scope *scope, AstNode *source_node, Stage1AirInst *value,
        ZigType *elem_type, bool is_const, bool is_volatile)
{
    Error err;

    if (type_is_invalid(elem_type))
        return ira->codegen->invalid_inst_gen;

    if (instr_is_comptime(value)) {
        ZigValue *val = ir_resolve_const(ira, value, LazyOk);
        if (!val)
            return ira->codegen->invalid_inst_gen;
        return ir_get_const_ptr(ira, scope, source_node, val, elem_type,
                ConstPtrMutComptimeConst, is_const, is_volatile, 0);
    }

    ZigType *ptr_type = get_pointer_to_type_extra(ira->codegen, elem_type,
            is_const, is_volatile, PtrLenSingle, 0, 0, 0, false);

    if ((err = type_resolve(ira->codegen, ptr_type, ResolveStatusZeroBitsKnown)))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *result_loc;
    if (type_has_bits(ira->codegen, ptr_type) && !handle_is_ptr(ira->codegen, elem_type)) {
        result_loc = ir_resolve_result(ira, ira->suspend_source_instr, no_result_loc(), elem_type, nullptr, true, true);
    } else {
        result_loc = nullptr;
    }

    Stage1AirInst *new_instruction = ir_build_ref_gen(ira, scope, source_node, ptr_type, value, result_loc);
    new_instruction->value->data.rh_ptr = RuntimeHintPtrStack;
    return new_instruction;
}

static Stage1AirInst *ir_get_ref(IrAnalyze *ira, Scope *scope, AstNode *source_node, Stage1AirInst *value,
        bool is_const, bool is_volatile)
{
    return ir_get_ref2(ira, scope, source_node, value, value->value->type, is_const, is_volatile);
}

static ZigType *ir_resolve_union_tag_type(IrAnalyze *ira, AstNode *source_node, 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_node(ira, source_node, 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 bool can_fold_enum_type(ZigType *ty) {
    assert(ty->id == ZigTypeIdEnum);
    // We can fold the enum type (and avoid any check, be it at runtime or at
    // compile time) iff it has only a single element and its tag type is
    // zero-sized.
    ZigType *tag_int_type = ty->data.enumeration.tag_int_type;
    return ty->data.enumeration.layout == ContainerLayoutAuto &&
            ty->data.enumeration.src_field_count == 1 &&
            !ty->data.enumeration.non_exhaustive &&
            (tag_int_type->id == ZigTypeIdInt && tag_int_type->data.integral.bit_count == 0);
}

static Stage1AirInst *ir_analyze_enum_to_int(IrAnalyze *ira, Scope *scope, AstNode *source_node, Stage1AirInst *target) {
    Error err;

    Stage1AirInst *enum_target;
    ZigType *enum_type;
    if (target->value->type->id == ZigTypeIdUnion) {
        enum_type = ir_resolve_union_tag_type(ira, target->source_node, target->value->type);
        if (type_is_invalid(enum_type))
            return ira->codegen->invalid_inst_gen;
        enum_target = ir_implicit_cast(ira, target, enum_type);
        if (type_is_invalid(enum_target->value->type))
            return ira->codegen->invalid_inst_gen;
    } else if (target->value->type->id == ZigTypeIdEnum) {
        enum_target = target;
        enum_type = target->value->type;
    } else {
        ir_add_error_node(ira, target->source_node,
            buf_sprintf("expected enum, found type '%s'", buf_ptr(&target->value->type->name)));
        return ira->codegen->invalid_inst_gen;
    }

    if ((err = type_resolve(ira->codegen, enum_type, ResolveStatusSizeKnown)))
        return ira->codegen->invalid_inst_gen;

    ZigType *tag_type = enum_type->data.enumeration.tag_int_type;
    assert(tag_type->id == ZigTypeIdInt || tag_type->id == ZigTypeIdComptimeInt);

    // If there is only one possible tag, then we know at comptime what it is.
    if (can_fold_enum_type(enum_type)) {
        Stage1AirInst *result = ir_const(ira, scope, source_node, tag_type);
        init_const_bigint(result->value, tag_type,
                &enum_type->data.enumeration.fields[0].value);
        return result;
    }

    if (instr_is_comptime(enum_target)) {
        ZigValue *val = ir_resolve_const(ira, enum_target, UndefBad);
        if (!val)
            return ira->codegen->invalid_inst_gen;
        Stage1AirInst *result = ir_const(ira, scope, source_node, tag_type);
        init_const_bigint(result->value, tag_type, &val->data.x_enum_tag);
        return result;
    }

    return ir_build_widen_or_shorten(ira, scope, source_node, enum_target, tag_type);
}

static Stage1AirInst *ir_analyze_union_to_tag(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        Stage1AirInst *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)) {
        ZigValue *val = ir_resolve_const(ira, target, UndefBad);
        if (!val)
            return ira->codegen->invalid_inst_gen;
        Stage1AirInst *result = ir_const(ira, scope, source_node, 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 (can_fold_enum_type(wanted_type)) {
        Stage1AirInst *result = ir_const(ira, scope, source_node, 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;
    }

    return ir_build_union_tag(ira, scope, source_node, target, wanted_type);
}

static Stage1AirInst *ir_analyze_undefined_to_anything(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        Stage1AirInst *target, ZigType *wanted_type)
{
    Stage1AirInst *result = ir_const(ira, scope, source_node, wanted_type);
    result->value->special = ConstValSpecialUndef;
    return result;
}

static Stage1AirInst *ir_analyze_enum_to_union(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        Stage1AirInst *uncasted_target, ZigType *wanted_type)
{
    Error err;
    assert(wanted_type->id == ZigTypeIdUnion);

    if ((err = type_resolve(ira->codegen, wanted_type, ResolveStatusZeroBitsKnown)))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *target = ir_implicit_cast(ira, uncasted_target, wanted_type->data.unionation.tag_type);
    if (type_is_invalid(target->value->type))
        return ira->codegen->invalid_inst_gen;

    if (instr_is_comptime(target)) {
        ZigValue *val = ir_resolve_const(ira, target, UndefBad);
        if (!val)
            return ira->codegen->invalid_inst_gen;
        TypeUnionField *union_field = find_union_field_by_tag(wanted_type, &val->data.x_enum_tag);
        if (union_field == nullptr) {
            Buf *int_buf = buf_alloc();
            bigint_append_buf(int_buf, &target->value->data.x_enum_tag, 10);

            ir_add_error(ira, target,
                buf_sprintf("no tag by value %s", buf_ptr(int_buf)));
            return ira->codegen->invalid_inst_gen;
        }
        ZigType *field_type = resolve_union_field_type(ira->codegen, union_field);
        if (field_type == nullptr)
            return ira->codegen->invalid_inst_gen;
        if ((err = type_resolve(ira->codegen, field_type, ResolveStatusZeroBitsKnown)))
            return ira->codegen->invalid_inst_gen;

        switch (type_has_one_possible_value(ira->codegen, field_type)) {
            case OnePossibleValueInvalid:
                return ira->codegen->invalid_inst_gen;
            case OnePossibleValueNo: {
                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_node(ira, source_node,
                        buf_sprintf("cast to union '%s' must initialize '%s' field '%s'",
                            buf_ptr(&wanted_type->name),
                            buf_ptr(&field_type->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_inst_gen;
            }
            case OnePossibleValueYes:
                break;
        }

        Stage1AirInst *result = ir_const(ira, scope, source_node, 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);
        result->value->data.x_union.payload = ira->codegen->pass1_arena->create<ZigValue>();
        result->value->data.x_union.payload->special = ConstValSpecialStatic;
        result->value->data.x_union.payload->type = field_type;
        return result;
    }

    if (target->value->type->data.enumeration.non_exhaustive) {
        ir_add_error_node(ira, source_node,
                buf_sprintf("runtime cast to union '%s' from non-exhaustive enum",
                    buf_ptr(&wanted_type->name)));
        return ira->codegen->invalid_inst_gen;
    }

    // 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) {
        return ir_build_cast(ira, target->scope, target->source_node, wanted_type, target, CastOpNoop);
    }

    ErrorMsg *msg = ir_add_error_node(ira, source_node,
            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];
        ZigType *field_type = resolve_union_field_type(ira->codegen, union_field);
        if (field_type == nullptr)
            return ira->codegen->invalid_inst_gen;
        bool has_bits;
        if ((err = type_has_bits2(ira->codegen, field_type, &has_bits)))
            return ira->codegen->invalid_inst_gen;
        if (has_bits) {
            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(&field_type->name)));
        }
    }
    return ira->codegen->invalid_inst_gen;
}

static bool value_numeric_fits_in_type(ZigValue *value, ZigType *type_entry);

static Stage1AirInst *ir_analyze_widen_or_shorten(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        Stage1AirInst *target, ZigType *wanted_type)
{
    ZigType *wanted_scalar_type = (target->value->type->id == ZigTypeIdVector) ?
        wanted_type->data.vector.elem_type : wanted_type;

    assert(wanted_scalar_type->id == ZigTypeIdInt || wanted_scalar_type->id == ZigTypeIdFloat);

    if (instr_is_comptime(target)) {
        ZigValue *val = ir_resolve_const(ira, target, UndefBad);
        if (!val)
            return ira->codegen->invalid_inst_gen;

        if (wanted_scalar_type->id == ZigTypeIdInt) {
            if (!wanted_scalar_type->data.integral.is_signed && value_cmp_numeric_val_any(val, CmpLT, nullptr)) {
                ir_add_error_node(ira, source_node,
                    buf_sprintf("attempt to cast negative value to unsigned integer"));
                return ira->codegen->invalid_inst_gen;
            }
            if (!value_numeric_fits_in_type(val, wanted_scalar_type)) {
                ir_add_error_node(ira, source_node,
                    buf_sprintf("cast from '%s' to '%s' truncates bits",
                        buf_ptr(&target->value->type->name), buf_ptr(&wanted_scalar_type->name)));
                return ira->codegen->invalid_inst_gen;
            }
        }

        Stage1AirInst *result = ir_const(ira, scope, source_node, wanted_type);
        result->value->type = wanted_type;

        if (wanted_type->id == ZigTypeIdVector) {
            result->value->data.x_array.data.s_none.elements = ira->codegen->pass1_arena->allocate<ZigValue>(wanted_type->data.vector.len);

            for (size_t i = 0; i < wanted_type->data.vector.len; i++) {
                ZigValue *scalar_dest_value = &result->value->data.x_array.data.s_none.elements[i];
                ZigValue *scalar_src_value = &val->data.x_array.data.s_none.elements[i];

                scalar_dest_value->type = wanted_scalar_type;
                scalar_dest_value->special = ConstValSpecialStatic;

                if (wanted_scalar_type->id == ZigTypeIdInt) {
                    bigint_init_bigint(&scalar_dest_value->data.x_bigint, &scalar_src_value->data.x_bigint);
                } else {
                    float_init_float(scalar_dest_value, scalar_src_value);
                }
            }
        } else {
            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(ira->codegen, wanted_type)) {
        assert(wanted_type->id == ZigTypeIdInt);
        assert(type_has_bits(ira->codegen, target->value->type));
        ir_build_assert_zero(ira, scope, source_node, target);
        Stage1AirInst *result = ir_const_unsigned(ira, scope, source_node, 0);
        result->value->type = wanted_type;
        return result;
    }

    return ir_build_widen_or_shorten(ira, scope, source_node, target, wanted_type);
}

static Stage1AirInst *ir_analyze_int_to_enum(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        Stage1AirInst *target, ZigType *wanted_type)
{
    Error err;
    assert(wanted_type->id == ZigTypeIdEnum);

    ZigType *actual_type = target->value->type;

    if ((err = type_resolve(ira->codegen, wanted_type, ResolveStatusSizeKnown)))
        return ira->codegen->invalid_inst_gen;

    if (actual_type != wanted_type->data.enumeration.tag_int_type) {
        ir_add_error_node(ira, source_node,
                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_inst_gen;
    }

    assert(actual_type->id == ZigTypeIdInt || actual_type->id == ZigTypeIdComptimeInt);

    if (instr_is_comptime(target)) {
        ZigValue *val = ir_resolve_const(ira, target, UndefBad);
        if (!val)
            return ira->codegen->invalid_inst_gen;

        TypeEnumField *field = find_enum_field_by_tag(wanted_type, &val->data.x_bigint);
        if (field == nullptr && !wanted_type->data.enumeration.non_exhaustive) {
            Buf *val_buf = buf_alloc();
            bigint_append_buf(val_buf, &val->data.x_bigint, 10);
            ErrorMsg *msg = ir_add_error_node(ira, source_node,
                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_inst_gen;
        }

        Stage1AirInst *result = ir_const(ira, scope, source_node, wanted_type);
        bigint_init_bigint(&result->value->data.x_enum_tag, &val->data.x_bigint);
        return result;
    }

    return ir_build_int_to_enum_gen(ira, scope, source_node, wanted_type, target);
}

static Stage1AirInst *ir_analyze_number_to_literal(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        Stage1AirInst *target, ZigType *wanted_type)
{
    ZigValue *val = ir_resolve_const(ira, target, UndefBad);
    if (!val)
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *result = ir_const(ira, scope, source_node, 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 Stage1AirInst *ir_analyze_int_to_err(IrAnalyze *ira, Scope *scope, AstNode *source_node, Stage1AirInst *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)) {
        ZigValue *val = ir_resolve_const(ira, target, UndefBad);
        if (!val)
            return ira->codegen->invalid_inst_gen;

        Stage1AirInst *result = ir_const(ira, scope, source_node, wanted_type);

        if (!resolve_inferred_error_set(ira->codegen, wanted_type, source_node)) {
            return ira->codegen->invalid_inst_gen;
        }

        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_node(ira, source_node,
                    buf_sprintf("integer value %s represents no error", buf_ptr(val_buf)));
                return ira->codegen->invalid_inst_gen;
            }

            size_t index = bigint_as_usize(&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_node(ira, source_node,
                    buf_sprintf("integer value %s represents no error in '%s'", buf_ptr(val_buf), buf_ptr(&wanted_type->name)));
                return ira->codegen->invalid_inst_gen;
            }

            result->value->data.x_err_set = err;
            return result;
        }
    }

    return ir_build_int_to_err_gen(ira, scope, source_node, target, wanted_type);
}

static Stage1AirInst *ir_analyze_err_to_int(IrAnalyze *ira, Scope *scope, AstNode *source_node, Stage1AirInst *target,
        ZigType *wanted_type)
{
    assert(wanted_type->id == ZigTypeIdInt);

    ZigType *err_type = target->value->type;

    if (instr_is_comptime(target)) {
        ZigValue *val = ir_resolve_const(ira, target, UndefBad);
        if (!val)
            return ira->codegen->invalid_inst_gen;

        Stage1AirInst *result = ir_const(ira, scope, source_node, 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_node,
                    buf_sprintf("error code '%s' does not fit in '%s'",
                        buf_ptr(&err->name), buf_ptr(&wanted_type->name)));
            return ira->codegen->invalid_inst_gen;
        }

        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_node)) {
            return ira->codegen->invalid_inst_gen;
        }
        if (err_set_type->data.error_set.err_count == 0) {
            Stage1AirInst *result = ir_const(ira, scope, source_node, wanted_type);
            bigint_init_unsigned(&result->value->data.x_bigint, 0);
            return result;
        } else if (err_set_type->data.error_set.err_count == 1) {
            Stage1AirInst *result = ir_const(ira, scope, source_node, 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_node,
                buf_sprintf("too many error values to fit in '%s'", buf_ptr(&wanted_type->name)));
        return ira->codegen->invalid_inst_gen;
    }

    return ir_build_err_to_int_gen(ira, scope, source_node, target, wanted_type);
}

static Stage1AirInst *ir_analyze_ptr_to_array(IrAnalyze *ira, Scope *scope, AstNode *source_node, Stage1AirInst *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_inst_gen;
    assert((wanted_type->data.pointer.is_const && target->value->type->data.pointer.is_const) || !target->value->type->data.pointer.is_const);
    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)) {
        ZigValue *val = ir_resolve_const(ira, target, UndefBad);
        if (!val)
            return ira->codegen->invalid_inst_gen;

        assert(val->type->id == ZigTypeIdPointer);
        ZigValue *pointee = const_ptr_pointee(ira, ira->codegen, val, source_node);
        if (pointee == nullptr)
            return ira->codegen->invalid_inst_gen;
        if (pointee->special != ConstValSpecialRuntime) {
            ZigValue *array_val = ira->codegen->pass1_arena->create<ZigValue>();
            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;

            Stage1AirInstConst *const_instruction = ir_create_inst_gen<Stage1AirInstConst>(&ira->new_irb,
                    scope, 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
    return ir_build_cast(ira, target->scope, target->source_node, wanted_type, target, CastOpBitCast);
}

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 ConstCastResultIdOptionalShape: {
            add_error_note(ira->codegen, parent_msg, source_node,
                    buf_sprintf("optional type child '%s' cannot cast into optional type '%s'",
                        buf_ptr(&cast_result->data.type_mismatch->actual_type->name),
                        buf_ptr(&cast_result->data.type_mismatch->wanted_type->name)));
            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, ast_field_to_symbol_node(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 ConstCastResultIdPtrLens: {
            add_error_note(ira->codegen, parent_msg, source_node,
                    buf_sprintf("pointer length mismatch"));
            break;
        }
        case ConstCastResultIdPtrSentinel: {
            ZigType *actual_type = cast_result->data.bad_ptr_sentinel->actual_type;
            ZigType *wanted_type = cast_result->data.bad_ptr_sentinel->wanted_type;
            {
                Buf *txt_msg = buf_sprintf("destination pointer requires a terminating '");
                render_const_value(ira->codegen, txt_msg, wanted_type->data.pointer.sentinel);
                buf_appendf(txt_msg, "' sentinel");
                if (actual_type->data.pointer.sentinel != nullptr) {
                    buf_appendf(txt_msg, ", but source pointer has a terminating '");
                    render_const_value(ira->codegen, txt_msg, actual_type->data.pointer.sentinel);
                    buf_appendf(txt_msg, "' sentinel");
                }
                add_error_note(ira->codegen, parent_msg, source_node, txt_msg);
            }
            break;
        }
        case ConstCastResultIdSentinelArrays: {
            ZigType *actual_type = cast_result->data.sentinel_arrays->actual_type;
            ZigType *wanted_type = cast_result->data.sentinel_arrays->wanted_type;
            Buf *txt_msg = buf_sprintf("destination array requires a terminating '");
            render_const_value(ira->codegen, txt_msg, wanted_type->data.array.sentinel);
            buf_appendf(txt_msg, "' sentinel");
            if (actual_type->data.array.sentinel != nullptr) {
                buf_appendf(txt_msg, ", but source array has a terminating '");
                render_const_value(ira->codegen, txt_msg, actual_type->data.array.sentinel);
                buf_appendf(txt_msg, "' sentinel");
            }
            add_error_note(ira->codegen, parent_msg, source_node, txt_msg);
            break;
        }
        case ConstCastResultIdCV: {
            ZigType *wanted_type = cast_result->data.bad_cv->wanted_type;
            ZigType *actual_type = cast_result->data.bad_cv->actual_type;
            bool ok_const = !actual_type->data.pointer.is_const || wanted_type->data.pointer.is_const;
            bool ok_volatile = !actual_type->data.pointer.is_volatile || wanted_type->data.pointer.is_volatile;
            if (!ok_const) {
                add_error_note(ira->codegen, parent_msg, source_node, buf_sprintf("cast discards const qualifier"));
            } else if (!ok_volatile) {
                add_error_note(ira->codegen, parent_msg, source_node, buf_sprintf("cast discards volatile qualifier"));
            } else {
                zig_unreachable();
            }
            break;
        }
        case ConstCastResultIdFnIsGeneric:
            add_error_note(ira->codegen, parent_msg, source_node,
                    buf_sprintf("only one of the functions is generic"));
            break;
        case ConstCastResultIdFnCC:
            add_error_note(ira->codegen, parent_msg, source_node,
                    buf_sprintf("calling convention mismatch"));
            break;
        case ConstCastResultIdIntShorten: {
            ZigType *wanted_type = cast_result->data.int_shorten->wanted_type;
            ZigType *actual_type = cast_result->data.int_shorten->actual_type;
            const char *wanted_signed = wanted_type->data.integral.is_signed ? "signed" : "unsigned";
            const char *actual_signed = actual_type->data.integral.is_signed ? "signed" : "unsigned";
            add_error_note(ira->codegen, parent_msg, source_node,
                buf_sprintf("%s %" PRIu32 "-bit int cannot represent all possible %s %" PRIu32 "-bit values",
                    wanted_signed, wanted_type->data.integral.bit_count,
                    actual_signed, actual_type->data.integral.bit_count));
            break;
        }
        case ConstCastResultIdVectorLength: // TODO
        case ConstCastResultIdVectorChild: // TODO
        case ConstCastResultIdFnAlign: // TODO
        case ConstCastResultIdFnVarArgs: // TODO
        case ConstCastResultIdFnReturnType: // TODO
        case ConstCastResultIdFnArgCount: // TODO
        case ConstCastResultIdFnGenericArgCount: // TODO
        case ConstCastResultIdFnArgNoAlias: // TODO
        case ConstCastResultIdUnresolvedInferredErrSet: // TODO
        case ConstCastResultIdAsyncAllocatorType: // TODO
        case ConstCastResultIdArrayChild: // TODO
            break;
    }
}

static Stage1AirInst *ir_analyze_array_to_vector(IrAnalyze *ira, Scope *scope, AstNode *source_node,
    Stage1AirInst *array, ZigType *vector_type)
{
    if (instr_is_comptime(array)) {
        // arrays and vectors have the same ZigValue representation
        Stage1AirInst *result = ir_const(ira, scope, source_node, vector_type);
        copy_const_val(ira->codegen, result->value, array->value);
        result->value->type = vector_type;
        return result;
    }
    return ir_build_array_to_vector(ira, scope, source_node, array, vector_type);
}

static Stage1AirInst *ir_analyze_vector_to_array(IrAnalyze *ira, Scope *scope, AstNode *source_node,
    Stage1AirInst *vector, ZigType *array_type, ResultLoc *result_loc)
{
    if (instr_is_comptime(vector)) {
        // arrays and vectors have the same ZigValue representation
        Stage1AirInst *result = ir_const(ira, scope, source_node, array_type);
        copy_const_val(ira->codegen, result->value, vector->value);
        result->value->type = array_type;
        return result;
    }
    if (result_loc == nullptr) {
        result_loc = no_result_loc();
    }
    Stage1AirInst *result_loc_inst = ir_resolve_result(ira, ira->suspend_source_instr, result_loc, array_type, nullptr, true, true);
    if (type_is_invalid(result_loc_inst->value->type) || result_loc_inst->value->type->id == ZigTypeIdUnreachable) {
        return result_loc_inst;
    }
    return ir_build_vector_to_array(ira, scope, source_node, array_type, vector, result_loc_inst);
}

static Stage1AirInst *ir_analyze_int_to_c_ptr(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        Stage1AirInst *integer, ZigType *dest_type)
{
    Stage1AirInst *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_node(ira, source_node,
                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_inst_gen;
        }

        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, scope, source_node, integer, unsigned_int_type);
            if (type_is_invalid(unsigned_integer->value->type))
                return ira->codegen->invalid_inst_gen;
        } else {
            unsigned_integer = integer;
        }
    }

    return ir_analyze_int_to_ptr(ira, scope, source_node, unsigned_integer, dest_type);
}

static bool is_pointery_and_elem_is_not_pointery(ZigType *ty) {
    if (ty->id == ZigTypeIdPointer) return ty->data.pointer.child_type->id != ZigTypeIdPointer;
    if (ty->id == ZigTypeIdFn) return true;
    if (ty->id == ZigTypeIdOptional) {
        ZigType *ptr_ty = ty->data.maybe.child_type;
        if (ptr_ty->id == ZigTypeIdPointer) return ptr_ty->data.pointer.child_type->id != ZigTypeIdPointer;
        if (ptr_ty->id == ZigTypeIdFn) return true;
    }
    return false;
}

static Stage1AirInst *ir_analyze_enum_literal(IrAnalyze *ira, Scope *scope, AstNode *source_node, Stage1AirInst *value,
        ZigType *enum_type)
{
    assert(enum_type->id == ZigTypeIdEnum);

    Error err;
    if ((err = type_resolve(ira->codegen, enum_type, ResolveStatusZeroBitsKnown)))
        return ira->codegen->invalid_inst_gen;

    TypeEnumField *field = find_enum_type_field(enum_type, value->value->data.x_enum_literal);
    if (field == nullptr) {
        ErrorMsg *msg = ir_add_error_node(ira, source_node, buf_sprintf("enum '%s' has no field named '%s'",
                buf_ptr(&enum_type->name), buf_ptr(value->value->data.x_enum_literal)));
        add_error_note(ira->codegen, msg, enum_type->data.enumeration.decl_node,
                buf_sprintf("'%s' declared here", buf_ptr(&enum_type->name)));
        return ira->codegen->invalid_inst_gen;
    }
    Stage1AirInst *result = ir_const(ira, scope, source_node, enum_type);
    bigint_init_bigint(&result->value->data.x_enum_tag, &field->value);

    return result;
}

static Stage1AirInst *ir_analyze_struct_literal_to_array(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        Stage1AirInst *struct_ptr, ZigType *actual_type, ZigType *wanted_type)
{
    Error err;

    if ((err = type_resolve(ira->codegen, wanted_type, ResolveStatusSizeKnown)))
        return ira->codegen->invalid_inst_gen;

    size_t array_len = wanted_type->data.array.len;
    size_t instr_field_count = actual_type->data.structure.src_field_count;
    assert(array_len == instr_field_count);

    bool need_comptime = ir_should_inline(ira->zir, scope)
        || type_requires_comptime(ira->codegen, wanted_type) == ReqCompTimeYes;
    bool is_comptime = true;

    ZigType *elem_type = wanted_type->data.array.child_type;

    // Determine if the struct_operand will be comptime.
    ZigValue *elem_values = heap::c_allocator.allocate<ZigValue>(array_len);
    Stage1AirInst **casted_fields = heap::c_allocator.allocate<Stage1AirInst *>(array_len);
    Stage1AirInst *const_result = ir_const(ira, scope, source_node, wanted_type);

    for (size_t i = 0; i < array_len; i += 1) {
        TypeStructField *src_field = actual_type->data.structure.fields[i];

        Stage1AirInst *field_ptr = ir_analyze_struct_field_ptr(ira, scope, source_node, src_field, struct_ptr,
                actual_type, false);
        if (type_is_invalid(field_ptr->value->type))
            return ira->codegen->invalid_inst_gen;
        Stage1AirInst *field_value = ir_get_deref(ira, scope, source_node, field_ptr, nullptr);
        if (type_is_invalid(field_value->value->type))
            return ira->codegen->invalid_inst_gen;
        Stage1AirInst *casted_value = ir_implicit_cast(ira, field_value, elem_type);
        if (type_is_invalid(casted_value->value->type))
            return ira->codegen->invalid_inst_gen;

        casted_fields[i] = casted_value;
        if (need_comptime || instr_is_comptime(casted_value)) {
            ZigValue *field_val = ir_resolve_const(ira, casted_value, UndefOk);
            if (field_val == nullptr)
                return ira->codegen->invalid_inst_gen;

            field_val->parent.id = ConstParentIdArray;
            field_val->parent.data.p_array.array_val = const_result->value;
            field_val->parent.data.p_array.elem_index = i;
            elem_values[i] = *field_val;
            if (field_val->type->id == ZigTypeIdUndefined) {
                elem_values[i].special = ConstValSpecialUndef;
            }
        } else {
            is_comptime = false;
        }
    }

    if (is_comptime) {
        Stage1AirInst *const_result = ir_const(ira, scope, source_node, wanted_type);
        const_result->value->data.x_array.special = ConstArraySpecialNone;
        const_result->value->data.x_array.data.s_none.elements = elem_values;
        return const_result;
    }

    Stage1AirInst *result_loc_inst = ir_resolve_result(ira, ira->suspend_source_instr, no_result_loc(),
        wanted_type, nullptr, true, true);
    if (type_is_invalid(result_loc_inst->value->type) || result_loc_inst->value->type->id == ZigTypeIdUnreachable) {
        return ira->codegen->invalid_inst_gen;
    }

    ZigType *elem_type_ptr = get_pointer_to_type(ira->codegen, elem_type, false);
    for (size_t i = 0; i < array_len; i += 1) {
        Stage1AirInst *index_val = ir_const(ira, scope, source_node, ira->codegen->builtin_types.entry_usize);
        bigint_init_unsigned(&index_val->value->data.x_bigint, i);

        Stage1AirInst *elem_ptr = ir_build_elem_ptr_gen(ira, scope, source_node,
            result_loc_inst, index_val, false, elem_type_ptr);
        Stage1AirInst *store_ptr_inst = ir_analyze_store_ptr(ira, scope, source_node, elem_ptr, casted_fields[i], true);
        if (type_is_invalid(store_ptr_inst->value->type))
            return ira->codegen->invalid_inst_gen;
    }

    heap::c_allocator.deallocate(elem_values, array_len);
    heap::c_allocator.deallocate(casted_fields, array_len);

    return result_loc_inst;
}

static Stage1AirInst *ir_analyze_struct_literal_to_struct(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        Stage1AirInst *struct_ptr, ZigType *actual_type, ZigType *wanted_type)
{
    Error err;

    if (wanted_type->data.structure.resolve_status == ResolveStatusBeingInferred) {
        ir_add_error_node(ira, source_node, buf_sprintf("type coercion of anon struct literal to inferred struct"));
        return ira->codegen->invalid_inst_gen;
    }

    if ((err = type_resolve(ira->codegen, wanted_type, ResolveStatusSizeKnown)))
        return ira->codegen->invalid_inst_gen;

    size_t actual_field_count = wanted_type->data.structure.src_field_count;
    size_t instr_field_count = actual_type->data.structure.src_field_count;

    bool need_comptime = ir_should_inline(ira->zir, scope)
        || type_requires_comptime(ira->codegen, wanted_type) == ReqCompTimeYes;
    bool is_comptime = true;

    // Determine if the struct_operand will be comptime.
    // Also emit compile errors for missing fields and duplicate fields.
    AstNode **field_assign_nodes = heap::c_allocator.allocate<AstNode *>(actual_field_count);
    ZigValue **field_values = heap::c_allocator.allocate<ZigValue *>(actual_field_count);
    Stage1AirInst **casted_fields = heap::c_allocator.allocate<Stage1AirInst *>(actual_field_count);
    Stage1AirInst *const_result = ir_const(ira, scope, source_node, wanted_type);

    for (size_t i = 0; i < instr_field_count; i += 1) {
        TypeStructField *src_field = actual_type->data.structure.fields[i];
        TypeStructField *dst_field = find_struct_type_field(wanted_type, src_field->name);
        if (dst_field == nullptr) {
            ErrorMsg *msg = ir_add_error_node(ira, source_node, buf_sprintf("no field named '%s' in struct '%s'",
                    buf_ptr(src_field->name), buf_ptr(&wanted_type->name)));
            if (wanted_type->data.structure.decl_node) {
                add_error_note(ira->codegen, msg, wanted_type->data.structure.decl_node,
                    buf_sprintf("struct '%s' declared here", buf_ptr(&wanted_type->name)));
            }
            add_error_note(ira->codegen, msg, src_field->decl_node,
                buf_sprintf("field '%s' declared here", buf_ptr(src_field->name)));
            return ira->codegen->invalid_inst_gen;
        }
        if (dst_field->is_comptime) {
            ErrorMsg *msg = ir_add_error_node(ira, source_node, buf_sprintf("field '%s' in struct '%s' is comptime, it cannot be assigned",
                    buf_ptr(src_field->name), buf_ptr(&wanted_type->name)));
            if (wanted_type->data.structure.decl_node) {
                add_error_note(ira->codegen, msg, wanted_type->data.structure.decl_node,
                    buf_sprintf("struct '%s' declared here", buf_ptr(&wanted_type->name)));
            }
            add_error_note(ira->codegen, msg, src_field->decl_node,
                buf_sprintf("field '%s' declared here", buf_ptr(src_field->name)));
            return ira->codegen->invalid_inst_gen;
        }

        src_assert(src_field->decl_node != nullptr, source_node);
        AstNode *existing_assign_node = field_assign_nodes[dst_field->src_index];
        if (existing_assign_node != nullptr) {
            ErrorMsg *msg = ir_add_error_node(ira, source_node, buf_sprintf("duplicate field"));
            add_error_note(ira->codegen, msg, existing_assign_node, buf_sprintf("other field here"));
            return ira->codegen->invalid_inst_gen;
        }
        field_assign_nodes[dst_field->src_index] = src_field->decl_node;

        Stage1AirInst *field_ptr = ir_analyze_struct_field_ptr(ira, scope, source_node, src_field, struct_ptr,
                actual_type, false);
        if (type_is_invalid(field_ptr->value->type))
            return ira->codegen->invalid_inst_gen;
        Stage1AirInst *field_value = ir_get_deref(ira, scope, source_node, field_ptr, nullptr);
        if (type_is_invalid(field_value->value->type))
            return ira->codegen->invalid_inst_gen;
        Stage1AirInst *casted_value = ir_implicit_cast(ira, field_value, dst_field->type_entry);
        if (type_is_invalid(casted_value->value->type))
            return ira->codegen->invalid_inst_gen;

        casted_fields[dst_field->src_index] = casted_value;
        if (need_comptime || instr_is_comptime(casted_value)) {
            ZigValue *field_val = ir_resolve_const(ira, casted_value, UndefOk);
            if (field_val == nullptr)
                return ira->codegen->invalid_inst_gen;
            field_val->parent.id = ConstParentIdStruct;
            field_val->parent.data.p_struct.struct_val = const_result->value;
            field_val->parent.data.p_struct.field_index = dst_field->src_index;
            field_values[dst_field->src_index] = field_val;
            if (field_val->type->id == ZigTypeIdUndefined && dst_field->type_entry->id != ZigTypeIdUndefined) {
                field_values[dst_field->src_index]->special = ConstValSpecialUndef;
            }
        } else {
            is_comptime = false;
        }
    }

    bool any_missing = false;
    for (size_t i = 0; i < actual_field_count; i += 1) {
        if (field_assign_nodes[i] != nullptr) continue;

        // look for a default field value
        TypeStructField *field = wanted_type->data.structure.fields[i];
        assert(!field->is_comptime); // field_assign_nodes[i] should be null for comptime fields
        memoize_field_init_val(ira->codegen, wanted_type, field);
        if (field->init_val == nullptr) {
            ir_add_error_node(ira, source_node,
                buf_sprintf("missing field: '%s'", buf_ptr(field->name)));
            any_missing = true;
            continue;
        }
        if (type_is_invalid(field->init_val->type))
            return ira->codegen->invalid_inst_gen;
        ZigValue *init_val_copy = ira->codegen->pass1_arena->create<ZigValue>();
        copy_const_val(ira->codegen, init_val_copy, field->init_val);
        init_val_copy->parent.id = ConstParentIdStruct;
        init_val_copy->parent.data.p_struct.struct_val = const_result->value;
        init_val_copy->parent.data.p_struct.field_index = i;
        field_values[i] = init_val_copy;
        casted_fields[i] = ir_const_move(ira, scope, source_node, init_val_copy);
    }
    if (any_missing)
        return ira->codegen->invalid_inst_gen;

    if (is_comptime) {
        heap::c_allocator.deallocate(field_assign_nodes, actual_field_count);
        Stage1AirInst *const_result = ir_const(ira, scope, source_node, wanted_type);
        const_result->value->data.x_struct.fields = field_values;
        return const_result;
    }

    Stage1AirInst *result_loc_inst = ir_resolve_result(ira, ira->suspend_source_instr, no_result_loc(),
        wanted_type, nullptr, true, true);
    if (type_is_invalid(result_loc_inst->value->type) || result_loc_inst->value->type->id == ZigTypeIdUnreachable) {
        return ira->codegen->invalid_inst_gen;
    }

    for (size_t i = 0; i < actual_field_count; i += 1) {
        TypeStructField *field = wanted_type->data.structure.fields[i];
        if (field->is_comptime)
            continue;

        Stage1AirInst *field_ptr = ir_analyze_struct_field_ptr(ira, scope, source_node, field, result_loc_inst, wanted_type, true);
        if (type_is_invalid(field_ptr->value->type))
            return ira->codegen->invalid_inst_gen;
        Stage1AirInst *store_ptr_inst = ir_analyze_store_ptr(ira, scope, source_node, field_ptr, casted_fields[i], true);
        if (type_is_invalid(store_ptr_inst->value->type))
            return ira->codegen->invalid_inst_gen;
    }

    heap::c_allocator.deallocate(field_assign_nodes, actual_field_count);
    heap::c_allocator.deallocate(field_values, actual_field_count);
    heap::c_allocator.deallocate(casted_fields, actual_field_count);

    return result_loc_inst;
}

static Stage1AirInst *ir_analyze_struct_literal_to_union(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        Stage1AirInst *struct_ptr, ZigType *struct_type, ZigType *union_type)
{
    Error err;

    assert(struct_type->id == ZigTypeIdStruct);
    assert(union_type->id == ZigTypeIdUnion);
    assert(struct_type->data.structure.src_field_count == 1);

    TypeStructField *only_field = struct_type->data.structure.fields[0];

    if ((err = type_resolve(ira->codegen, union_type, ResolveStatusZeroBitsKnown)))
        return ira->codegen->invalid_inst_gen;

    TypeUnionField *union_field = find_union_type_field(union_type, only_field->name);
    if (union_field == nullptr) {
        ir_add_error_node(ira, only_field->decl_node,
            buf_sprintf("no field named '%s' in union '%s'",
                buf_ptr(only_field->name), buf_ptr(&union_type->name)));
        return ira->codegen->invalid_inst_gen;
    }

    ZigType *payload_type = resolve_union_field_type(ira->codegen, union_field);
    if (payload_type == nullptr)
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *field_ptr = ir_analyze_struct_field_ptr(ira, scope, source_node, only_field, struct_ptr,
            struct_type, false);
    if (type_is_invalid(field_ptr->value->type))
        return ira->codegen->invalid_inst_gen;
    Stage1AirInst *field_value =  ir_get_deref(ira, scope, source_node, field_ptr, nullptr);
    if (type_is_invalid(field_value->value->type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *casted_value = ir_implicit_cast(ira, field_value, payload_type);
    if (type_is_invalid(casted_value->value->type))
        return ira->codegen->invalid_inst_gen;

    if (instr_is_comptime(casted_value)) {
        ZigValue *val = ir_resolve_const(ira, casted_value, UndefBad);
        if (val == nullptr)
            return ira->codegen->invalid_inst_gen;

        Stage1AirInst *result = ir_const(ira, scope, source_node, union_type);
        bigint_init_bigint(&result->value->data.x_union.tag, &union_field->enum_field->value);
        result->value->data.x_union.payload = val;

        val->parent.id = ConstParentIdUnion;
        val->parent.data.p_union.union_val = result->value;

        return result;
    }

    Stage1AirInst *result_loc_inst = ir_resolve_result(ira, ira->suspend_source_instr, no_result_loc(),
        union_type, nullptr, true, true);
    if (type_is_invalid(result_loc_inst->value->type) || result_loc_inst->value->type->id == ZigTypeIdUnreachable) {
        return ira->codegen->invalid_inst_gen;
    }

    Stage1AirInst *payload_ptr = ir_analyze_container_field_ptr(ira, only_field->name,
            scope, source_node, result_loc_inst, source_node, union_type, true);
    if (type_is_invalid(payload_ptr->value->type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *store_ptr_inst = ir_analyze_store_ptr(ira, scope, source_node, payload_ptr, casted_value, false);
    if (type_is_invalid(store_ptr_inst->value->type))
        return ira->codegen->invalid_inst_gen;

    return result_loc_inst;
}

// Add a compile error and return ErrorSemanticAnalyzeFail if the pointer alignment does not work,
// otherwise return ErrorNone. Does not emit any instructions.
// Assumes that the pointer types have element types with the same ABI alignment. Avoids resolving the
// pointer types' alignments if both of the pointer types are ABI aligned.
static Error ir_cast_ptr_align(IrAnalyze *ira, Scope *scope, AstNode *source_node, ZigType *dest_ptr_type,
        ZigType *src_ptr_type, AstNode *src_source_node)
{
    Error err;

    src_assert(dest_ptr_type->id == ZigTypeIdPointer, source_node);
    src_assert(src_ptr_type->id == ZigTypeIdPointer, source_node);

    if (dest_ptr_type->data.pointer.explicit_alignment == 0 &&
        src_ptr_type->data.pointer.explicit_alignment == 0)
    {
        return ErrorNone;
    }

    if ((err = type_resolve(ira->codegen, dest_ptr_type->data.pointer.child_type, ResolveStatusAlignmentKnown)))
        return ErrorSemanticAnalyzeFail;

    if ((err = type_resolve(ira->codegen, src_ptr_type->data.pointer.child_type, ResolveStatusAlignmentKnown)))
        return ErrorSemanticAnalyzeFail;

    uint32_t wanted_align = get_ptr_align(ira->codegen, dest_ptr_type);
    uint32_t actual_align = get_ptr_align(ira->codegen, src_ptr_type);
    if (wanted_align > actual_align) {
        ErrorMsg *msg = ir_add_error_node(ira, source_node, buf_sprintf("cast increases pointer alignment"));
        add_error_note(ira->codegen, msg, src_source_node,
                buf_sprintf("'%s' has alignment %" PRIu32, buf_ptr(&src_ptr_type->name), actual_align));
        add_error_note(ira->codegen, msg, source_node,
                buf_sprintf("'%s' has alignment %" PRIu32, buf_ptr(&dest_ptr_type->name), wanted_align));
        return ErrorSemanticAnalyzeFail;
    }

    return ErrorNone;
}

static Stage1AirInst *ir_analyze_struct_value_field_value(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        Stage1AirInst *struct_operand, TypeStructField *field)
{
    Stage1AirInst *struct_ptr = ir_get_ref(ira, scope, source_node, struct_operand, true, false);
    if (type_is_invalid(struct_ptr->value->type))
        return ira->codegen->invalid_inst_gen;
    Stage1AirInst *field_ptr = ir_analyze_struct_field_ptr(ira, scope, source_node, field, struct_ptr,
            struct_operand->value->type, false);
    if (type_is_invalid(field_ptr->value->type))
        return ira->codegen->invalid_inst_gen;
    return ir_get_deref(ira, scope, source_node, field_ptr, nullptr);
}

static Stage1AirInst *ir_analyze_optional_value_payload_value(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        Stage1AirInst *optional_operand, bool safety_check_on)
{
    Stage1AirInst *opt_ptr = ir_get_ref(ira, scope, source_node, optional_operand, true, false);
    Stage1AirInst *payload_ptr = ir_analyze_unwrap_optional_payload(ira, scope, source_node, opt_ptr,
            safety_check_on, false);
    return ir_get_deref(ira, scope, source_node, payload_ptr, nullptr);
}

static Stage1AirInst *ir_analyze_cast(IrAnalyze *ira, Scope *scope, AstNode *source_node,
    ZigType *wanted_type, Stage1AirInst *value)
{
    Error err;
    ZigType *actual_type = value->value->type;

    if (type_is_invalid(wanted_type) || type_is_invalid(actual_type)) {
        return ira->codegen->invalid_inst_gen;
    }

    // This means the wanted type is anything.
    if (wanted_type == ira->codegen->builtin_types.entry_anytype) {
        return value;
    }

    // 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_inst_gen;
    if (const_cast_result.id == ConstCastResultIdOk) {
        return ir_resolve_cast(ira, scope, source_node, value, wanted_type, CastOpNoop);
    }

    if (const_cast_result.id == ConstCastResultIdFnCC) {
        src_assert(value->value->type->id == ZigTypeIdFn, source_node);
        // ConstCastResultIdFnCC is guaranteed to be the last one reported, meaning everything else is ok.
        if (wanted_type->data.fn.fn_type_id.cc == CallingConventionAsync &&
            actual_type->data.fn.fn_type_id.cc == CallingConventionUnspecified)
        {
            src_assert(value->value->data.x_ptr.special == ConstPtrSpecialFunction, source_node);
            ZigFn *fn = value->value->data.x_ptr.data.fn.fn_entry;
            if (fn->inferred_async_node == nullptr) {
                fn->inferred_async_node = source_node;
            }
            return ir_resolve_cast(ira, scope, source_node, value, wanted_type, CastOpNoop);
        }
    }

    // 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, scope, source_node, value, wanted_type, nullptr);
        } 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, scope, source_node, value, wanted_type, nullptr);
            } else {
                return ira->codegen->invalid_inst_gen;
            }
        } 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_inst_gen;
            if ((err = type_resolve(ira->codegen, wanted_child_type->data.pointer.child_type, ResolveStatusAlignmentKnown)))
                return ira->codegen->invalid_inst_gen;
            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)
            {
                Stage1AirInst *cast1 = ir_resolve_ptr_of_array_to_unknown_len_ptr(ira, scope, source_node, value,
                        wanted_child_type);
                if (type_is_invalid(cast1->value->type))
                    return ira->codegen->invalid_inst_gen;
                return ir_analyze_optional_wrap(ira, scope, source_node, cast1, wanted_type, nullptr);
            }
        }
    }

    // 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, scope, source_node, value, wanted_type, nullptr);
        } 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, scope, source_node, value, wanted_type, nullptr);
            } else {
                return ira->codegen->invalid_inst_gen;
            }
        }
    }

    // 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)
        {
            Stage1AirInst *cast1 = ir_analyze_cast(ira, scope, source_node, wanted_type->data.error_union.payload_type, value);
            if (type_is_invalid(cast1->value->type))
                return ira->codegen->invalid_inst_gen;

            Stage1AirInst *cast2 = ir_analyze_cast(ira, scope, source_node, wanted_type, cast1);
            if (type_is_invalid(cast2->value->type))
                return ira->codegen->invalid_inst_gen;

            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) {
            Stage1AirInst *result = ir_const(ira, scope, source_node, 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) {
                Stage1AirInst *result = ir_const(ira, scope, source_node, wanted_type);
                if (actual_type->id == ZigTypeIdComptimeInt || actual_type->id == ZigTypeIdInt) {
                    copy_const_val(ira->codegen, result->value, value->value);
                    result->value->type = wanted_type;
                } else {
                    float_init_bigint(&result->value->data.x_bigint, value->value);
                }
                return result;
            } else if (wanted_type->id == ZigTypeIdComptimeFloat || wanted_type->id == ZigTypeIdFloat) {
                Stage1AirInst *result = ir_const(ira, scope, source_node, 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_inst_gen;
        }
    }

    // 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, scope, source_node, 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, scope, source_node, 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, scope, source_node, value, wanted_type);
    }

    // *[N]T to ?[]T
    if (wanted_type->id == ZigTypeIdOptional &&
        is_slice(wanted_type->data.maybe.child_type) &&
        actual_type->id == ZigTypeIdPointer &&
        actual_type->data.pointer.ptr_len == PtrLenSingle &&
        actual_type->data.pointer.child_type->id == ZigTypeIdArray)
    {
        Stage1AirInst *cast1 = ir_analyze_cast(ira, scope, source_node, wanted_type->data.maybe.child_type, value);
        if (type_is_invalid(cast1->value->type))
            return ira->codegen->invalid_inst_gen;

        Stage1AirInst *cast2 = ir_analyze_cast(ira, scope, source_node, wanted_type, cast1);
        if (type_is_invalid(cast2->value->type))
            return ira->codegen->invalid_inst_gen;

        return cast2;
    }

    // *[N]T to [*]T and [*c]T
    if (wanted_type->id == ZigTypeIdPointer &&
        (wanted_type->data.pointer.ptr_len == PtrLenUnknown || wanted_type->data.pointer.ptr_len == PtrLenC) &&
        actual_type->id == ZigTypeIdPointer &&
        actual_type->data.pointer.ptr_len == PtrLenSingle &&
        actual_type->data.pointer.child_type->id == ZigTypeIdArray &&
        (!actual_type->data.pointer.is_const || wanted_type->data.pointer.is_const) &&
        (!actual_type->data.pointer.is_volatile || wanted_type->data.pointer.is_volatile))
    {
        ZigType *actual_array_type = actual_type->data.pointer.child_type;
        if (wanted_type->data.pointer.sentinel == nullptr ||
            (actual_array_type->data.array.sentinel != nullptr &&
             const_values_equal(ira->codegen, wanted_type->data.pointer.sentinel,
                 actual_array_type->data.array.sentinel)))
        {
            if ((err = type_resolve(ira->codegen, actual_type->data.pointer.child_type, ResolveStatusAlignmentKnown)))
                return ira->codegen->invalid_inst_gen;
            if ((err = type_resolve(ira->codegen, wanted_type->data.pointer.child_type, ResolveStatusAlignmentKnown)))
                return ira->codegen->invalid_inst_gen;
            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, scope, source_node, value, wanted_type);
            }
        }
    }

    // *[N]T to []T
    // *[N]T to E![]T
    if ((is_slice(wanted_type) ||
            (wanted_type->id == ZigTypeIdErrorUnion &&
            is_slice(wanted_type->data.error_union.payload_type))) &&
        actual_type->id == ZigTypeIdPointer &&
        actual_type->data.pointer.ptr_len == PtrLenSingle &&
        actual_type->data.pointer.child_type->id == ZigTypeIdArray)
    {
        ZigType *slice_type = (wanted_type->id == ZigTypeIdErrorUnion) ?
            wanted_type->data.error_union.payload_type : wanted_type;
        ZigType *slice_ptr_type = slice_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 &&
            (slice_ptr_type->data.pointer.sentinel == nullptr ||
             (array_type->data.array.sentinel != nullptr &&
              const_values_equal(ira->codegen, array_type->data.array.sentinel,
                  slice_ptr_type->data.pointer.sentinel))))
        {
            // If the pointers both have ABI align, it works.
            // Or if the array length is 0, alignment doesn't matter.
            bool ok_align = array_type->data.array.len == 0 ||
                (slice_ptr_type->data.pointer.explicit_alignment == 0 &&
                actual_type->data.pointer.explicit_alignment == 0);
            if (!ok_align) {
                // If either one has non ABI align, we have to resolve them both
                if ((err = type_resolve(ira->codegen, actual_type->data.pointer.child_type,
                                ResolveStatusAlignmentKnown)))
                {
                    return ira->codegen->invalid_inst_gen;
                }
                if ((err = type_resolve(ira->codegen, slice_ptr_type->data.pointer.child_type,
                                ResolveStatusAlignmentKnown)))
                {
                    return ira->codegen->invalid_inst_gen;
                }
                ok_align = get_ptr_align(ira->codegen, actual_type) >= get_ptr_align(ira->codegen, slice_ptr_type);
            }
            if (ok_align) {
                if (wanted_type->id == ZigTypeIdErrorUnion) {
                    Stage1AirInst *cast1 = ir_analyze_cast(ira, scope, source_node, slice_type, value);
                    if (type_is_invalid(cast1->value->type))
                        return ira->codegen->invalid_inst_gen;

                    Stage1AirInst *cast2 = ir_analyze_cast(ira, scope, source_node, wanted_type, cast1);
                    if (type_is_invalid(cast2->value->type))
                        return ira->codegen->invalid_inst_gen;

                    return cast2;
                } else {
                    return ir_resolve_ptr_of_array_to_slice(ira, scope, source_node, value, slice_type, nullptr);
                }
            }
        }
    }

    // @Vector(N,T1) to @Vector(N,T2)
    if (actual_type->id == ZigTypeIdVector && wanted_type->id == ZigTypeIdVector &&
            actual_type->data.vector.len == wanted_type->data.vector.len)
    {
        ZigType *scalar_actual_type = actual_type->data.vector.elem_type;
        ZigType *scalar_wanted_type = wanted_type->data.vector.elem_type;

        // widening conversion
        if (scalar_wanted_type->id == ZigTypeIdInt &&
            scalar_actual_type->id == ZigTypeIdInt &&
            scalar_wanted_type->data.integral.is_signed == scalar_actual_type->data.integral.is_signed &&
            scalar_wanted_type->data.integral.bit_count >= scalar_actual_type->data.integral.bit_count)
        {
            return ir_analyze_widen_or_shorten(ira, scope, source_node, value, wanted_type);
        }

        // small enough unsigned ints can get casted to large enough signed ints
        if (scalar_wanted_type->id == ZigTypeIdInt && scalar_wanted_type->data.integral.is_signed &&
            scalar_actual_type->id == ZigTypeIdInt && !scalar_actual_type->data.integral.is_signed &&
            scalar_wanted_type->data.integral.bit_count > scalar_actual_type->data.integral.bit_count)
        {
            return ir_analyze_widen_or_shorten(ira, scope, source_node, value, wanted_type);
        }

        // float widening conversion
        if (scalar_wanted_type->id == ZigTypeIdFloat &&
            scalar_actual_type->id == ZigTypeIdFloat &&
            scalar_wanted_type->data.floating.bit_count >= scalar_actual_type->data.floating.bit_count)
        {
            return ir_analyze_widen_or_shorten(ira, scope, source_node, value, wanted_type);
        }
    }

    // *@Frame(func) to anyframe->T or anyframe
    // *@Frame(func) to ?anyframe->T or ?anyframe
    // *@Frame(func) to E!anyframe->T or E!anyframe
    if (actual_type->id == ZigTypeIdPointer && actual_type->data.pointer.ptr_len == PtrLenSingle &&
        !actual_type->data.pointer.is_const &&
        actual_type->data.pointer.child_type->id == ZigTypeIdFnFrame)
    {
        ZigType *anyframe_type;
        if (wanted_type->id == ZigTypeIdAnyFrame) {
            anyframe_type = wanted_type;
        } else if (wanted_type->id == ZigTypeIdOptional &&
                wanted_type->data.maybe.child_type->id == ZigTypeIdAnyFrame)
        {
            anyframe_type = wanted_type->data.maybe.child_type;
        } else if (wanted_type->id == ZigTypeIdErrorUnion &&
                wanted_type->data.error_union.payload_type->id == ZigTypeIdAnyFrame)
        {
            anyframe_type = wanted_type->data.error_union.payload_type;
        } else {
            anyframe_type = nullptr;
        }
        if (anyframe_type != nullptr) {
            bool ok = true;
            if (anyframe_type->data.any_frame.result_type != nullptr) {
                ZigFn *fn = actual_type->data.pointer.child_type->data.frame.fn;
                ZigType *fn_return_type = fn->type_entry->data.fn.fn_type_id.return_type;
                if (anyframe_type->data.any_frame.result_type != fn_return_type) {
                    ok = false;
                }
            }
            if (ok) {
                Stage1AirInst *cast1 = ir_analyze_frame_ptr_to_anyframe(ira, scope, source_node, value, anyframe_type);
                if (anyframe_type == wanted_type)
                    return cast1;
                return ir_analyze_cast(ira, scope, source_node, wanted_type, cast1);
            }
        }
    }

    // anyframe->T to anyframe
    if (actual_type->id == ZigTypeIdAnyFrame && actual_type->data.any_frame.result_type != nullptr &&
        wanted_type->id == ZigTypeIdAnyFrame && wanted_type->data.any_frame.result_type == nullptr)
    {
        return ir_analyze_anyframe_to_anyframe(ira, scope, source_node, 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, scope, source_node, value, wanted_type);
    }

    // cast from null literal to C pointer
    if (wanted_type->id == ZigTypeIdPointer && wanted_type->data.pointer.ptr_len == PtrLenC &&
        actual_type->id == ZigTypeIdNull)
    {
        return ir_analyze_null_to_c_pointer(ira, scope, source_node, value, wanted_type);
    }

    // cast from E to E!T
    if (wanted_type->id == ZigTypeIdErrorUnion &&
        actual_type->id == ZigTypeIdErrorSet)
    {
        return ir_analyze_err_wrap_code(ira, scope, source_node, value, wanted_type, nullptr);
    }

    // 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, scope, source_node, value, wanted_type);
    }

    // cast from enum literal to enum with matching field name
    if (actual_type->id == ZigTypeIdEnumLiteral && wanted_type->id == ZigTypeIdEnum)
    {
        return ir_analyze_enum_literal(ira, scope, source_node, value, wanted_type);
    }

    // cast from enum literal to optional enum
    if (actual_type->id == ZigTypeIdEnumLiteral &&
        (wanted_type->id == ZigTypeIdOptional && wanted_type->data.maybe.child_type->id == ZigTypeIdEnum))
    {
        Stage1AirInst *result = ir_analyze_enum_literal(ira, scope, source_node, value, wanted_type->data.maybe.child_type);
        if (type_is_invalid(result->value->type))
            return result;

        return ir_analyze_optional_wrap(ira, scope, source_node, value, wanted_type, nullptr);
    }

    // cast from enum literal to error union when payload is an enum
    if (actual_type->id == ZigTypeIdEnumLiteral &&
        (wanted_type->id == ZigTypeIdErrorUnion && wanted_type->data.error_union.payload_type->id == ZigTypeIdEnum))
    {
        Stage1AirInst *result = ir_analyze_enum_literal(ira, scope, source_node, value, wanted_type->data.error_union.payload_type);
        if (type_is_invalid(result->value->type))
            return result;

        return ir_analyze_err_wrap_payload(ira, scope, source_node, value, wanted_type, nullptr);
    }

    // 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_inst_gen;

        if (actual_type->data.unionation.tag_type == wanted_type) {
            return ir_analyze_union_to_tag(ira, scope, source_node, value, wanted_type);
        }
    }

    // enum to union which has the enum as the tag type, or
    // enum literal to union which has a matching enum as the tag type
    if (is_tagged_union(wanted_type) && (actual_type->id == ZigTypeIdEnum ||
                actual_type->id == ZigTypeIdEnumLiteral))
    {
        return ir_analyze_enum_to_union(ira, scope, source_node, 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 &&
            // `types_match_const_cast_only` only gets info for child_types
            (!actual_type->data.pointer.is_const || wanted_type->data.pointer.is_const) &&
            (!actual_type->data.pointer.is_volatile || wanted_type->data.pointer.is_volatile))
        {
            if ((err = ir_cast_ptr_align(ira, scope, source_node, wanted_type, actual_type, value->source_node)))
                return ira->codegen->invalid_inst_gen;

            return ir_analyze_ptr_to_array(ira, scope, source_node, value, wanted_type);
        }
    }

    // [:x]T to [*:x]T
    // [:x]T to [*c]T
    if (wanted_type->id == ZigTypeIdPointer && is_slice(actual_type) &&
        ((wanted_type->data.pointer.ptr_len == PtrLenUnknown && wanted_type->data.pointer.sentinel != nullptr) ||
         wanted_type->data.pointer.ptr_len == PtrLenC))
    {
        ZigType *slice_ptr_type = resolve_struct_field_type(ira->codegen,
                actual_type->data.structure.fields[slice_ptr_index]);
        if (types_match_const_cast_only(ira, wanted_type->data.pointer.child_type,
                slice_ptr_type->data.pointer.child_type, source_node,
                !wanted_type->data.pointer.is_const).id == ConstCastResultIdOk &&
            (slice_ptr_type->data.pointer.sentinel != nullptr &&
                (wanted_type->data.pointer.ptr_len == PtrLenC ||
                    const_values_equal(ira->codegen, wanted_type->data.pointer.sentinel,
                        slice_ptr_type->data.pointer.sentinel))))
        {
            TypeStructField *ptr_field = actual_type->data.structure.fields[slice_ptr_index];
            Stage1AirInst *slice_ptr = ir_analyze_struct_value_field_value(ira, scope, source_node, value, ptr_field);
            return ir_implicit_cast2(ira, scope, source_node, slice_ptr, wanted_type);
        }
    }

    // cast from *T and [*]T to *anyopaque and ?*anyopaque
    // but don't do it if the actual type is a double pointer
    if (is_pointery_and_elem_is_not_pointery(actual_type)) {
        ZigType *dest_ptr_type = nullptr;
        if (wanted_type->id == ZigTypeIdPointer &&
            actual_type->id != ZigTypeIdOptional &&
            wanted_type->data.pointer.child_type == ira->codegen->builtin_types.entry_anyopaque)
        {
            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.child_type == ira->codegen->builtin_types.entry_anyopaque)
        {
            dest_ptr_type = wanted_type->data.maybe.child_type;
        }
        if (dest_ptr_type != nullptr) {
            return ir_analyze_ptr_cast(ira, scope, source_node, value, source_node,
                    wanted_type, source_node, true, false);
        }
    }

    // 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)
    {
        bool has_bits;
        if ((err = type_has_bits2(ira->codegen, actual_type, &has_bits)))
            return ira->codegen->invalid_inst_gen;
        if (!has_bits) {
            return ir_get_ref(ira, scope, source_node, 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, scope, source_node, value, wanted_type, nullptr);
    }

    // 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, scope, source_node, 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, scope, source_node, value, source_node,
                wanted_type, source_node, true, false);
    }

    // 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, scope, source_node, value, wanted_type);
    }

    // cast from inferred struct type to array, union, or struct
    if (is_anon_container(actual_type)) {
        const bool is_array_init =
            actual_type->data.structure.special == StructSpecialInferredTuple;
        const uint32_t field_count = actual_type->data.structure.src_field_count;

        if (wanted_type->id == ZigTypeIdArray && (is_array_init || field_count == 0) &&
            wanted_type->data.array.len == field_count)
        {
            Stage1AirInst *struct_ptr = ir_get_ref(ira, scope, source_node, value, true, false);
            if (type_is_invalid(struct_ptr->value->type))
                return ira->codegen->invalid_inst_gen;

            Stage1AirInst *ptr = ir_analyze_struct_literal_to_array(ira, scope, source_node, struct_ptr, actual_type, wanted_type);
            if (ptr->value->type->id != ZigTypeIdPointer)
                return ptr;
            return ir_get_deref(ira, scope, source_node, ptr, nullptr);
        } else if (wanted_type->id == ZigTypeIdStruct && !is_slice(wanted_type) &&
                (!is_array_init || field_count == 0))
        {
            Stage1AirInst *struct_ptr = ir_get_ref(ira, scope, source_node, value, true, false);
            if (type_is_invalid(struct_ptr->value->type))
                return ira->codegen->invalid_inst_gen;

            Stage1AirInst *ptr = ir_analyze_struct_literal_to_struct(ira, scope, source_node, struct_ptr, actual_type, wanted_type);
            if (ptr->value->type->id != ZigTypeIdPointer)
                return ptr;
            return ir_get_deref(ira, scope, source_node, ptr, nullptr);
        } else if (wanted_type->id == ZigTypeIdUnion && !is_array_init && field_count == 1) {
            Stage1AirInst *struct_ptr = ir_get_ref(ira, scope, source_node, value, true, false);
            if (type_is_invalid(struct_ptr->value->type))
                return ira->codegen->invalid_inst_gen;

            Stage1AirInst *ptr = ir_analyze_struct_literal_to_union(ira, scope, source_node, struct_ptr, actual_type, wanted_type);
            if (ptr->value->type->id != ZigTypeIdPointer)
                return ptr;
            return ir_get_deref(ira, scope, source_node, ptr, nullptr);
        }
    }

    // cast from pointer to inferred struct type to pointer to array, union, or struct
    if (actual_type->id == ZigTypeIdPointer && is_anon_container(actual_type->data.pointer.child_type)) {
        ZigType *anon_type = actual_type->data.pointer.child_type;
        const bool is_array_init =
            anon_type->data.structure.special == StructSpecialInferredTuple;
        const uint32_t field_count = anon_type->data.structure.src_field_count;

        if (wanted_type->id == ZigTypeIdPointer &&
            (!actual_type->data.pointer.is_volatile || wanted_type->data.pointer.is_volatile))
        {
            ZigType *wanted_child = wanted_type->data.pointer.child_type;
            bool const_ok = (!actual_type->data.pointer.is_const || wanted_type->data.pointer.is_const);
            if (wanted_child->id == ZigTypeIdArray && (is_array_init || field_count == 0) &&
                wanted_child->data.array.len == field_count && (const_ok || field_count == 0))
            {
                Stage1AirInst *res = ir_analyze_struct_literal_to_array(ira, scope, source_node, value, anon_type, wanted_child);
                if (res->value->type->id == ZigTypeIdPointer)
                    return res;
                return ir_get_ref(ira, scope, source_node, res, actual_type->data.pointer.is_const, actual_type->data.pointer.is_volatile);
            } else if (wanted_child->id == ZigTypeIdStruct && !is_slice(wanted_type) &&
                    (!is_array_init || field_count == 0) && const_ok)
            {
                Stage1AirInst *res = ir_analyze_struct_literal_to_struct(ira, scope, source_node, value, anon_type, wanted_child);
                if (res->value->type->id == ZigTypeIdPointer)
                    return res;
                return ir_get_ref(ira, scope, source_node, res, actual_type->data.pointer.is_const, actual_type->data.pointer.is_volatile);
            } else if (wanted_child->id == ZigTypeIdUnion && !is_array_init && field_count == 1 && const_ok) {
                Stage1AirInst *res =  ir_analyze_struct_literal_to_union(ira, scope, source_node, value, anon_type, wanted_child);
                if (res->value->type->id == ZigTypeIdPointer)
                    return res;
                return ir_get_ref(ira, scope, source_node, res, actual_type->data.pointer.is_const, actual_type->data.pointer.is_volatile);
            }
        } else if (is_slice(wanted_type) && (is_array_init || field_count == 0)) {
            ZigType *slice_type = wanted_type->data.structure.fields[slice_ptr_index]->type_entry;
            if ((!actual_type->data.pointer.is_const || slice_type->data.pointer.is_const || field_count == 0) &&
                (!actual_type->data.pointer.is_volatile || slice_type->data.pointer.is_volatile))
            {
                ZigType *slice_child_type = slice_type->data.pointer.child_type;
                ZigType *slice_array_type = get_array_type(ira->codegen, slice_child_type, field_count, nullptr);
                Stage1AirInst *res = ir_analyze_struct_literal_to_array(ira, scope, source_node, value, anon_type, slice_array_type);
                if (type_is_invalid(res->value->type))
                    return ira->codegen->invalid_inst_gen;
                if (res->value->type->id != ZigTypeIdPointer)
                    res = ir_get_ref(ira, scope, source_node, res, actual_type->data.pointer.is_const, actual_type->data.pointer.is_volatile);

                return ir_resolve_ptr_of_array_to_slice(ira, scope, source_node, res, wanted_type, nullptr);
            }
        }
    }

    // cast from undefined to anything
    if (actual_type->id == ZigTypeIdUndefined) {
        return ir_analyze_undefined_to_anything(ira, scope, source_node, value, wanted_type);
    }

    // T to ?U, where T implicitly casts to U
    if (wanted_type->id == ZigTypeIdOptional && actual_type->id != ZigTypeIdOptional) {
        Stage1AirInst *cast1 = ir_implicit_cast2(ira, scope, source_node, value, wanted_type->data.maybe.child_type);
        if (type_is_invalid(cast1->value->type))
            return ira->codegen->invalid_inst_gen;
        return ir_implicit_cast2(ira, scope, source_node, cast1, wanted_type);
    }

    // T to E!U, where T implicitly casts to U
    if (wanted_type->id == ZigTypeIdErrorUnion && actual_type->id != ZigTypeIdErrorUnion &&
        actual_type->id != ZigTypeIdErrorSet)
    {
        Stage1AirInst *cast1 = ir_implicit_cast2(ira, scope, source_node, value, wanted_type->data.error_union.payload_type);
        if (type_is_invalid(cast1->value->type))
            return ira->codegen->invalid_inst_gen;
        return ir_implicit_cast2(ira, scope, source_node, cast1, wanted_type);
    }

    ErrorMsg *parent_msg = ir_add_error_node(ira, source_node,
        buf_sprintf("expected type '%s', found '%s'",
            buf_ptr(&wanted_type->name),
            buf_ptr(&actual_type->name)));
    report_recursive_error(ira, source_node, &const_cast_result, parent_msg);
    return ira->codegen->invalid_inst_gen;
}

static Stage1AirInst *ir_implicit_cast2(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        Stage1AirInst *value, ZigType *expected_type)
{
    assert(value);
    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, scope, source_node, expected_type, value);
}

static Stage1AirInst *ir_implicit_cast(IrAnalyze *ira, Stage1AirInst *value, ZigType *expected_type) {
    return ir_implicit_cast2(ira, value->scope, value->source_node, value, expected_type);
}

static ZigType *get_ptr_elem_type(CodeGen *g, Stage1AirInst *ptr) {
    ir_assert(ptr->value->type->id == ZigTypeIdPointer, ptr);
    ZigType *elem_type = ptr->value->type->data.pointer.child_type;
    if (elem_type != g->builtin_types.entry_anytype)
        return elem_type;

    if (ir_resolve_lazy(g, ptr->source_node, ptr->value))
        return g->builtin_types.entry_invalid;

    assert(value_is_comptime(ptr->value));
    ZigValue *pointee = const_ptr_pointee_unchecked(g, ptr->value);
    return pointee->type;
}

static Stage1AirInst *ir_get_deref(IrAnalyze *ira, Scope *scope, AstNode *source_node, Stage1AirInst *ptr,
        ResultLoc *result_loc)
{
    Error err;
    ZigType *ptr_type = ptr->value->type;
    if (type_is_invalid(ptr_type))
        return ira->codegen->invalid_inst_gen;

    if (ptr_type->id != ZigTypeIdPointer) {
        ir_add_error_node(ira, source_node,
            buf_sprintf("attempt to dereference non-pointer type '%s'",
                buf_ptr(&ptr_type->name)));
        return ira->codegen->invalid_inst_gen;
    }

    ZigType *child_type = ptr_type->data.pointer.child_type;
    if (type_is_invalid(child_type))
        return ira->codegen->invalid_inst_gen;
    // if the child type has one possible value, the deref is comptime
    switch (type_has_one_possible_value(ira->codegen, child_type)) {
        case OnePossibleValueInvalid:
            return ira->codegen->invalid_inst_gen;
        case OnePossibleValueYes:
            return ir_const_move(ira, scope, source_node,
                     get_the_one_possible_value(ira->codegen, child_type));
        case OnePossibleValueNo:
            break;
    }
    if (instr_is_comptime(ptr)) {
        if (ptr->value->special == ConstValSpecialUndef) {
            // If we are in a TypeOf call, we return an undefined value instead of erroring
            // since we know the type.
            if (get_scope_typeof(scope)) {
                return ir_const_undef(ira, scope, source_node, child_type);
            }

            ir_add_error(ira, ptr, buf_sprintf("attempt to dereference undefined value"));
            return ira->codegen->invalid_inst_gen;
        }
        if (ptr->value->data.x_ptr.mut != ConstPtrMutRuntimeVar) {
            ZigValue *pointee = const_ptr_pointee_unchecked(ira->codegen, ptr->value);
            if (child_type == ira->codegen->builtin_types.entry_anytype) {
                child_type = pointee->type;
            }
            if (pointee->special != ConstValSpecialRuntime) {
                Stage1AirInst *result = ir_const(ira, scope, source_node, child_type);

                if ((err = ir_read_const_ptr(ira, ira->codegen, source_node, result->value,
                                ptr->value)))
                {
                    return ira->codegen->invalid_inst_gen;
                }
                result->value->type = child_type;
                return result;
            }
        }
    }

    // if the instruction is a const ref instruction we can skip it
    if (ptr->id == Stage1AirInstIdRef) {
        Stage1AirInstRef *ref_inst = reinterpret_cast<Stage1AirInstRef *>(ptr);
        return ref_inst->operand;
    }

    // If the instruction is a element pointer instruction to a vector, we emit
    // vector element extract instruction rather than load pointer. If the
    // pointer type has non-VECTOR_INDEX_RUNTIME value, it would have been
    // possible to implement this in the codegen for Stage1AirInstLoadPtr.
    // However if it has VECTOR_INDEX_RUNTIME then we must emit a compile error
    // if the vector index cannot be determined right here, right now, because
    // the type information does not contain enough information to actually
    // perform a dereference.
    if (ptr_type->data.pointer.vector_index == VECTOR_INDEX_RUNTIME) {
        if (ptr->id == Stage1AirInstIdElemPtr) {
            Stage1AirInstElemPtr *elem_ptr = (Stage1AirInstElemPtr *)ptr;
            Stage1AirInst *vector_loaded = ir_get_deref(ira, elem_ptr->array_ptr->scope,
                    elem_ptr->array_ptr->source_node, elem_ptr->array_ptr, nullptr);
            Stage1AirInst *elem_index = elem_ptr->elem_index;
            return ir_build_vector_extract_elem(ira, scope, source_node, vector_loaded, elem_index);
        }
        ir_add_error(ira, ptr,
            buf_sprintf("unable to determine vector element index of type '%s'", buf_ptr(&ptr_type->name)));
        return ira->codegen->invalid_inst_gen;
    }

    Stage1AirInst *result_loc_inst;
    if (ptr_type->data.pointer.host_int_bytes != 0 && handle_is_ptr(ira->codegen, child_type)) {
        if (result_loc == nullptr) result_loc = no_result_loc();
        result_loc_inst = ir_resolve_result(ira, ira->suspend_source_instr, result_loc, child_type, nullptr, true, true);
        if (type_is_invalid(result_loc_inst->value->type) || result_loc_inst->value->type->id == ZigTypeIdUnreachable) {
            return result_loc_inst;
        }
    } else {
        result_loc_inst = nullptr;
    }

    return ir_build_load_ptr_gen(ira, scope, source_node, ptr, child_type, result_loc_inst);
}

static bool ir_resolve_const_align(CodeGen *codegen, Stage1Air *exec, AstNode *source_node,
        ZigValue *const_val, uint32_t *out)
{
    Error err;
    if ((err = ir_resolve_const_val(codegen, exec, source_node, const_val, UndefBad)))
        return false;

    uint32_t align_bytes = bigint_as_u32(&const_val->data.x_bigint);
    if (align_bytes == 0) {
        exec_add_error_node_gen(codegen, exec, source_node, buf_sprintf("alignment must be >= 1"));
        return false;
    }

    if (!is_power_of_2(align_bytes)) {
        exec_add_error_node_gen(codegen, exec, source_node,
                buf_sprintf("alignment value %" PRIu32 " is not a power of 2", align_bytes));
        return false;
    }

    *out = align_bytes;
    return true;
}

static bool ir_resolve_align(IrAnalyze *ira, Stage1AirInst *value, ZigType *elem_type, uint32_t *out) {
    if (type_is_invalid(value->value->type))
        return false;

    // Look for this pattern: `*align(@alignOf(T)) T`.
    // This can be resolved to be `*out = 0` without resolving any alignment.
    if (elem_type != nullptr && value->value->special == ConstValSpecialLazy &&
        value->value->data.x_lazy->id == LazyValueIdAlignOf)
    {
        LazyValueAlignOf *lazy_align_of = reinterpret_cast<LazyValueAlignOf *>(value->value->data.x_lazy);

        ZigType *lazy_elem_type = ir_resolve_type(lazy_align_of->ira, lazy_align_of->target_type);
        if (type_is_invalid(lazy_elem_type))
            return false;

        if (elem_type == lazy_elem_type) {
            *out = 0;
            return true;
        }
    }

    Stage1AirInst *casted_value = ir_implicit_cast(ira, value, get_align_amt_type(ira->codegen));
    if (type_is_invalid(casted_value->value->type))
        return false;

    return ir_resolve_const_align(ira->codegen, ira->new_irb.exec, value->source_node,
            casted_value->value, out);
}

static bool ir_resolve_unsigned(IrAnalyze *ira, Stage1AirInst *value, ZigType *int_type, uint64_t *out) {
    if (type_is_invalid(value->value->type))
        return false;

    Stage1AirInst *casted_value = ir_implicit_cast(ira, value, int_type);
    if (type_is_invalid(casted_value->value->type))
        return false;

    ZigValue *const_val = ir_resolve_const(ira, casted_value, UndefBad);
    if (!const_val)
        return false;

    *out = bigint_as_u64(&const_val->data.x_bigint);
    return true;
}

static bool ir_resolve_usize(IrAnalyze *ira, Stage1AirInst *value, uint64_t *out) {
    return ir_resolve_unsigned(ira, value, ira->codegen->builtin_types.entry_usize, out);
}

static bool ir_resolve_bool(IrAnalyze *ira, Stage1AirInst *value, bool *out) {
    if (type_is_invalid(value->value->type))
        return false;

    Stage1AirInst *casted_value = ir_implicit_cast(ira, value, ira->codegen->builtin_types.entry_bool);
    if (type_is_invalid(casted_value->value->type))
        return false;

    ZigValue *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, Stage1AirInst *value, bool *out) {
    if (!value) {
        *out = false;
        return true;
    }
    return ir_resolve_bool(ira, value, out);
}

static bool ir_resolve_reduce_op(IrAnalyze *ira, Stage1AirInst *value, ReduceOp *out) {
    if (type_is_invalid(value->value->type))
        return false;

    ZigType *reduce_op_type = get_builtin_type(ira->codegen, "ReduceOp");

    Stage1AirInst *casted_value = ir_implicit_cast(ira, value, reduce_op_type);
    if (type_is_invalid(casted_value->value->type))
        return false;

    ZigValue *const_val = ir_resolve_const(ira, casted_value, UndefBad);
    if (!const_val)
        return false;

    *out = (ReduceOp)bigint_as_u32(&const_val->data.x_enum_tag);
    return true;
}

static bool ir_resolve_atomic_order(IrAnalyze *ira, Stage1AirInst *value, AtomicOrder *out) {
    if (type_is_invalid(value->value->type))
        return false;

    ZigType *atomic_order_type = get_builtin_type(ira->codegen, "AtomicOrder");

    Stage1AirInst *casted_value = ir_implicit_cast(ira, value, atomic_order_type);
    if (type_is_invalid(casted_value->value->type))
        return false;

    ZigValue *const_val = ir_resolve_const(ira, casted_value, UndefBad);
    if (!const_val)
        return false;

    *out = (AtomicOrder)bigint_as_u32(&const_val->data.x_enum_tag);
    return true;
}

static bool ir_resolve_atomic_rmw_op(IrAnalyze *ira, Stage1AirInst *value, AtomicRmwOp *out) {
    if (type_is_invalid(value->value->type))
        return false;

    ZigType *atomic_rmw_op_type = get_builtin_type(ira->codegen, "AtomicRmwOp");

    Stage1AirInst *casted_value = ir_implicit_cast(ira, value, atomic_rmw_op_type);
    if (type_is_invalid(casted_value->value->type))
        return false;

    ZigValue *const_val = ir_resolve_const(ira, casted_value, UndefBad);
    if (!const_val)
        return false;

    *out = (AtomicRmwOp)bigint_as_u32(&const_val->data.x_enum_tag);
    return true;
}

static bool ir_resolve_global_linkage(IrAnalyze *ira, Stage1AirInst *value, GlobalLinkageId *out) {
    if (type_is_invalid(value->value->type))
        return false;

    ZigType *global_linkage_type = get_builtin_type(ira->codegen, "GlobalLinkage");

    Stage1AirInst *casted_value = ir_implicit_cast(ira, value, global_linkage_type);
    if (type_is_invalid(casted_value->value->type))
        return false;

    ZigValue *const_val = ir_resolve_const(ira, casted_value, UndefBad);
    if (!const_val)
        return false;

    *out = (GlobalLinkageId)bigint_as_u32(&const_val->data.x_enum_tag);
    return true;
}

static bool ir_resolve_float_mode(IrAnalyze *ira, Stage1AirInst *value, FloatMode *out) {
    if (type_is_invalid(value->value->type))
        return false;

    ZigType *float_mode_type = get_builtin_type(ira->codegen, "FloatMode");

    Stage1AirInst *casted_value = ir_implicit_cast(ira, value, float_mode_type);
    if (type_is_invalid(casted_value->value->type))
        return false;

    ZigValue *const_val = ir_resolve_const(ira, casted_value, UndefBad);
    if (!const_val)
        return false;

    *out = (FloatMode)bigint_as_u32(&const_val->data.x_enum_tag);
    return true;
}

static Buf *ir_resolve_str(IrAnalyze *ira, Stage1AirInst *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, false);
    ZigType *str_type = get_slice_type(ira->codegen, ptr_type);
    Stage1AirInst *casted_value = ir_implicit_cast(ira, value, str_type);
    if (type_is_invalid(casted_value->value->type))
        return nullptr;

    ZigValue *const_val = ir_resolve_const(ira, casted_value, UndefBad);
    if (!const_val)
        return nullptr;

    ZigValue *ptr_field = const_val->data.x_struct.fields[slice_ptr_index];
    ZigValue *len_field = const_val->data.x_struct.fields[slice_len_index];

    assert(ptr_field->data.x_ptr.special == ConstPtrSpecialBaseArray);
    ZigValue *array_val = ptr_field->data.x_ptr.data.base_array.array_val;
    expand_undef_array(ira->codegen, array_val);
    size_t len = bigint_as_usize(&len_field->data.x_bigint);
    if (array_val->data.x_array.special == ConstArraySpecialBuf && len == buf_len(array_val->data.x_array.data.s_buf)) {
        return array_val->data.x_array.data.s_buf;
    }
    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;
        ZigValue *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_u64(&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 Stage1AirInst *ir_analyze_instruction_add_implicit_return_type(IrAnalyze *ira,
        Stage1ZirInstAddImplicitReturnType *instruction)
{
    Stage1AirInst *value = instruction->value->child;
    if (type_is_invalid(value->value->type))
        return ir_unreach_error(ira);

    if (instruction->result_loc_ret == nullptr || !instruction->result_loc_ret->implicit_return_type_done) {
        ira->src_implicit_return_type_list.append(value);
    }

    return ir_const_void(ira, instruction->base.scope, instruction->base.source_node);
}

static Stage1AirInst *ir_analyze_instruction_return(IrAnalyze *ira, Stage1ZirInstReturn *instruction) {
    if (instruction->operand == nullptr) {
        // result location mechanism took care of it.
        Stage1AirInst *result = ir_build_return_gen(ira, instruction->base.scope, instruction->base.source_node, nullptr);
        return ir_finish_anal(ira, result);
    }

    Stage1AirInst *operand = instruction->operand->child;
    if (type_is_invalid(operand->value->type))
        return ir_unreach_error(ira);

    Stage1AirInst *casted_operand = ir_implicit_cast(ira, operand, ira->explicit_return_type);
    if (type_is_invalid(casted_operand->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 (!instr_is_comptime(operand) && ira->explicit_return_type != nullptr &&
            handle_is_ptr(ira->codegen, ira->explicit_return_type))
    {
        // result location mechanism took care of it.
        Stage1AirInst *result = ir_build_return_gen(ira, instruction->base.scope, instruction->base.source_node, nullptr);
        return ir_finish_anal(ira, result);
    }

    if (casted_operand->value->special == ConstValSpecialRuntime &&
        casted_operand->value->type->id == ZigTypeIdPointer &&
        casted_operand->value->data.rh_ptr == RuntimeHintPtrStack)
    {
        ir_add_error_node(ira, instruction->operand->source_node,
                buf_sprintf("function returns address of local variable"));
        return ir_unreach_error(ira);
    }

    Stage1AirInst *result = ir_build_return_gen(ira, instruction->base.scope, instruction->base.source_node, casted_operand);
    return ir_finish_anal(ira, result);
}

static Stage1AirInst *ir_analyze_instruction_const(IrAnalyze *ira, Stage1ZirInstConst *instruction) {
    return ir_const_move(ira, instruction->base.scope, instruction->base.source_node, instruction->value);
}

static Stage1AirInst *ir_analyze_bin_op_bool(IrAnalyze *ira, Stage1ZirInstBinOp *bin_op_instruction) {
    Stage1AirInst *op1 = bin_op_instruction->op1->child;
    if (type_is_invalid(op1->value->type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *op2 = bin_op_instruction->op2->child;
    if (type_is_invalid(op2->value->type))
        return ira->codegen->invalid_inst_gen;

    ZigType *bool_type = ira->codegen->builtin_types.entry_bool;

    Stage1AirInst *casted_op1 = ir_implicit_cast(ira, op1, bool_type);
    if (type_is_invalid(casted_op1->value->type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *casted_op2 = ir_implicit_cast(ira, op2, bool_type);
    if (type_is_invalid(casted_op2->value->type))
        return ira->codegen->invalid_inst_gen;

    if (instr_is_comptime(casted_op1) && instr_is_comptime(casted_op2)) {
        ZigValue *op1_val = ir_resolve_const(ira, casted_op1, UndefBad);
        if (op1_val == nullptr)
            return ira->codegen->invalid_inst_gen;

        ZigValue *op2_val = ir_resolve_const(ira, casted_op2, UndefBad);
        if (op2_val == nullptr)
            return ira->codegen->invalid_inst_gen;

        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.scope,
                bin_op_instruction->base.source_node, result_bool);
    }

    return ir_build_bin_op_gen(ira, bin_op_instruction->base.scope,
            bin_op_instruction->base.source_node, bool_type, bin_op_instruction->op_id,
            casted_op1, casted_op2, bin_op_instruction->safety_check_on);
}

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 void set_optional_value_to_null(ZigValue *val) {
    assert(val->special == ConstValSpecialStatic);
    if (val->type->id == ZigTypeIdNull) return; // nothing to do
    assert(val->type->id == ZigTypeIdOptional);
    if (get_src_ptr_type(val->type) != nullptr) {
        val->data.x_ptr.special = ConstPtrSpecialNull;
    } else if (is_opt_err_set(val->type)) {
        val->data.x_err_set = nullptr;
    } else {
        val->data.x_optional = nullptr;
    }
}

static void set_optional_payload(ZigValue *opt_val, ZigValue *payload) {
    assert(opt_val->special == ConstValSpecialStatic);
    assert(opt_val->type->id == ZigTypeIdOptional);
    if (payload == nullptr) {
        set_optional_value_to_null(opt_val);
    } else if (get_src_ptr_type(opt_val->type)) {
        assert(get_src_ptr_type(payload->type));
        opt_val->data.x_ptr = payload->data.x_ptr;
    } else if (is_opt_err_set(opt_val->type)) {
        assert(payload->type->id == ZigTypeIdErrorSet);
        opt_val->data.x_err_set = payload->data.x_err_set;
    } else {
        opt_val->data.x_optional = payload;
    }
}

static Stage1AirInst *ir_evaluate_bin_op_cmp(IrAnalyze *ira, ZigType *resolved_type,
    ZigValue *op1_val, ZigValue *op2_val, Scope *scope, AstNode *source_node, IrBinOp op_id,
    bool one_possible_value)
{
    if (op1_val->special == ConstValSpecialUndef ||
        op2_val->special == ConstValSpecialUndef)
        return ir_const_undef(ira, scope, source_node, resolved_type);
    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, scope, source_node, 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, scope, source_node, answer);
    }
    zig_unreachable();
}

static Stage1AirInst *ir_try_evaluate_bin_op_cmp_const(IrAnalyze *ira, Scope *scope, AstNode *source_node, Stage1AirInst *op1, Stage1AirInst *op2,
        ZigType *resolved_type, IrBinOp op_id)
{
    assert(op1->value->type == resolved_type && op2->value->type == resolved_type);
    bool one_possible_value;
    switch (type_has_one_possible_value(ira->codegen, resolved_type)) {
        case OnePossibleValueInvalid:
            return ira->codegen->invalid_inst_gen;
        case OnePossibleValueYes:
            one_possible_value = true;
            break;
        case OnePossibleValueNo:
            one_possible_value = false;
            break;
    }

    if (one_possible_value || (instr_is_comptime(op1) && instr_is_comptime(op2))) {
        ZigValue *op1_val = one_possible_value ? op1->value : ir_resolve_const(ira, op1, UndefBad);
        if (op1_val == nullptr)
            return ira->codegen->invalid_inst_gen;
        ZigValue *op2_val = one_possible_value ? op2->value : ir_resolve_const(ira, op2, UndefBad);
        if (op2_val == nullptr)
            return ira->codegen->invalid_inst_gen;
        if (resolved_type->id != ZigTypeIdVector)
            return ir_evaluate_bin_op_cmp(ira, resolved_type, op1_val, op2_val, scope, source_node, op_id, one_possible_value);
        Stage1AirInst *result = ir_const(ira, scope, source_node,
                                     get_vector_type(ira->codegen, resolved_type->data.vector.len, ira->codegen->builtin_types.entry_bool));
        result->value->data.x_array.data.s_none.elements =
                ira->codegen->pass1_arena->allocate<ZigValue>(resolved_type->data.vector.len);

        expand_undef_array(ira->codegen, result->value);
        for (size_t i = 0;i < resolved_type->data.vector.len;i++) {
            Stage1AirInst *cur_res = ir_evaluate_bin_op_cmp(ira, resolved_type->data.vector.elem_type,
                                                        &op1_val->data.x_array.data.s_none.elements[i],
                                                        &op2_val->data.x_array.data.s_none.elements[i],
                                                        scope, source_node, op_id, one_possible_value);
            copy_const_val(ira->codegen, &result->value->data.x_array.data.s_none.elements[i], cur_res->value);
        }
        return result;
    } else {
        return nullptr;
    }
}

// Returns ErrorNotLazy when the value cannot be determined
static Error lazy_cmp_zero(CodeGen *codegen, AstNode *source_node, ZigValue *val, Cmp *result) {
    Error err;

    switch (type_has_one_possible_value(codegen, val->type)) {
        case OnePossibleValueInvalid:
            return ErrorSemanticAnalyzeFail;
        case OnePossibleValueNo:
            break;
        case OnePossibleValueYes:
            switch (val->type->id) {
                case ZigTypeIdInt:
                    src_assert(val->type->data.integral.bit_count == 0, source_node);
                    *result = CmpEQ;
                    return ErrorNone;
                case ZigTypeIdUndefined:
                    return ErrorNotLazy;
                default:
                    zig_unreachable();
            }
    }

    switch (val->special) {
        case ConstValSpecialRuntime:
        case ConstValSpecialUndef:
            return ErrorNotLazy;
        case ConstValSpecialStatic:
            switch (val->type->id) {
                case ZigTypeIdComptimeInt:
                case ZigTypeIdInt:
                    *result = bigint_cmp_zero(&val->data.x_bigint);
                    return ErrorNone;
                case ZigTypeIdComptimeFloat:
                case ZigTypeIdFloat:
                    if (float_is_nan(val))
                        return ErrorNotLazy;
                    *result = float_cmp_zero(val);
                    return ErrorNone;
                default:
                    return ErrorNotLazy;
            }
        case ConstValSpecialLazy:
            switch (val->data.x_lazy->id) {
                case LazyValueIdInvalid:
                    zig_unreachable();
                case LazyValueIdAlignOf: {
                    LazyValueAlignOf *lazy_align_of = reinterpret_cast<LazyValueAlignOf *>(val->data.x_lazy);
                    IrAnalyze *ira = lazy_align_of->ira;

                    bool is_zero_bits;
                    if ((err = type_val_resolve_zero_bits(ira->codegen, lazy_align_of->target_type->value,
                                    nullptr, nullptr, &is_zero_bits)))
                    {
                        return err;
                    }

                    *result = is_zero_bits ? CmpEQ : CmpGT;
                    return ErrorNone;
                }
                case LazyValueIdSizeOf: {
                    LazyValueSizeOf *lazy_size_of = reinterpret_cast<LazyValueSizeOf *>(val->data.x_lazy);
                    IrAnalyze *ira = lazy_size_of->ira;
                    bool is_zero_bits;
                    if ((err = type_val_resolve_zero_bits(ira->codegen, lazy_size_of->target_type->value,
                        nullptr, nullptr, &is_zero_bits)))
                    {
                        return err;
                    }
                    *result = is_zero_bits ? CmpEQ : CmpGT;
                    return ErrorNone;
                }
                default:
                    return ErrorNotLazy;
            }
    }
    zig_unreachable();
}

static ErrorMsg *ir_eval_bin_op_cmp_scalar(IrAnalyze *ira, Scope *scope, AstNode *source_node,
    ZigValue *op1_val, IrBinOp op_id, ZigValue *op2_val, ZigValue *out_val)
{
    Error err;
    {
        // Before resolving the values, we special case comparisons against zero. These can often
        // be done without resolving lazy values, preventing potential dependency loops.
        Cmp op1_cmp_zero;
        if ((err = lazy_cmp_zero(ira->codegen, source_node, op1_val, &op1_cmp_zero))) {
            if (err == ErrorNotLazy) goto never_mind_just_calculate_it_normally;
            return ira->codegen->trace_err;
        }
        Cmp op2_cmp_zero;
        if ((err = lazy_cmp_zero(ira->codegen, source_node, op2_val, &op2_cmp_zero))) {
            if (err == ErrorNotLazy) goto never_mind_just_calculate_it_normally;
            return ira->codegen->trace_err;
        }
        bool can_cmp_zero = false;
        Cmp cmp_result;
        if (op1_cmp_zero == CmpEQ && op2_cmp_zero == CmpEQ) {
            can_cmp_zero = true;
            cmp_result = CmpEQ;
        } else if (op1_cmp_zero == CmpGT && op2_cmp_zero == CmpEQ) {
            can_cmp_zero = true;
            cmp_result = CmpGT;
        } else if (op1_cmp_zero == CmpEQ && op2_cmp_zero == CmpGT) {
            can_cmp_zero = true;
            cmp_result = CmpLT;
        } else if (op1_cmp_zero == CmpLT && op2_cmp_zero == CmpEQ) {
            can_cmp_zero = true;
            cmp_result = CmpLT;
        } else if (op1_cmp_zero == CmpEQ && op2_cmp_zero == CmpLT) {
            can_cmp_zero = true;
            cmp_result = CmpGT;
        } else if (op1_cmp_zero == CmpLT && op2_cmp_zero == CmpGT) {
            can_cmp_zero = true;
            cmp_result = CmpLT;
        } else if (op1_cmp_zero == CmpGT && op2_cmp_zero == CmpLT) {
            can_cmp_zero = true;
            cmp_result = CmpGT;
        }
        if (can_cmp_zero) {
            bool answer = resolve_cmp_op_id(op_id, cmp_result);
            out_val->special = ConstValSpecialStatic;
            out_val->data.x_bool = answer;
            return nullptr;
        }
    }
never_mind_just_calculate_it_normally:

    if ((err = ir_resolve_const_val(ira->codegen, ira->new_irb.exec, source_node,
                    op1_val, UndefOk)))
    {
        return ira->codegen->trace_err;
    }
    if ((err = ir_resolve_const_val(ira->codegen, ira->new_irb.exec, source_node,
                    op2_val, UndefOk)))
    {
        return ira->codegen->trace_err;
    }


    if (op1_val->special == ConstValSpecialUndef || op2_val->special == ConstValSpecialUndef ||
        op1_val->type->id == ZigTypeIdUndefined || op2_val->type->id == ZigTypeIdUndefined)
    {
        out_val->special = ConstValSpecialUndef;
        return nullptr;
    }

    bool op1_is_float = op1_val->type->id == ZigTypeIdFloat || op1_val->type->id == ZigTypeIdComptimeFloat;
    bool op2_is_float = op2_val->type->id == ZigTypeIdFloat || op2_val->type->id == ZigTypeIdComptimeFloat;
    if (op1_is_float && op2_is_float) {
        if (float_is_nan(op1_val) || float_is_nan(op2_val)) {
            out_val->special = ConstValSpecialStatic;
            out_val->data.x_bool = op_id == IrBinOpCmpNotEq;
            return nullptr;
        }
        if (op1_val->type->id == ZigTypeIdComptimeFloat) {
            Stage1AirInst *tmp = ir_const_noval(ira, scope, source_node);
            tmp->value = op1_val;
            Stage1AirInst *casted = ir_implicit_cast(ira, tmp, op2_val->type);
            op1_val = casted->value;
        } else if (op2_val->type->id == ZigTypeIdComptimeFloat) {
            Stage1AirInst *tmp = ir_const_noval(ira, scope, source_node);
            tmp->value = op2_val;
            Stage1AirInst *casted = ir_implicit_cast(ira, tmp, op1_val->type);
            op2_val = casted->value;
        }
        Cmp cmp_result = float_cmp(op1_val, op2_val);
        out_val->special = ConstValSpecialStatic;
        out_val->data.x_bool = resolve_cmp_op_id(op_id, cmp_result);
        return nullptr;
    }

    bool op1_is_int = op1_val->type->id == ZigTypeIdInt || op1_val->type->id == ZigTypeIdComptimeInt;
    bool op2_is_int = op2_val->type->id == ZigTypeIdInt || op2_val->type->id == ZigTypeIdComptimeInt;

    if (op1_is_int && op2_is_int) {
        Cmp cmp_result = bigint_cmp(&op1_val->data.x_bigint, &op2_val->data.x_bigint);
        out_val->special = ConstValSpecialStatic;
        out_val->data.x_bool = resolve_cmp_op_id(op_id, cmp_result);

        return nullptr;
    }

    // Handle the case where one of the two operands is a fp value and the other
    // is an integer value
    ZigValue *float_val;
    if (op1_is_int && op2_is_float) {
        float_val = op2_val;
    } else if (op1_is_float && op2_is_int) {
        float_val = op1_val;
    } else {
        zig_unreachable();
    }

    // They can never be equal if the fp value has a non-zero decimal part
    if (op_id == IrBinOpCmpEq || op_id == IrBinOpCmpNotEq) {
        if (float_has_fraction(float_val)) {
            out_val->special = ConstValSpecialStatic;
            out_val->data.x_bool = op_id == IrBinOpCmpNotEq;
            return nullptr;
        }
    }

    // Cast the integer operand into a fp value to perform the comparison
    BigFloat op1_bigfloat;
    BigFloat op2_bigfloat;
    value_to_bigfloat(&op1_bigfloat, op1_val);
    value_to_bigfloat(&op2_bigfloat, op2_val);

    Cmp cmp_result = bigfloat_cmp(&op1_bigfloat, &op2_bigfloat);
    out_val->special = ConstValSpecialStatic;
    out_val->data.x_bool = resolve_cmp_op_id(op_id, cmp_result);

    return nullptr;
}

static Stage1AirInst *ir_analyze_bin_op_cmp_numeric(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        Stage1AirInst *op1, Stage1AirInst *op2, IrBinOp op_id)
{
    Error err;

    ZigType *scalar_result_type = ira->codegen->builtin_types.entry_bool;
    ZigType *result_type = scalar_result_type;
    ZigType *op1_scalar_type = op1->value->type;
    ZigType *op2_scalar_type = op2->value->type;
    if (op1->value->type->id == ZigTypeIdVector && op2->value->type->id == ZigTypeIdVector) {
        if (op1->value->type->data.vector.len != op2->value->type->data.vector.len) {
            ir_add_error_node(ira, source_node,
                buf_sprintf("vector length mismatch: %" PRIu64 " and %" PRIu64,
                    op1->value->type->data.vector.len, op2->value->type->data.vector.len));
            return ira->codegen->invalid_inst_gen;
        }
        result_type = get_vector_type(ira->codegen, op1->value->type->data.vector.len, scalar_result_type);
        op1_scalar_type = op1->value->type->data.vector.elem_type;
        op2_scalar_type = op2->value->type->data.vector.elem_type;
    } else if (op1->value->type->id == ZigTypeIdVector || op2->value->type->id == ZigTypeIdVector) {
        ir_add_error_node(ira, source_node,
            buf_sprintf("mixed scalar and vector operands to comparison operator: '%s' and '%s'",
                buf_ptr(&op1->value->type->name), buf_ptr(&op2->value->type->name)));
        return ira->codegen->invalid_inst_gen;
    }

    bool opv_op1;
    switch (type_has_one_possible_value(ira->codegen, op1->value->type)) {
        case OnePossibleValueInvalid:
            return ira->codegen->invalid_inst_gen;
        case OnePossibleValueYes:
            opv_op1 = true;
            break;
        case OnePossibleValueNo:
            opv_op1 = false;
            break;
    }
    bool opv_op2;
    switch (type_has_one_possible_value(ira->codegen, op2->value->type)) {
        case OnePossibleValueInvalid:
            return ira->codegen->invalid_inst_gen;
        case OnePossibleValueYes:
            opv_op2 = true;
            break;
        case OnePossibleValueNo:
            opv_op2 = false;
            break;
    }
    Cmp op1_cmp_zero;
    bool have_op1_cmp_zero = false;
    if ((err = lazy_cmp_zero(ira->codegen, source_node, op1->value, &op1_cmp_zero))) {
        if (err != ErrorNotLazy) return ira->codegen->invalid_inst_gen;
    } else {
        have_op1_cmp_zero = true;
    }
    Cmp op2_cmp_zero;
    bool have_op2_cmp_zero = false;
    if ((err = lazy_cmp_zero(ira->codegen, source_node, op2->value, &op2_cmp_zero))) {
        if (err != ErrorNotLazy) return ira->codegen->invalid_inst_gen;
    } else {
        have_op2_cmp_zero = true;
    }
    if (((opv_op1 || instr_is_comptime(op1)) && (opv_op2 || instr_is_comptime(op2))) ||
        (have_op1_cmp_zero && have_op2_cmp_zero))
    {
        Stage1AirInst *result_instruction = ir_const(ira, scope, source_node, result_type);
        ZigValue *out_val = result_instruction->value;
        if (result_type->id == ZigTypeIdVector) {
            size_t len = result_type->data.vector.len;
            expand_undef_array(ira->codegen, op1->value);
            expand_undef_array(ira->codegen, op2->value);
            out_val->special = ConstValSpecialUndef;
            expand_undef_array(ira->codegen, out_val);
            for (size_t i = 0; i < len; i += 1) {
                ZigValue *scalar_op1_val = &op1->value->data.x_array.data.s_none.elements[i];
                ZigValue *scalar_op2_val = &op2->value->data.x_array.data.s_none.elements[i];
                ZigValue *scalar_out_val = &out_val->data.x_array.data.s_none.elements[i];
                assert(scalar_out_val->type == scalar_result_type);
                ErrorMsg *msg = ir_eval_bin_op_cmp_scalar(ira, scope, source_node,
                        scalar_op1_val, op_id, scalar_op2_val, scalar_out_val);
                if (msg != nullptr) {
                    add_error_note(ira->codegen, msg, source_node,
                        buf_sprintf("when computing vector element at index %" ZIG_PRI_usize, i));
                    return ira->codegen->invalid_inst_gen;
                }
            }
            out_val->type = result_type;
            out_val->special = ConstValSpecialStatic;
        } else {
            if (ir_eval_bin_op_cmp_scalar(ira, scope, source_node, op1->value, op_id,
                        op2->value, out_val) != nullptr)
            {
                return ira->codegen->invalid_inst_gen;
            }
        }
        return result_instruction;
    }

    // If one operand has a comptime-known comparison with 0, and the other operand is unsigned, we might
    // know the answer, depending on the operator.
    // TODO make this work with vectors
    if (have_op1_cmp_zero && op2_scalar_type->id == ZigTypeIdInt && !op2_scalar_type->data.integral.is_signed) {
        if (op1_cmp_zero == CmpEQ) {
            // 0 <= unsigned_x    // true
            // 0 >  unsigned_x    // false
            switch (op_id) {
                case IrBinOpCmpLessOrEq:
                    return ir_const_bool(ira, scope, source_node, true);
                case IrBinOpCmpGreaterThan:
                    return ir_const_bool(ira, scope, source_node, false);
                default:
                    break;
            }
        } else if (op1_cmp_zero == CmpLT) {
            // -1 != unsigned_x   // true
            // -1 <= unsigned_x   // true
            // -1 <  unsigned_x   // true
            // -1 == unsigned_x   // false
            // -1 >= unsigned_x   // false
            // -1 >  unsigned_x   // false
            switch (op_id) {
                case IrBinOpCmpNotEq:
                case IrBinOpCmpLessOrEq:
                case IrBinOpCmpLessThan:
                    return ir_const_bool(ira, scope, source_node, true);
                case IrBinOpCmpEq:
                case IrBinOpCmpGreaterOrEq:
                case IrBinOpCmpGreaterThan:
                    return ir_const_bool(ira, scope, source_node, false);
                default:
                    break;
            }
        }
    }
    if (have_op2_cmp_zero && op1_scalar_type->id == ZigTypeIdInt && !op1_scalar_type->data.integral.is_signed) {
        if (op2_cmp_zero == CmpEQ) {
            // unsigned_x <  0    // false
            // unsigned_x >= 0    // true
            switch (op_id) {
                case IrBinOpCmpLessThan:
                    return ir_const_bool(ira, scope, source_node, false);
                case IrBinOpCmpGreaterOrEq:
                    return ir_const_bool(ira, scope, source_node, true);
                default:
                    break;
            }
        } else if (op2_cmp_zero == CmpLT) {
            // unsigned_x != -1   // true
            // unsigned_x >= -1   // true
            // unsigned_x >  -1   // true
            // unsigned_x == -1   // false
            // unsigned_x <  -1   // false
            // unsigned_x <= -1   // false
            switch (op_id) {
                case IrBinOpCmpNotEq:
                case IrBinOpCmpGreaterOrEq:
                case IrBinOpCmpGreaterThan:
                    return ir_const_bool(ira, scope, source_node, true);
                case IrBinOpCmpEq:
                case IrBinOpCmpLessThan:
                case IrBinOpCmpLessOrEq:
                    return ir_const_bool(ira, scope, source_node, false);
                default:
                    break;
            }
        }
    }

    // It must be a runtime comparison.
    // For floats, emit a float comparison instruction.
    bool op1_is_float = op1_scalar_type->id == ZigTypeIdFloat || op1_scalar_type->id == ZigTypeIdComptimeFloat;
    bool op2_is_float = op2_scalar_type->id == ZigTypeIdFloat || op2_scalar_type->id == ZigTypeIdComptimeFloat;
    if (op1_is_float && op2_is_float) {
        // Implicit cast the smaller one to the larger one.
        ZigType *dest_scalar_type;
        if (op1_scalar_type->id == ZigTypeIdComptimeFloat) {
            dest_scalar_type = op2_scalar_type;
        } else if (op2_scalar_type->id == ZigTypeIdComptimeFloat) {
            dest_scalar_type = op1_scalar_type;
        } else if (op1_scalar_type->data.floating.bit_count >= op2_scalar_type->data.floating.bit_count) {
            dest_scalar_type = op1_scalar_type;
        } else {
            dest_scalar_type = op2_scalar_type;
        }
        ZigType *dest_type = (result_type->id == ZigTypeIdVector) ?
            get_vector_type(ira->codegen, result_type->data.vector.len, dest_scalar_type) : dest_scalar_type;
        Stage1AirInst *casted_op1 = ir_implicit_cast(ira, op1, dest_type);
        Stage1AirInst *casted_op2 = ir_implicit_cast(ira, op2, dest_type);
        if (type_is_invalid(casted_op1->value->type) || type_is_invalid(casted_op2->value->type))
            return ira->codegen->invalid_inst_gen;
        return ir_build_bin_op_gen(ira, scope, source_node, result_type, op_id, casted_op1, casted_op2, true);
    }

    // For mixed unsigned integer sizes, implicit cast both operands to the larger integer.
    // For mixed signed and unsigned integers, implicit cast both operands to a signed
    // integer with + 1 bit.
    // For mixed floats and integers, extract the integer part from the float, cast that to
    // a signed integer with mantissa bits + 1, and if there was any non-integral part of the float,
    // add/subtract 1.
    bool dest_int_is_signed = false;
    if (have_op1_cmp_zero) {
        if (op1_cmp_zero == CmpLT) dest_int_is_signed = true;
    } else if (op1_is_float) {
        dest_int_is_signed = true;
    } else if (op1_scalar_type->id == ZigTypeIdInt && op1_scalar_type->data.integral.is_signed) {
        dest_int_is_signed = true;
    }
    if (have_op2_cmp_zero) {
        if (op2_cmp_zero == CmpLT) dest_int_is_signed = true;
    } else if (op2_is_float) {
        dest_int_is_signed = true;
    } else if (op2->value->type->id == ZigTypeIdInt && op2->value->type->data.integral.is_signed) {
        dest_int_is_signed = true;
    }
    ZigType *dest_float_type = nullptr;
    uint32_t op1_bits;
    if (instr_is_comptime(op1) && result_type->id != ZigTypeIdVector) {
        ZigValue *op1_val = ir_resolve_const(ira, op1, UndefOk);
        if (op1_val == nullptr)
            return ira->codegen->invalid_inst_gen;
        if (op1_val->special == ConstValSpecialUndef)
            return ir_const_undef(ira, scope, source_node, ira->codegen->builtin_types.entry_bool);
        bool is_unsigned;
        if (op1_is_float) {
            BigInt bigint = {};
            float_init_bigint(&bigint, op1_val);
            Cmp zcmp = float_cmp_zero(op1_val);
            if (float_has_fraction(op1_val)) {
                if (op_id == IrBinOpCmpEq || op_id == IrBinOpCmpNotEq) {
                    return ir_const_bool(ira, scope, source_node, op_id == IrBinOpCmpNotEq);
                }
                if (zcmp == CmpLT) {
                    bigint_decr(&bigint);
                } else {
                    bigint_incr(&bigint);
                }
            }
            op1_bits = bigint_bits_needed(&bigint);
            is_unsigned = zcmp != CmpLT;
        } else {
            op1_bits = bigint_bits_needed(&op1_val->data.x_bigint);
            is_unsigned = bigint_cmp_zero(&op1_val->data.x_bigint) != CmpLT;
        }
        if (is_unsigned && dest_int_is_signed) {
            op1_bits += 1;
        }
    } else if (op1_is_float) {
        src_assert(op1_scalar_type->id == ZigTypeIdFloat, source_node);
        dest_float_type = op1_scalar_type;
    } else {
        src_assert(op1_scalar_type->id == ZigTypeIdInt, source_node);
        op1_bits = op1_scalar_type->data.integral.bit_count;
        if (!op1_scalar_type->data.integral.is_signed && dest_int_is_signed) {
            op1_bits += 1;
        }
    }
    uint32_t op2_bits;
    if (instr_is_comptime(op2) && result_type->id != ZigTypeIdVector) {
        ZigValue *op2_val = ir_resolve_const(ira, op2, UndefOk);
        if (op2_val == nullptr)
            return ira->codegen->invalid_inst_gen;
        if (op2_val->special == ConstValSpecialUndef)
            return ir_const_undef(ira, scope, source_node, ira->codegen->builtin_types.entry_bool);
        bool is_unsigned;
        if (op2_is_float) {
            BigInt bigint = {};
            float_init_bigint(&bigint, op2_val);
            Cmp zcmp = float_cmp_zero(op2_val);
            if (float_has_fraction(op2_val)) {
                if (op_id == IrBinOpCmpEq || op_id == IrBinOpCmpNotEq) {
                    return ir_const_bool(ira, scope, source_node, op_id == IrBinOpCmpNotEq);
                }
                if (zcmp == CmpLT) {
                    bigint_decr(&bigint);
                } else {
                    bigint_incr(&bigint);
                }
            }
            op2_bits = bigint_bits_needed(&bigint);
            is_unsigned = zcmp != CmpLT;
        } else {
            op2_bits = bigint_bits_needed(&op2_val->data.x_bigint);
            is_unsigned = bigint_cmp_zero(&op2_val->data.x_bigint) != CmpLT;
        }
        if (is_unsigned && dest_int_is_signed) {
            op2_bits += 1;
        }
    } else if (op2_is_float) {
        src_assert(op2_scalar_type->id == ZigTypeIdFloat, source_node);
        dest_float_type = op2_scalar_type;
    } else {
        src_assert(op2_scalar_type->id == ZigTypeIdInt, source_node);
        op2_bits = op2_scalar_type->data.integral.bit_count;
        if (!op2_scalar_type->data.integral.is_signed && dest_int_is_signed) {
            op2_bits += 1;
        }
    }
    ZigType *dest_scalar_type = (dest_float_type == nullptr) ?
        get_int_type(ira->codegen, dest_int_is_signed, (op1_bits > op2_bits) ? op1_bits : op2_bits) :
        dest_float_type;
    ZigType *dest_type = (result_type->id == ZigTypeIdVector) ?
        get_vector_type(ira->codegen, result_type->data.vector.len, dest_scalar_type) : dest_scalar_type;

    Stage1AirInst *casted_op1 = ir_implicit_cast(ira, op1, dest_type);
    if (type_is_invalid(casted_op1->value->type))
        return ira->codegen->invalid_inst_gen;
    Stage1AirInst *casted_op2 = ir_implicit_cast(ira, op2, dest_type);
    if (type_is_invalid(casted_op2->value->type))
        return ira->codegen->invalid_inst_gen;
    return ir_build_bin_op_gen(ira, scope, source_node, result_type, op_id, casted_op1, casted_op2, true);
}

static bool type_is_self_comparable(ZigType *ty, bool is_equality_cmp) {
    if (type_is_numeric(ty)) {
        return true;
    }
    switch (ty->id) {
        case ZigTypeIdInvalid:
            zig_unreachable();

        case ZigTypeIdComptimeFloat:
        case ZigTypeIdComptimeInt:
        case ZigTypeIdInt:
        case ZigTypeIdFloat:
            zig_unreachable(); // handled with the type_is_numeric check above

        case ZigTypeIdVector:
            // Not every case is handled by the type_is_numeric check above,
            // vectors of bool trigger this code path
        case ZigTypeIdBool:
        case ZigTypeIdMetaType:
        case ZigTypeIdVoid:
        case ZigTypeIdErrorSet:
        case ZigTypeIdFn:
        case ZigTypeIdOpaque:
        case ZigTypeIdBoundFn:
        case ZigTypeIdEnum:
        case ZigTypeIdEnumLiteral:
        case ZigTypeIdAnyFrame:
            return is_equality_cmp;

        case ZigTypeIdPointer:
            return is_equality_cmp || (ty->data.pointer.ptr_len == PtrLenC);

        case ZigTypeIdUnreachable:
        case ZigTypeIdArray:
        case ZigTypeIdStruct:
        case ZigTypeIdUndefined:
        case ZigTypeIdNull:
        case ZigTypeIdErrorUnion:
        case ZigTypeIdUnion:
        case ZigTypeIdFnFrame:
            return false;

        case ZigTypeIdOptional:
            return is_equality_cmp && get_src_ptr_type(ty) != nullptr;
    }
    zig_unreachable();
}

static Stage1AirInst *ir_try_evaluate_cmp_optional_non_optional_const(IrAnalyze *ira, Scope *scope, AstNode *source_node, ZigType *child_type,
        Stage1AirInst *optional, Stage1AirInst *non_optional, IrBinOp op_id)
{
    assert(optional->value->type->id == ZigTypeIdOptional);
    assert(optional->value->type->data.maybe.child_type == non_optional->value->type);
    assert(non_optional->value->type == child_type);
    assert(op_id == IrBinOpCmpEq || op_id == IrBinOpCmpNotEq);

    if (instr_is_comptime(optional) && instr_is_comptime(non_optional)) {
        ZigValue *optional_val = ir_resolve_const(ira, optional, UndefBad);
        if (!optional_val) {
            return ira->codegen->invalid_inst_gen;
        }

        ZigValue *non_optional_val = ir_resolve_const(ira, non_optional, UndefBad);
        if (!non_optional_val) {
            return ira->codegen->invalid_inst_gen;
        }

        if (!optional_value_is_null(optional_val)) {
            Stage1AirInst *optional_unwrapped = ir_analyze_optional_value_payload_value(ira, scope, source_node, optional, false);
            if (type_is_invalid(optional_unwrapped->value->type)) {
                return ira->codegen->invalid_inst_gen;
            }

            Stage1AirInst *ret = ir_try_evaluate_bin_op_cmp_const(ira, scope, source_node, optional_unwrapped, non_optional, child_type, op_id);
            assert(ret != nullptr);
            return ret;
        }
        return ir_const_bool(ira, scope, source_node, (op_id != IrBinOpCmpEq));
    } else {
        return nullptr;
    }
}

static Stage1AirInst *ir_evaluate_cmp_optional_non_optional(IrAnalyze *ira, Scope *scope, AstNode *source_node, ZigType *child_type,
        Stage1AirInst *optional, Stage1AirInst *non_optional, IrBinOp op_id)
{
    assert(optional->value->type->id == ZigTypeIdOptional);
    assert(optional->value->type->data.maybe.child_type == non_optional->value->type);
    assert(non_optional->value->type == child_type);
    assert(op_id == IrBinOpCmpEq || op_id == IrBinOpCmpNotEq);

    ZigType *result_type = ira->codegen->builtin_types.entry_bool;
    ir_append_basic_block_gen(&ira->new_irb, ira->new_irb.current_basic_block);

    Stage1AirBasicBlock *null_block = ir_create_basic_block_gen(ira, scope, "CmpOptionalNonOptionalOptionalNull");
    Stage1AirBasicBlock *non_null_block = ir_create_basic_block_gen(ira, scope, "CmpOptionalNonOptionalOptionalNotNull");
    Stage1AirBasicBlock *end_block = ir_create_basic_block_gen(ira, scope, "CmpOptionalNonOptionalEnd");

    Stage1AirInst *is_non_null = ir_build_test_non_null_gen(ira, scope, source_node, optional);
    ir_build_cond_br_gen(ira, scope, source_node, is_non_null, non_null_block, null_block);

    ir_set_cursor_at_end_and_append_block_gen(&ira->new_irb, non_null_block);
    Stage1AirInst *optional_unwrapped = ir_analyze_optional_value_payload_value(ira, scope, source_node, optional, false);
    if (type_is_invalid(optional_unwrapped->value->type)) {
        return ira->codegen->invalid_inst_gen;
    }
    Stage1AirInst *non_null_cmp_result = ir_build_bin_op_gen(ira, scope, source_node, result_type, op_id,
            optional_unwrapped, non_optional, false); // safety check unnecessary for comparison operators
    ir_build_br_gen(ira, scope, source_node, end_block);


    ir_set_cursor_at_end_and_append_block_gen(&ira->new_irb, null_block);
    Stage1AirInst *null_result = ir_const_bool(ira, scope, source_node, (op_id != IrBinOpCmpEq));
    ir_build_br_gen(ira, scope, source_node, end_block);

    ir_set_cursor_at_end_gen(&ira->new_irb, end_block);
    int incoming_count = 2;
    Stage1AirBasicBlock **incoming_blocks = heap::c_allocator.allocate_nonzero<Stage1AirBasicBlock *>(incoming_count);
    incoming_blocks[0] = null_block;
    incoming_blocks[1] = non_null_block;
    Stage1AirInst **incoming_values = heap::c_allocator.allocate_nonzero<Stage1AirInst *>(incoming_count);
    incoming_values[0] = null_result;
    incoming_values[1] = non_null_cmp_result;

    return ir_build_phi_gen(ira, scope, source_node, incoming_count, incoming_blocks, incoming_values, result_type);
}

static Stage1AirInst *ir_analyze_cmp_optional_non_optional(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        Stage1AirInst *op1, Stage1AirInst *op2, Stage1AirInst *optional, IrBinOp op_id)
{
    assert(op_id == IrBinOpCmpEq || op_id == IrBinOpCmpNotEq);
    assert(optional->value->type->id == ZigTypeIdOptional);
    assert(get_src_ptr_type(optional->value->type) == nullptr);

    Stage1AirInst *non_optional;
    if (op1 == optional) {
        non_optional = op2;
    } else if (op2 == optional) {
        non_optional = op1;
    } else {
        zig_unreachable();
    }

    ZigType *child_type = optional->value->type->data.maybe.child_type;
    bool child_type_matches = (child_type == non_optional->value->type);
    if (!child_type_matches || !type_is_self_comparable(child_type, true)) {
        ErrorMsg *msg = ir_add_error_node(ira, source_node, buf_sprintf("cannot compare types '%s' and '%s'",
            buf_ptr(&op1->value->type->name),
            buf_ptr(&op2->value->type->name)));

        if (!child_type_matches) {
            if (non_optional->value->type->id == ZigTypeIdOptional) {
                add_error_note(ira->codegen, msg, source_node, buf_sprintf(
                        "optional to optional comparison is only supported for optional pointer types"));
            } else {
                add_error_note(ira->codegen, msg, source_node,
                        buf_sprintf("optional child type '%s' must be the same as non-optional type '%s'",
                                buf_ptr(&child_type->name),
                                buf_ptr(&non_optional->value->type->name)));
            }
        } else {
            add_error_note(ira->codegen, msg, source_node,
                    buf_sprintf("operator not supported for type '%s'",
                            buf_ptr(&child_type->name)));
        }
        return ira->codegen->invalid_inst_gen;
    }

    if (child_type->id == ZigTypeIdVector) {
        ir_add_error_node(ira, source_node, buf_sprintf("TODO add comparison of optional vector"));
        return ira->codegen->invalid_inst_gen;
    }

    if (Stage1AirInst *const_result = ir_try_evaluate_cmp_optional_non_optional_const(ira, scope, source_node, child_type,
            optional, non_optional, op_id))
    {
        return const_result;
    }

    return ir_evaluate_cmp_optional_non_optional(ira, scope, source_node, child_type, optional, non_optional, op_id);
}

static Stage1AirInst *ir_analyze_bin_op_cmp(IrAnalyze *ira, Stage1ZirInstBinOp *bin_op_instruction) {
    Stage1AirInst *op1 = bin_op_instruction->op1->child;
    if (type_is_invalid(op1->value->type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *op2 = bin_op_instruction->op2->child;
    if (type_is_invalid(op2->value->type))
        return ira->codegen->invalid_inst_gen;

    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 == ZigTypeIdNull) {
        return ir_const_bool(ira, bin_op_instruction->base.scope,
                bin_op_instruction->base.source_node, (op_id == IrBinOpCmpEq));
    } else if (is_equality_cmp &&
        ((op1->value->type->id == ZigTypeIdNull && op2->value->type->id == ZigTypeIdOptional) ||
        (op2->value->type->id == ZigTypeIdNull && op1->value->type->id == ZigTypeIdOptional)))
    {
        Stage1AirInst *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)) {
            ZigValue *maybe_val = ir_resolve_const(ira, maybe_op, UndefBad);
            if (!maybe_val)
                return ira->codegen->invalid_inst_gen;
            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.scope,
                    bin_op_instruction->base.source_node, bool_result);
        }

        Stage1AirInst *is_non_null = ir_build_test_non_null_gen(ira, bin_op_instruction->base.scope,
                bin_op_instruction->base.source_node, maybe_op);

        if (op_id == IrBinOpCmpEq) {
            return ir_build_bool_not_gen(ira, bin_op_instruction->base.scope,
                    bin_op_instruction->base.source_node, is_non_null);
        } else {
            return is_non_null;
        }
    } else if (is_equality_cmp &&
        ((op1->value->type->id == ZigTypeIdNull && op2->value->type->id == ZigTypeIdPointer &&
            op2->value->type->data.pointer.ptr_len == PtrLenC) ||
        (op2->value->type->id == ZigTypeIdNull && op1->value->type->id == ZigTypeIdPointer &&
            op1->value->type->data.pointer.ptr_len == PtrLenC)))
    {
        Stage1AirInst *c_ptr_op;
        if (op1->value->type->id == ZigTypeIdNull) {
            c_ptr_op = op2;
        } else if (op2->value->type->id == ZigTypeIdNull) {
            c_ptr_op = op1;
        } else {
            zig_unreachable();
        }
        if (instr_is_comptime(c_ptr_op)) {
            ZigValue *c_ptr_val = ir_resolve_const(ira, c_ptr_op, UndefOk);
            if (!c_ptr_val)
                return ira->codegen->invalid_inst_gen;
            if (c_ptr_val->special == ConstValSpecialUndef)
                return ir_const_undef(ira, bin_op_instruction->base.scope, bin_op_instruction->base.source_node, ira->codegen->builtin_types.entry_bool);
            bool is_null = c_ptr_val->data.x_ptr.special == ConstPtrSpecialNull ||
                (c_ptr_val->data.x_ptr.special == ConstPtrSpecialHardCodedAddr &&
                    c_ptr_val->data.x_ptr.data.hard_coded_addr.addr == 0);
            bool bool_result = (op_id == IrBinOpCmpEq) ? is_null : !is_null;
            return ir_const_bool(ira, bin_op_instruction->base.scope, bin_op_instruction->base.source_node, bool_result);
        }
        Stage1AirInst *is_non_null = ir_build_test_non_null_gen(ira, bin_op_instruction->base.scope, bin_op_instruction->base.source_node, c_ptr_op);

        if (op_id == IrBinOpCmpEq) {
            return ir_build_bool_not_gen(ira, bin_op_instruction->base.scope, bin_op_instruction->base.source_node, is_non_null);
        } else {
            return is_non_null;
        }
    } else if (is_equality_cmp &&
        (op1->value->type->id == ZigTypeIdOptional && get_src_ptr_type(op1->value->type) == nullptr))
    {
        return ir_analyze_cmp_optional_non_optional(ira, bin_op_instruction->base.scope, bin_op_instruction->base.source_node, op1, op2, op1, op_id);
    } else if(is_equality_cmp &&
        (op2->value->type->id == ZigTypeIdOptional && get_src_ptr_type(op2->value->type) == nullptr))
    {
        return ir_analyze_cmp_optional_non_optional(ira, bin_op_instruction->base.scope, bin_op_instruction->base.source_node, op1, op2, op2, op_id);
    } else if (op1->value->type->id == ZigTypeIdNull || op2->value->type->id == ZigTypeIdNull) {
        ZigType *non_null_type = (op1->value->type->id == ZigTypeIdNull) ? op2->value->type : op1->value->type;
        ir_add_error_node(ira, source_node, buf_sprintf("comparison of '%s' with null",
            buf_ptr(&non_null_type->name)));
        return ira->codegen->invalid_inst_gen;
    } else if (is_equality_cmp && (
        (op1->value->type->id == ZigTypeIdEnumLiteral && op2->value->type->id == ZigTypeIdUnion) ||
        (op2->value->type->id == ZigTypeIdEnumLiteral && op1->value->type->id == ZigTypeIdUnion)))
    {
        // Support equality comparison between a union's tag value and a enum literal
        Stage1AirInst *union_val = op1->value->type->id == ZigTypeIdUnion ? op1 : op2;
        Stage1AirInst *enum_val = op1->value->type->id == ZigTypeIdUnion ? op2 : op1;

        if (!is_tagged_union(union_val->value->type)) {
            ErrorMsg *msg = ir_add_error_node(ira, source_node,
                buf_sprintf("comparison of union and enum literal is only valid for tagged union types"));
            add_error_note(ira->codegen, msg, union_val->value->type->data.unionation.decl_node,
                buf_sprintf("type %s is not a tagged union",
                    buf_ptr(&union_val->value->type->name)));
            return ira->codegen->invalid_inst_gen;
        }

        ZigType *tag_type = union_val->value->type->data.unionation.tag_type;
        assert(tag_type != nullptr);

        Stage1AirInst *casted_union = ir_implicit_cast(ira, union_val, tag_type);
        if (type_is_invalid(casted_union->value->type))
            return ira->codegen->invalid_inst_gen;

        Stage1AirInst *casted_val = ir_implicit_cast(ira, enum_val, tag_type);
        if (type_is_invalid(casted_val->value->type))
            return ira->codegen->invalid_inst_gen;

        if (instr_is_comptime(casted_union)) {
            ZigValue *const_union_val = ir_resolve_const(ira, casted_union, UndefBad);
            if (!const_union_val)
                return ira->codegen->invalid_inst_gen;

            ZigValue *const_enum_val = ir_resolve_const(ira, casted_val, UndefBad);
            if (!const_enum_val)
                return ira->codegen->invalid_inst_gen;

            Cmp cmp_result = bigint_cmp(&const_union_val->data.x_union.tag, &const_enum_val->data.x_enum_tag);
            bool bool_result = (op_id == IrBinOpCmpEq) ? cmp_result == CmpEQ : cmp_result != CmpEQ;

            return ir_const_bool(ira, bin_op_instruction->base.scope, bin_op_instruction->base.source_node, bool_result);
        }

        return ir_build_bin_op_gen(ira, bin_op_instruction->base.scope, bin_op_instruction->base.source_node, ira->codegen->builtin_types.entry_bool,
            op_id, casted_union, casted_val, bin_op_instruction->safety_check_on);
    }

    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_inst_gen;
        }
        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_inst_gen;
        }

        if (!resolve_inferred_error_set(ira->codegen, intersect_type, source_node)) {
            return ira->codegen->invalid_inst_gen;
        }

        // 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.scope, bin_op_instruction->base.source_node, 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_inst_gen;
            }
            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.scope, bin_op_instruction->base.source_node, answer);
            }
        }

        if (instr_is_comptime(op1) && instr_is_comptime(op2)) {
            ZigValue *op1_val = ir_resolve_const(ira, op1, UndefBad);
            if (op1_val == nullptr)
                return ira->codegen->invalid_inst_gen;
            ZigValue *op2_val = ir_resolve_const(ira, op2, UndefBad);
            if (op2_val == nullptr)
                return ira->codegen->invalid_inst_gen;

            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.scope, bin_op_instruction->base.source_node, answer);
        }

        return ir_build_bin_op_gen(ira, bin_op_instruction->base.scope, bin_op_instruction->base.source_node, ira->codegen->builtin_types.entry_bool,
                op_id, op1, op2, bin_op_instruction->safety_check_on);
    }

    if (type_is_numeric(op1->value->type) && type_is_numeric(op2->value->type)) {
        // This operation allows any combination of integer and float types, regardless of the
        // signed-ness, comptime-ness, and bit-width. So peer type resolution is incorrect for
        // numeric types.
        return ir_analyze_bin_op_cmp_numeric(ira, bin_op_instruction->base.scope, bin_op_instruction->base.source_node, op1, op2, op_id);
    }

    Stage1AirInst *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_inst_gen;

    bool operator_allowed = type_is_self_comparable(resolved_type, is_equality_cmp);

    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_inst_gen;
    }

    Stage1AirInst *casted_op1 = ir_implicit_cast(ira, op1, resolved_type);
    if (type_is_invalid(casted_op1->value->type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *casted_op2 = ir_implicit_cast(ira, op2, resolved_type);
    if (type_is_invalid(casted_op2->value->type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *resolve_const_result = ir_try_evaluate_bin_op_cmp_const(ira, bin_op_instruction->base.scope, bin_op_instruction->base.source_node, casted_op1,
                                                                       casted_op2, resolved_type, op_id);
    if (resolve_const_result != nullptr) {
        return resolve_const_result;
    }

    ZigType *res_type = (resolved_type->id == ZigTypeIdVector) ?
        get_vector_type(ira->codegen, resolved_type->data.vector.len, ira->codegen->builtin_types.entry_bool) :
        ira->codegen->builtin_types.entry_bool;
    return ir_build_bin_op_gen(ira, bin_op_instruction->base.scope, bin_op_instruction->base.source_node, res_type,
            op_id, casted_op1, casted_op2, bin_op_instruction->safety_check_on);
}

static ErrorMsg *ir_eval_math_op_scalar(IrAnalyze *ira, Scope *scope, AstNode *source_node, ZigType *type_entry,
        ZigValue *op1_val, IrBinOp op_id, ZigValue *op2_val, ZigValue *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_node(ira, source_node, buf_sprintf("division by zero"));
    }
    if ((op_id == IrBinOpRemRem || op_id == IrBinOpRemMod) && op2_zcmp == CmpLT) {
        return ir_add_error_node(ira, source_node, 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:
            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_node(ira, source_node, 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_node(ira, source_node, buf_sprintf("exact division had a remainder"));
                }
            } else {
                float_div_trunc(out_val, op1_val, op2_val);
                ZigValue remainder = {};
                float_rem(&remainder, op1_val, op2_val);
                if (float_cmp_zero(&remainder) != CmpEQ) {
                    return ir_add_error_node(ira, source_node, 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;
        case IrBinOpMaximum:
            if (is_int) {
                bigint_max(&out_val->data.x_bigint, &op1_val->data.x_bigint, &op2_val->data.x_bigint);
            } else {
                float_max(out_val, op1_val, op2_val);
            }
            break;
        case IrBinOpMinimum:
            if (is_int) {
                bigint_min(&out_val->data.x_bigint, &op1_val->data.x_bigint, &op2_val->data.x_bigint);
            } else {
                float_min(out_val, op1_val, op2_val);
            }
            break;
        case IrBinOpAddSat:
            if (is_int) {
                bigint_add_sat(&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);
            } else {
                zig_unreachable();
            }
            break;
        case IrBinOpSubSat:
            if (is_int) {
                bigint_sub_sat(&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);
            } else {
                zig_unreachable();
            }
            break;
        case IrBinOpMultSat:
            if (is_int) {
                bigint_mul_sat(&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);
            } else {
                zig_unreachable();
            }
            break;
        case IrBinOpShlSat:
            if (is_int) {
                bigint_shl_sat(&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);
            } else {
                zig_unreachable();
            }
            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_node(ira, source_node, 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 Stage1AirInst *ir_analyze_math_op(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        ZigType *type_entry, ZigValue *op1_val, IrBinOp op_id, ZigValue *op2_val)
{
    Stage1AirInst *result_instruction = ir_const(ira, scope, source_node, type_entry);
    ZigValue *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) {
            ZigValue *scalar_op1_val = &op1_val->data.x_array.data.s_none.elements[i];
            ZigValue *scalar_op2_val = &op2_val->data.x_array.data.s_none.elements[i];
            ZigValue *scalar_out_val = &out_val->data.x_array.data.s_none.elements[i];
            assert(scalar_op1_val->type == scalar_type);
            assert(scalar_out_val->type == scalar_type);
            ErrorMsg *msg = ir_eval_math_op_scalar(ira, scope, source_node, scalar_type,
                    scalar_op1_val, op_id, scalar_op2_val, scalar_out_val);
            if (msg != nullptr) {
                add_error_note(ira->codegen, msg, source_node,
                    buf_sprintf("when computing vector element at index %" ZIG_PRI_usize, i));
                return ira->codegen->invalid_inst_gen;
            }
        }
        out_val->type = type_entry;
        out_val->special = ConstValSpecialStatic;
    } else {
        if (ir_eval_math_op_scalar(ira, scope, source_node, type_entry, op1_val, op_id, op2_val, out_val) != nullptr) {
            return ira->codegen->invalid_inst_gen;
        }
    }
    return ir_implicit_cast(ira, result_instruction, type_entry);
}

static Stage1AirInst *ir_analyze_truncate(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        ZigType *dest_scalar_type, AstNode *dest_type_node,
        Stage1AirInst *operand, AstNode *operand_node)
{
    if (dest_scalar_type->id != ZigTypeIdInt &&
        dest_scalar_type->id != ZigTypeIdComptimeInt)
    {
        ir_add_error_node(ira, dest_type_node,
            buf_sprintf("expected integer type, found '%s'", buf_ptr(&dest_scalar_type->name)));
        return ira->codegen->invalid_inst_gen;
    }

    ZigType *src_type = operand->value->type;
    bool is_vector = (src_type->id == ZigTypeIdVector);
    ZigType *src_scalar_type = is_vector ?
        src_type->data.vector.elem_type : src_type;

    ZigType *dest_type = is_vector ?
        get_vector_type(ira->codegen, src_type->data.vector.len, dest_scalar_type) :
        dest_scalar_type;

    if (src_scalar_type->id != ZigTypeIdInt && src_scalar_type->id != ZigTypeIdComptimeInt) {
        ir_add_error_node(ira, operand_node,
            buf_sprintf("expected integer type, found '%s'", buf_ptr(&src_scalar_type->name)));
        return ira->codegen->invalid_inst_gen;
    }

    if (dest_scalar_type->id == ZigTypeIdComptimeInt) {
        return ir_implicit_cast2(ira, scope, operand_node, operand, dest_type);
    }

    if (src_scalar_type->id != ZigTypeIdComptimeInt) {
        if (src_scalar_type->data.integral.is_signed != dest_scalar_type->data.integral.is_signed) {
            const char *sign_str = dest_scalar_type->data.integral.is_signed ? "signed" : "unsigned";
            ir_add_error_node(ira, operand_node, buf_sprintf("expected %s integer type, found '%s'", sign_str, buf_ptr(&src_scalar_type->name)));
            return ira->codegen->invalid_inst_gen;
        } else if (src_scalar_type->data.integral.bit_count > 0 && src_scalar_type->data.integral.bit_count < dest_scalar_type->data.integral.bit_count) {
            ir_add_error_node(ira, operand_node, buf_sprintf("type '%s' has fewer bits than destination type '%s'",
                        buf_ptr(&src_scalar_type->name), buf_ptr(&dest_scalar_type->name)));
            return ira->codegen->invalid_inst_gen;
        }
    }

    if (instr_is_comptime(operand)) {
        ZigValue *val = ir_resolve_const(ira, operand, UndefBad);
        if (val == nullptr)
            return ira->codegen->invalid_inst_gen;

        if (!is_vector) {
            Stage1AirInst *result = ir_const(ira, scope, source_node, dest_type);
            bigint_truncate(&result->value->data.x_bigint, &val->data.x_bigint,
                    dest_scalar_type->data.integral.bit_count,
                    dest_scalar_type->data.integral.is_signed);
            return result;
        }

        Stage1AirInst *result_instruction = ir_const(ira, scope, source_node, dest_type);
        ZigValue *out_val = result_instruction->value;
        expand_undef_array(ira->codegen, operand->value);
        out_val->special = ConstValSpecialUndef;
        expand_undef_array(ira->codegen, out_val);
        size_t len = dest_type->data.vector.len;
        for (size_t i = 0; i < len; i += 1) {
            ZigValue *scalar_operand_val = &operand->value->data.x_array.data.s_none.elements[i];
            ZigValue *scalar_out_val = &out_val->data.x_array.data.s_none.elements[i];
            assert(scalar_operand_val->type == dest_scalar_type);
            assert(scalar_out_val->type == dest_scalar_type);

            bigint_truncate(&scalar_out_val->data.x_bigint,
                    &scalar_operand_val->data.x_bigint,
                    dest_scalar_type->data.integral.bit_count,
                    dest_scalar_type->data.integral.is_signed);

            scalar_out_val->type = dest_scalar_type;
            scalar_out_val->special = ConstValSpecialStatic;
        }
        out_val->type = dest_type;
        out_val->special = ConstValSpecialStatic;
        return result_instruction;
    }

    if (src_scalar_type->data.integral.bit_count == 0 ||
        dest_scalar_type->data.integral.bit_count == 0)
    {
        Stage1AirInst *result = ir_const(ira, scope, source_node, dest_type);
        if (!is_vector) {
            bigint_init_unsigned(&result->value->data.x_bigint, 0);
        }
        return result;
    }

    return ir_build_truncate_gen(ira, scope, source_node, dest_type, operand);
}

static Stage1AirInst *ir_analyze_bit_shift(IrAnalyze *ira, Stage1ZirInstBinOp *bin_op_instruction) {
    Stage1AirInst *op1 = bin_op_instruction->op1->child;
    if (type_is_invalid(op1->value->type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *op2 = bin_op_instruction->op2->child;
    if (type_is_invalid(op2->value->type))
        return ira->codegen->invalid_inst_gen;

    ZigType *op1_type = op1->value->type;
    ZigType *op2_type = op2->value->type;

    if (op1_type->id == ZigTypeIdVector && op2_type->id != ZigTypeIdVector) {
        ir_add_error_node(ira, bin_op_instruction->op1->source_node,
            buf_sprintf("bit shifting operation expected vector type, found '%s'",
                buf_ptr(&op2_type->name)));
        return ira->codegen->invalid_inst_gen;
    }

    if (op1_type->id != ZigTypeIdVector && op2_type->id == ZigTypeIdVector) {
        ir_add_error_node(ira, bin_op_instruction->op1->source_node,
            buf_sprintf("bit shifting operation expected vector type, found '%s'",
                buf_ptr(&op1_type->name)));
        return ira->codegen->invalid_inst_gen;
    }

    ZigType *op1_scalar_type = (op1_type->id == ZigTypeIdVector) ?
        op1_type->data.vector.elem_type : op1_type;
    ZigType *op2_scalar_type = (op2_type->id == ZigTypeIdVector) ?
        op2_type->data.vector.elem_type : op2_type;

    if (op1_scalar_type->id != ZigTypeIdInt && op1_scalar_type->id != ZigTypeIdComptimeInt) {
        ir_add_error_node(ira, bin_op_instruction->op1->source_node,
            buf_sprintf("bit shifting operation expected integer type, found '%s'",
                buf_ptr(&op1_scalar_type->name)));
        return ira->codegen->invalid_inst_gen;
    }

    if (op2_scalar_type->id != ZigTypeIdInt && op2_scalar_type->id != ZigTypeIdComptimeInt) {
        ir_add_error_node(ira, bin_op_instruction->op2->source_node,
            buf_sprintf("shift amount has to be an integer type, but found '%s'",
                buf_ptr(&op2_scalar_type->name)));
        return ira->codegen->invalid_inst_gen;
    }

    Stage1AirInst *casted_op2;
    IrBinOp op_id = bin_op_instruction->op_id;
    if (op1_scalar_type->id == ZigTypeIdComptimeInt) {
        // comptime_int has no finite bit width
        casted_op2 = op2;

        if (op_id == IrBinOpShlSat) {
            ir_add_error_node(ira, bin_op_instruction->base.source_node,
                buf_sprintf("saturating shift on a comptime_int which has unlimited bits"));
            return ira->codegen->invalid_inst_gen;
        }

        if (op_id == IrBinOpBitShiftLeftLossy) {
            op_id = IrBinOpBitShiftLeftExact;
        }

        if (!instr_is_comptime(op2)) {
            ir_add_error_node(ira, bin_op_instruction->base.source_node,
                buf_sprintf("LHS of shift must be a fixed-width integer type, or RHS must be compile-time known"));
            return ira->codegen->invalid_inst_gen;
        }

        ZigValue *op2_val = ir_resolve_const(ira, casted_op2, UndefBad);
        if (op2_val == nullptr)
            return ira->codegen->invalid_inst_gen;

        if (op2_val->data.x_bigint.is_negative) {
            Buf *val_buf = buf_alloc();
            bigint_append_buf(val_buf, &op2_val->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_inst_gen;
        }
    } else if (op_id == IrBinOpShlSat) {
        casted_op2 = ir_analyze_truncate(ira,
                bin_op_instruction->base.scope, bin_op_instruction->base.source_node,
                op1_scalar_type, bin_op_instruction->op1->source_node,
                op2, bin_op_instruction->op2->source_node);
        if (type_is_invalid(casted_op2->value->type))
            return ira->codegen->invalid_inst_gen;
    } else {
        const unsigned bit_count = op1_scalar_type->data.integral.bit_count;
        ZigType *shift_amt_type = get_smallest_unsigned_int_type(ira->codegen,
            bit_count > 0 ? bit_count - 1 : 0);

        if (op1_type->id == ZigTypeIdVector) {
            shift_amt_type = get_vector_type(ira->codegen, op1_type->data.vector.len,
                shift_amt_type);
        }

        casted_op2 = ir_implicit_cast(ira, op2, shift_amt_type);
        if (type_is_invalid(casted_op2->value->type))
            return ira->codegen->invalid_inst_gen;

        // This check is only valid iff op1 has at least one bit
        if (bit_count > 0 && instr_is_comptime(casted_op2)) {
            ZigValue *op2_val = ir_resolve_const(ira, casted_op2, UndefBad);
            if (op2_val == nullptr)
                return ira->codegen->invalid_inst_gen;

            ZigValue bit_count_value = {};
            init_const_usize(ira->codegen, &bit_count_value, bit_count);

            if (!value_cmp_numeric_val_all(op2_val, CmpLT, &bit_count_value)) {
                ErrorMsg* msg = ir_add_error_node(ira,
                    bin_op_instruction->base.source_node,
                    buf_sprintf("RHS of shift is too large for LHS type"));
                add_error_note(ira->codegen, msg, op1->source_node,
                    buf_sprintf("type %s has only %u bits",
                        buf_ptr(&op1->value->type->name), bit_count));

                return ira->codegen->invalid_inst_gen;
            }
        }
    }

    // Fast path for zero RHS
    if (instr_is_comptime(casted_op2)) {
        ZigValue *op2_val = ir_resolve_const(ira, casted_op2, UndefBad);
        if (op2_val == nullptr)
            return ira->codegen->invalid_inst_gen;

        if (op2_val->type->id == ZigTypeIdVector) {
            expand_undef_array(ira->codegen, op2_val);
            size_t len = op2_val->type->data.vector.len;
            for (size_t i = 0; i < len; i += 1) {
                ZigValue *scalar_val = &op2_val->data.x_array.data.s_none.elements[i];
                if (scalar_val->data.x_bigint.is_negative) {
                    Buf *val_buf = buf_alloc();
                    bigint_append_buf(val_buf, &scalar_val->data.x_bigint, 10);
                    ir_add_error(ira, casted_op2,
                        buf_sprintf("shift by negative value %s at vector index %zu",
                            buf_ptr(val_buf), i));
                    return ira->codegen->invalid_inst_gen;
                }
            }
        } else {
            if (op2_val->data.x_bigint.is_negative) {
                Buf *val_buf = buf_alloc();
                bigint_append_buf(val_buf, &op2_val->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_inst_gen;
            }
        }

        if (value_cmp_numeric_val_all(op2_val, CmpEQ, nullptr))
            return ir_analyze_cast(ira, bin_op_instruction->base.scope, bin_op_instruction->base.source_node, op1->value->type, op1);
    }

    if (instr_is_comptime(op1) && instr_is_comptime(casted_op2)) {
        ZigValue *op1_val = ir_resolve_const(ira, op1, UndefBad);
        if (op1_val == nullptr)
            return ira->codegen->invalid_inst_gen;

        ZigValue *op2_val = ir_resolve_const(ira, casted_op2, UndefBad);
        if (op2_val == nullptr)
            return ira->codegen->invalid_inst_gen;

        return ir_analyze_math_op(ira, bin_op_instruction->base.scope, bin_op_instruction->base.source_node, op1_type, op1_val, op_id, op2_val);
    }

    return ir_build_bin_op_gen(ira,
            bin_op_instruction->base.scope, bin_op_instruction->base.source_node,
            op1->value->type, op_id, op1, casted_op2, bin_op_instruction->safety_check_on);
}

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:
        case IrBinOpRemUnspecified:
        case IrBinOpMaximum:
        case IrBinOpMinimum:
            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 IrBinOpAddSat:
        case IrBinOpSubSat:
        case IrBinOpMultSat:
        case IrBinOpShlSat:
        case IrBinOpMultWrap:
        case IrBinOpArrayCat:
        case IrBinOpArrayMult:
            return false;
    }
    zig_unreachable();
}

static bool is_pointer_arithmetic_allowed(ZigType *lhs_type, IrBinOp op) {
    switch (op) {
        case IrBinOpAdd:
        case IrBinOpSub:
            break;
        default:
            return false;
    }
    if (lhs_type->id != ZigTypeIdPointer)
        return false;
    switch (lhs_type->data.pointer.ptr_len) {
        case PtrLenSingle:
            return false;
        case PtrLenUnknown:
        case PtrLenC:
            return true;
    }
    zig_unreachable();
}

// Returns true if integer `value` can be converted to `type_entry` without
// losing data.
// If `value` is a vector the function returns true if this is valid for every
// element.
static bool value_numeric_fits_in_type(ZigValue *value, ZigType *type_entry) {
    assert(value->special == ConstValSpecialStatic);
    assert(type_entry->id == ZigTypeIdInt);

    switch (value->type->id) {
        case ZigTypeIdComptimeInt:
        case ZigTypeIdInt: {
            return bigint_fits_in_bits(&value->data.x_bigint, type_entry->data.integral.bit_count,
                    type_entry->data.integral.is_signed);
        }
        case ZigTypeIdVector: {
            for (size_t i = 0; i < value->type->data.vector.len; i++) {
                ZigValue *scalar_value = &value->data.x_array.data.s_none.elements[i];
                const bool result = bigint_fits_in_bits(&scalar_value->data.x_bigint,
                        type_entry->data.integral.bit_count, type_entry->data.integral.is_signed);
                if (!result) return false;
            }
            return true;
        }
        default: zig_unreachable();
    }
}

static bool value_cmp_numeric_val(ZigValue *left, Cmp predicate, ZigValue *right, bool any) {
    assert(left->special == ConstValSpecialStatic);
    assert(right == nullptr || right->special == ConstValSpecialStatic);

    switch (left->type->id) {
        case ZigTypeIdComptimeInt:
        case ZigTypeIdInt: {
            const Cmp result = right ?
                bigint_cmp(&left->data.x_bigint, &right->data.x_bigint) :
                bigint_cmp_zero(&left->data.x_bigint);
            return result == predicate;
        }
        case ZigTypeIdComptimeFloat:
        case ZigTypeIdFloat: {
            if (float_is_nan(left))
                return false;
            if (right != nullptr && float_is_nan(right))
                return false;

            const Cmp result = right ? float_cmp(left, right) : float_cmp_zero(left);
            return result == predicate;
        }
        case ZigTypeIdVector: {
            for (size_t i = 0; i < left->type->data.vector.len; i++) {
                ZigValue *scalar_val = &left->data.x_array.data.s_none.elements[i];
                const bool result = value_cmp_numeric_val(scalar_val, predicate, right, any);

                if (any && result)
                    return true; // This element satisfies the predicate
                else if (!any && !result)
                    return false; // This element doesn't satisfy the predicate
            }
            return any ? false : true;
        }
        default:
            zig_unreachable();
    }
}

static bool value_cmp_numeric_val_any(ZigValue *left, Cmp predicate, ZigValue *right) {
    return value_cmp_numeric_val(left, predicate, right, true);
}

static bool value_cmp_numeric_val_all(ZigValue *left, Cmp predicate, ZigValue *right) {
    return value_cmp_numeric_val(left, predicate, right, false);
}

static Stage1AirInst *ir_analyze_bin_op_math(IrAnalyze *ira, Stage1ZirInstBinOp *instruction) {
    Error err;

    Stage1AirInst *op1 = instruction->op1->child;
    if (type_is_invalid(op1->value->type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *op2 = instruction->op2->child;
    if (type_is_invalid(op2->value->type))
        return ira->codegen->invalid_inst_gen;

    IrBinOp op_id = instruction->op_id;

    // look for pointer math
    if (is_pointer_arithmetic_allowed(op1->value->type, op_id)) {
        Stage1AirInst *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_inst_gen;

        // If either operand is undef, result is undef.
        ZigValue *op1_val = nullptr;
        ZigValue *op2_val = nullptr;
        if (instr_is_comptime(op1)) {
            op1_val = ir_resolve_const(ira, op1, UndefOk);
            if (op1_val == nullptr)
                return ira->codegen->invalid_inst_gen;
            if (op1_val->special == ConstValSpecialUndef)
                return ir_const_undef(ira, instruction->base.scope, instruction->base.source_node, op1->value->type);
        }
        if (instr_is_comptime(casted_op2)) {
            op2_val = ir_resolve_const(ira, casted_op2, UndefOk);
            if (op2_val == nullptr)
                return ira->codegen->invalid_inst_gen;
            if (op2_val->special == ConstValSpecialUndef)
                return ir_const_undef(ira, instruction->base.scope, instruction->base.source_node, op1->value->type);
        }

        ZigType *elem_type = op1->value->type->data.pointer.child_type;
        if ((err = type_resolve(ira->codegen, elem_type, ResolveStatusSizeKnown)))
            return ira->codegen->invalid_inst_gen;

        // NOTE: this variable is meaningful iff op2_val is not null!
        uint64_t byte_offset;
        if (op2_val != nullptr) {
            uint64_t elem_offset;
            if (!ir_resolve_usize(ira, casted_op2, &elem_offset))
                return ira->codegen->invalid_inst_gen;

            byte_offset = type_size(ira->codegen, elem_type) * elem_offset;
        }

        // Fast path for cases where the RHS is zero
        if (op2_val != nullptr && byte_offset == 0) {
            return op1;
        }

        ZigType *result_type = op1->value->type;
        // Calculate the new alignment of the pointer
        {
            uint32_t align_bytes;
            if ((err = resolve_ptr_align(ira, op1->value->type, &align_bytes)))
                return ira->codegen->invalid_inst_gen;

            // If the addend is not a comptime-known value we can still count on
            // it being a multiple of the type size
            uint32_t addend = op2_val ? byte_offset : type_size(ira->codegen, elem_type);

            // The resulting pointer is aligned to the lcd between the
            // offset (an arbitrary number) and the alignment factor (always
            // a power of two, non zero)
            uint32_t new_align = 1 << ctzll(addend | align_bytes);
            // Rough guard to prevent overflows
            assert(new_align);
            result_type = adjust_ptr_align(ira->codegen, result_type, new_align);
        }

        if (op2_val != nullptr && op1_val != nullptr &&
            (op1->value->data.x_ptr.special == ConstPtrSpecialHardCodedAddr ||
            op1->value->data.x_ptr.special == ConstPtrSpecialNull))
        {
            uint64_t start_addr = (op1_val->data.x_ptr.special == ConstPtrSpecialNull) ?
                0 : op1_val->data.x_ptr.data.hard_coded_addr.addr;
            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();
            }
            Stage1AirInst *result = ir_const(ira, instruction->base.scope, instruction->base.source_node, result_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;
        }

        return ir_build_bin_op_gen(ira, instruction->base.scope, instruction->base.source_node, result_type, op_id, op1, casted_op2, true);
    }

    Stage1AirInst *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_inst_gen;

    ZigType *scalar_type = (resolved_type->id == ZigTypeIdVector) ?
        resolved_type->data.vector.elem_type : resolved_type;

    bool is_int = scalar_type->id == ZigTypeIdInt || scalar_type->id == ZigTypeIdComptimeInt;
    bool is_float = scalar_type->id == ZigTypeIdFloat || scalar_type->id == ZigTypeIdComptimeFloat;

    if (!is_int && !(is_float && ok_float_op(op_id))) {
        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_inst_gen;
    }

    Stage1AirInst *casted_op1 = ir_implicit_cast(ira, op1, resolved_type);
    if (type_is_invalid(casted_op1->value->type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *casted_op2 = ir_implicit_cast(ira, op2, resolved_type);
    if (type_is_invalid(casted_op2->value->type))
        return ira->codegen->invalid_inst_gen;

    // Comptime integers have no fixed size
    if (scalar_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;
        }
    }

    if (instr_is_comptime(casted_op1) && instr_is_comptime(casted_op2)) {
        ZigValue *op1_val = ir_resolve_const(ira, casted_op1, UndefBad);
        if (op1_val == nullptr)
            return ira->codegen->invalid_inst_gen;

        ZigValue *op2_val = ir_resolve_const(ira, casted_op2, UndefBad);
        if (op2_val == nullptr)
            return ira->codegen->invalid_inst_gen;

        // Promote division with negative numbers to signed
        bool is_signed_div = value_cmp_numeric_val_any(op1_val, CmpLT, nullptr) ||
            value_cmp_numeric_val_any(op2_val, CmpLT, nullptr);

        if (op_id == IrBinOpDivUnspecified && is_int) {
            // Default to truncating division and check if it's valid for the
            // given operands if signed
            op_id = IrBinOpDivTrunc;

            if (is_signed_div) {
                bool ok = false;

                if (value_cmp_numeric_val_any(op2_val, CmpEQ, nullptr)) {
                    // the division by zero error will be caught later, but we don't have a
                    // division function ambiguity problem.
                    ok = true;
                } else {
                    Stage1AirInst *trunc_val = ir_analyze_math_op(ira, instruction->base.scope, instruction->base.source_node, resolved_type,
                        op1_val, IrBinOpDivTrunc, op2_val);
                    if (type_is_invalid(trunc_val->value->type))
                        return ira->codegen->invalid_inst_gen;

                    Stage1AirInst *floor_val = ir_analyze_math_op(ira, instruction->base.scope, instruction->base.source_node, resolved_type,
                        op1_val, IrBinOpDivFloor, op2_val);
                    if (type_is_invalid(floor_val->value->type))
                        return ira->codegen->invalid_inst_gen;

                    Stage1AirInst *cmp_val = ir_analyze_bin_op_cmp_numeric(ira, instruction->base.scope, instruction->base.source_node,
                        trunc_val, floor_val, IrBinOpCmpEq);
                    if (type_is_invalid(cmp_val->value->type))
                        return ira->codegen->invalid_inst_gen;

                    // We can "upgrade" the operator only if trunc(a/b) == floor(a/b)
                    if (!ir_resolve_bool(ira, cmp_val, &ok))
                        return ira->codegen->invalid_inst_gen;
                }

                if (!ok) {
                    ir_add_error_node(ira, instruction->base.source_node,
                        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_inst_gen;
                }
            }
        } else if (op_id == IrBinOpRemUnspecified) {
            op_id = IrBinOpRemRem;

            if (is_signed_div) {
                bool ok = false;

                if (value_cmp_numeric_val_any(op2_val, CmpEQ, nullptr)) {
                    // the division by zero error will be caught later, but we don't have a
                    // division function ambiguity problem.
                    ok = true;
                } else {
                    Stage1AirInst *rem_val = ir_analyze_math_op(ira, instruction->base.scope, instruction->base.source_node, resolved_type,
                        op1_val, IrBinOpRemRem, op2_val);
                    if (type_is_invalid(rem_val->value->type))
                        return ira->codegen->invalid_inst_gen;

                    Stage1AirInst *mod_val = ir_analyze_math_op(ira, instruction->base.scope, instruction->base.source_node, resolved_type,
                        op1_val, IrBinOpRemMod, op2_val);
                    if (type_is_invalid(mod_val->value->type))
                        return ira->codegen->invalid_inst_gen;

                    Stage1AirInst *cmp_val = ir_analyze_bin_op_cmp_numeric(ira, instruction->base.scope, instruction->base.source_node,
                        rem_val, mod_val, IrBinOpCmpEq);
                    if (type_is_invalid(cmp_val->value->type))
                        return ira->codegen->invalid_inst_gen;

                    // We can "upgrade" the operator only if mod(a,b) == rem(a,b)
                    if (!ir_resolve_bool(ira, cmp_val, &ok))
                        return ira->codegen->invalid_inst_gen;
                }

                if (!ok) {
                    ir_add_error_node(ira, instruction->base.source_node,
                        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_inst_gen;
                }
            }
        }

        return ir_analyze_math_op(ira, instruction->base.scope, instruction->base.source_node, resolved_type, op1_val, op_id, op2_val);
    }

    const bool is_signed_div =
        (scalar_type->id == ZigTypeIdInt && scalar_type->data.integral.is_signed) ||
        scalar_type->id == ZigTypeIdFloat;

    // Warn the user to use the proper operators here
    if (op_id == IrBinOpDivUnspecified && is_int) {
        op_id = IrBinOpDivTrunc;

        if (is_signed_div) {
            ir_add_error_node(ira, instruction->base.source_node,
                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_inst_gen;
        }
    } else if (op_id == IrBinOpRemUnspecified) {
        op_id = IrBinOpRemRem;

        if (is_signed_div) {
            ir_add_error_node(ira, instruction->base.source_node,
                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_inst_gen;
        }
    }

    return ir_build_bin_op_gen(ira, instruction->base.scope, instruction->base.source_node, resolved_type,
            op_id, casted_op1, casted_op2, instruction->safety_check_on);
}

static Stage1AirInst *ir_analyze_tuple_cat(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        Stage1AirInst *op1, Stage1AirInst *op2)
{
    Error err;
    ZigType *op1_type = op1->value->type;
    ZigType *op2_type = op2->value->type;

    uint32_t op1_field_count = op1_type->data.structure.src_field_count;
    uint32_t op2_field_count = op2_type->data.structure.src_field_count;

    Buf *bare_name = buf_alloc();
    Buf *name = get_anon_type_name(ira->codegen, nullptr, container_string(ContainerKindStruct),
            scope, source_node, bare_name, nullptr);
    ZigType *new_type = get_partial_container_type(ira->codegen, scope,
        ContainerKindStruct, source_node, buf_ptr(name), bare_name, ContainerLayoutAuto);
    new_type->data.structure.special = StructSpecialInferredTuple;
    new_type->data.structure.resolve_status = ResolveStatusBeingInferred;
    uint32_t new_field_count = op1_field_count + op2_field_count;

    new_type->data.structure.src_field_count = new_field_count;
    new_type->data.structure.fields = realloc_type_struct_fields(new_type->data.structure.fields,
            0, new_field_count);

    Stage1AirInst *new_struct_ptr = ir_resolve_result(ira, ira->suspend_source_instr, no_result_loc(),
            new_type, nullptr, false, true);

    for (uint32_t i = 0; i < new_field_count; i += 1) {
        TypeStructField *src_field;
        if (i < op1_field_count) {
            src_field = op1_type->data.structure.fields[i];
        } else {
            src_field = op2_type->data.structure.fields[i - op1_field_count];
        }
        TypeStructField *new_field = new_type->data.structure.fields[i];
        new_field->name = buf_sprintf("%" PRIu32, i);
        new_field->type_entry = src_field->type_entry;
        new_field->type_val = src_field->type_val;
        new_field->src_index = i;
        new_field->decl_node = src_field->decl_node;
        new_field->init_val = src_field->init_val;
        new_field->is_comptime = src_field->is_comptime;
    }
    if ((err = type_resolve(ira->codegen, new_type, ResolveStatusZeroBitsKnown)))
        return ira->codegen->invalid_inst_gen;

    ZigList<Stage1AirInst *> const_ptrs = {};
    for (uint32_t i = 0; i < new_field_count; i += 1) {
        TypeStructField *dst_field = new_type->data.structure.fields[i];
        Stage1AirInst *src_struct_op;
        TypeStructField *src_field;
        if (i < op1_field_count) {
            src_field = op1_type->data.structure.fields[i];
            src_struct_op = op1;
        } else {
            src_field = op2_type->data.structure.fields[i - op1_field_count];
            src_struct_op = op2;
        }
        Stage1AirInst *field_value = ir_analyze_struct_value_field_value(ira, scope, source_node,
                src_struct_op, src_field);
        if (type_is_invalid(field_value->value->type))
            return ira->codegen->invalid_inst_gen;
        Stage1AirInst *dest_ptr = ir_analyze_struct_field_ptr(ira, scope, source_node, dst_field,
                new_struct_ptr, new_type, true);
        if (type_is_invalid(dest_ptr->value->type))
            return ira->codegen->invalid_inst_gen;
        if (instr_is_comptime(field_value)) {
            const_ptrs.append(dest_ptr);
        }
        Stage1AirInst *store_ptr_inst = ir_analyze_store_ptr(ira, scope, source_node, dest_ptr, field_value,
                true);
        if (type_is_invalid(store_ptr_inst->value->type))
            return ira->codegen->invalid_inst_gen;
    }
    if (const_ptrs.length != new_field_count) {
        new_struct_ptr->value->special = ConstValSpecialRuntime;
        for (size_t i = 0; i < const_ptrs.length; i += 1) {
            Stage1AirInst *elem_result_loc = const_ptrs.at(i);
            assert(elem_result_loc->value->special == ConstValSpecialStatic);
            if (elem_result_loc->value->type->data.pointer.inferred_struct_field != nullptr) {
                // This field will be generated comptime; no need to do this.
                continue;
            }
            Stage1AirInst *deref = ir_get_deref(ira, elem_result_loc->scope,
                    elem_result_loc->source_node, elem_result_loc, nullptr);
            if (!type_requires_comptime(ira->codegen, elem_result_loc->value->type->data.pointer.child_type)) {
                elem_result_loc->value->special = ConstValSpecialRuntime;
            }
            ir_analyze_store_ptr(ira, elem_result_loc->scope, elem_result_loc->source_node, elem_result_loc, deref, true);
        }
    }

    const_ptrs.deinit();

    return ir_get_deref(ira, scope, source_node, new_struct_ptr, nullptr);
}

static Stage1AirInst *ir_analyze_array_cat(IrAnalyze *ira, Stage1ZirInstBinOp *instruction) {
    Stage1AirInst *op1 = instruction->op1->child;
    ZigType *op1_type = op1->value->type;
    if (type_is_invalid(op1_type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *op2 = instruction->op2->child;
    ZigType *op2_type = op2->value->type;
    if (type_is_invalid(op2_type))
        return ira->codegen->invalid_inst_gen;

    if (is_tuple(op1_type) && is_tuple(op2_type)) {
        return ir_analyze_tuple_cat(ira, instruction->base.scope, instruction->base.source_node, op1, op2);
    }

    ZigValue *op1_val = ir_resolve_const(ira, op1, UndefBad);
    if (!op1_val)
        return ira->codegen->invalid_inst_gen;

    ZigValue *op2_val = ir_resolve_const(ira, op2, UndefBad);
    if (!op2_val)
        return ira->codegen->invalid_inst_gen;

    ZigValue *sentinel1 = nullptr;
    ZigValue *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;
        sentinel1 = op1_type->data.array.sentinel;
    } else if (op1_type->id == ZigTypeIdPointer &&
        op1_type->data.pointer.child_type == ira->codegen->builtin_types.entry_u8 &&
        op1_type->data.pointer.sentinel != nullptr &&
        op1_val->data.x_ptr.special == ConstPtrSpecialBaseArray)
    {
        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;
        sentinel1 = op1_type->data.pointer.sentinel;
    } 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;
        ZigValue *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;
        ZigValue *len_val = op1_val->data.x_struct.fields[slice_len_index];
        op1_array_end = op1_array_index + bigint_as_usize(&len_val->data.x_bigint);
        sentinel1 = ptr_type->data.pointer.sentinel;
    } else if (op1_type->id == ZigTypeIdPointer &&
               op1_type->data.pointer.ptr_len == PtrLenSingle &&
               op1_type->data.pointer.child_type->id == ZigTypeIdArray)
    {
        ZigType *array_type = op1_type->data.pointer.child_type;
        child_type = array_type->data.array.child_type;
        op1_array_val = const_ptr_pointee(ira, ira->codegen, op1_val, op1->source_node);
        if (op1_array_val == nullptr)
            return ira->codegen->invalid_inst_gen;
        op1_array_index = 0;
        op1_array_end = array_type->data.array.len;
        sentinel1 = array_type->data.array.sentinel;
    } else {
        ir_add_error(ira, op1, buf_sprintf("expected array, found '%s'", buf_ptr(&op1->value->type->name)));
        return ira->codegen->invalid_inst_gen;
    }

    ZigValue *sentinel2 = nullptr;
    ZigValue *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;
        sentinel2 = op2_type->data.array.sentinel;
    } else if (op2_type->id == ZigTypeIdPointer &&
        op2_type->data.pointer.sentinel != nullptr &&
        op2_val->data.x_ptr.special == ConstPtrSpecialBaseArray)
    {
        op2_type_valid = op2_type->data.pointer.child_type == child_type;
        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;

        sentinel2 = op2_type->data.pointer.sentinel;
    } 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;
        ZigValue *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;
        ZigValue *len_val = op2_val->data.x_struct.fields[slice_len_index];
        op2_array_end = op2_array_index + bigint_as_usize(&len_val->data.x_bigint);

        sentinel2 = ptr_type->data.pointer.sentinel;
    } else if (op2_type->id == ZigTypeIdPointer && op2_type->data.pointer.ptr_len == PtrLenSingle &&
            op2_type->data.pointer.child_type->id == ZigTypeIdArray)
    {
        ZigType *array_type = op2_type->data.pointer.child_type;
        op2_type_valid = array_type->data.array.child_type == child_type;
        op2_array_val = const_ptr_pointee(ira, ira->codegen, op2_val, op2->source_node);
        if (op2_array_val == nullptr)
            return ira->codegen->invalid_inst_gen;
        op2_array_index = 0;
        op2_array_end = array_type->data.array.len;

        sentinel2 = array_type->data.array.sentinel;
    } 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_inst_gen;
    }
    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_inst_gen;
    }

    ZigValue *sentinel;
    if (sentinel1 != nullptr && sentinel2 != nullptr) {
        // When there is a sentinel mismatch, no sentinel on the result. The type system
        // will catch this if it is a problem.
        sentinel = const_values_equal(ira->codegen, sentinel1, sentinel2) ? sentinel1 : nullptr;
    } else if (sentinel1 != nullptr) {
        sentinel = sentinel1;
    } else if (sentinel2 != nullptr) {
        sentinel = sentinel2;
    } else {
        sentinel = nullptr;
    }

    // The type of result is populated in the following if blocks
    Stage1AirInst *result = ir_const(ira, instruction->base.scope, instruction->base.source_node, nullptr);
    ZigValue *out_val = result->value;

    ZigValue *out_array_val;
    size_t new_len = (op1_array_end - op1_array_index) + (op2_array_end - op2_array_index);
    if (op1_type->id == ZigTypeIdPointer || op2_type->id == ZigTypeIdPointer) {
        out_array_val = ira->codegen->pass1_arena->create<ZigValue>();
        out_array_val->special = ConstValSpecialStatic;
        out_array_val->type = get_array_type(ira->codegen, child_type, new_len, sentinel);

        out_val->data.x_ptr.special = ConstPtrSpecialRef;
        out_val->data.x_ptr.data.ref.pointee = out_array_val;
        out_val->type = get_pointer_to_type(ira->codegen, out_array_val->type, true);
    } else if (is_slice(op1_type) || is_slice(op2_type)) {
        ZigType *ptr_type = get_pointer_to_type_extra2(ira->codegen, child_type,
                true, false, PtrLenUnknown, 0, 0, 0, false,
                VECTOR_INDEX_NONE, nullptr, sentinel);
        result->value->type = get_slice_type(ira->codegen, ptr_type);
        out_array_val = ira->codegen->pass1_arena->create<ZigValue>();
        out_array_val->special = ConstValSpecialStatic;
        out_array_val->type = get_array_type(ira->codegen, child_type, new_len, sentinel);

        out_val->data.x_struct.fields = alloc_const_vals_ptrs(ira->codegen, 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 if (op1_type->id == ZigTypeIdArray || op2_type->id == ZigTypeIdArray) {
        result->value->type = get_array_type(ira->codegen, child_type, new_len, sentinel);
        out_array_val = out_val;
    } else {
        result->value->type = get_pointer_to_type_extra2(ira->codegen, child_type, true, false, PtrLenUnknown,
                0, 0, 0, false, VECTOR_INDEX_NONE, nullptr, sentinel);
        out_array_val = ira->codegen->pass1_arena->create<ZigValue>();
        out_array_val->special = ConstValSpecialStatic;
        out_array_val->type = get_array_type(ira->codegen, child_type, new_len, sentinel);
        out_val->data.x_ptr.special = ConstPtrSpecialBaseArray;
        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;
    }

    uint64_t full_len = new_len + ((sentinel != nullptr) ? 1 : 0);
    out_array_val->data.x_array.data.s_none.elements = ira->codegen->pass1_arena->allocate<ZigValue>(full_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) {
        ZigValue *elem_dest_val = &out_array_val->data.x_array.data.s_none.elements[next_index];
        copy_const_val(ira->codegen, elem_dest_val, &op1_array_val->data.x_array.data.s_none.elements[i]);
        elem_dest_val->parent.id = ConstParentIdArray;
        elem_dest_val->parent.data.p_array.array_val = out_array_val;
        elem_dest_val->parent.data.p_array.elem_index = next_index;
    }
    for (size_t i = op2_array_index; i < op2_array_end; i += 1, next_index += 1) {
        ZigValue *elem_dest_val = &out_array_val->data.x_array.data.s_none.elements[next_index];
        copy_const_val(ira->codegen, elem_dest_val, &op2_array_val->data.x_array.data.s_none.elements[i]);
        elem_dest_val->parent.id = ConstParentIdArray;
        elem_dest_val->parent.data.p_array.array_val = out_array_val;
        elem_dest_val->parent.data.p_array.elem_index = next_index;
    }
    if (next_index < full_len) {
        ZigValue *elem_dest_val = &out_array_val->data.x_array.data.s_none.elements[next_index];
        copy_const_val(ira->codegen, elem_dest_val, sentinel);
        elem_dest_val->parent.id = ConstParentIdArray;
        elem_dest_val->parent.data.p_array.array_val = out_array_val;
        elem_dest_val->parent.data.p_array.elem_index = next_index;
        next_index += 1;
    }
    assert(next_index == full_len);

    return result;
}

static Stage1AirInst *ir_analyze_tuple_mult(IrAnalyze *ira, Scope *scope, AstNode *source_node,
                                        Stage1AirInst *op1, Stage1AirInst *op2)
{
    Error err;
    ZigType *op1_type = op1->value->type;
    uint64_t op1_field_count = op1_type->data.structure.src_field_count;

    uint64_t mult_amt;
    if (!ir_resolve_usize(ira, op2, &mult_amt))
        return ira->codegen->invalid_inst_gen;

    uint64_t new_field_count;
    if (mul_u64_overflow(op1_field_count, mult_amt, &new_field_count)) {
        ir_add_error_node(ira, source_node, buf_sprintf("operation results in overflow"));
        return ira->codegen->invalid_inst_gen;
    }

    Buf *bare_name = buf_alloc();
    Buf *name = get_anon_type_name(ira->codegen, nullptr, container_string(ContainerKindStruct),
        scope, source_node, bare_name, nullptr);
    ZigType *new_type = get_partial_container_type(ira->codegen, scope,
        ContainerKindStruct, source_node, buf_ptr(name), bare_name, ContainerLayoutAuto);
    new_type->data.structure.special = StructSpecialInferredTuple;
    new_type->data.structure.resolve_status = ResolveStatusBeingInferred;
    new_type->data.structure.src_field_count = new_field_count;
    new_type->data.structure.fields = realloc_type_struct_fields(
        new_type->data.structure.fields, 0, new_field_count);

    Stage1AirInst *new_struct_ptr = ir_resolve_result(ira, ira->suspend_source_instr, no_result_loc(),
        new_type, nullptr, false, true);

    for (uint64_t i = 0; i < new_field_count; i += 1) {
        TypeStructField *src_field = op1_type->data.structure.fields[i % op1_field_count];
        TypeStructField *new_field = new_type->data.structure.fields[i];

        new_field->name = buf_sprintf("%" ZIG_PRI_u64, i);
        new_field->type_entry = src_field->type_entry;
        new_field->type_val = src_field->type_val;
        new_field->src_index = i;
        new_field->decl_node = src_field->decl_node;
        new_field->init_val = src_field->init_val;
        new_field->is_comptime = src_field->is_comptime;
    }

    if ((err = type_resolve(ira->codegen, new_type, ResolveStatusZeroBitsKnown)))
        return ira->codegen->invalid_inst_gen;

    ZigList<Stage1AirInst *> const_ptrs = {};
    for (uint64_t i = 0; i < new_field_count; i += 1) {
        TypeStructField *src_field = op1_type->data.structure.fields[i % op1_field_count];
        TypeStructField *dst_field = new_type->data.structure.fields[i];

        Stage1AirInst *field_value = ir_analyze_struct_value_field_value(
            ira, scope, source_node, op1, src_field);
        if (type_is_invalid(field_value->value->type))
            return ira->codegen->invalid_inst_gen;

        Stage1AirInst *dest_ptr = ir_analyze_struct_field_ptr(
            ira, scope, source_node, dst_field, new_struct_ptr, new_type, true);
        if (type_is_invalid(dest_ptr->value->type))
            return ira->codegen->invalid_inst_gen;

        if (instr_is_comptime(field_value)) {
            const_ptrs.append(dest_ptr);
        }

        Stage1AirInst *store_ptr_inst = ir_analyze_store_ptr(
            ira, scope, source_node, dest_ptr, field_value, true);
        if (type_is_invalid(store_ptr_inst->value->type))
            return ira->codegen->invalid_inst_gen;
    }

    if (const_ptrs.length != new_field_count) {
        new_struct_ptr->value->special = ConstValSpecialRuntime;
        for (size_t i = 0; i < const_ptrs.length; i += 1) {
            Stage1AirInst *elem_result_loc = const_ptrs.at(i);
            assert(elem_result_loc->value->special == ConstValSpecialStatic);
            if (elem_result_loc->value->type->data.pointer.inferred_struct_field != nullptr) {
                // This field will be generated comptime; no need to do this.
                continue;
            }
            Stage1AirInst *deref = ir_get_deref(ira, elem_result_loc->scope,
                    elem_result_loc->source_node, elem_result_loc, nullptr);
            if (!type_requires_comptime(ira->codegen, elem_result_loc->value->type->data.pointer.child_type)) {
                elem_result_loc->value->special = ConstValSpecialRuntime;
            }
            Stage1AirInst *store_ptr_inst = ir_analyze_store_ptr(ira, elem_result_loc->scope,
                    elem_result_loc->source_node, elem_result_loc, deref, true);
            if (type_is_invalid(store_ptr_inst->value->type))
                return ira->codegen->invalid_inst_gen;
        }
    }

    const_ptrs.deinit();

    return ir_get_deref(ira, scope, source_node, new_struct_ptr, nullptr);
}

static Stage1AirInst *ir_analyze_array_mult(IrAnalyze *ira, Stage1ZirInstBinOp *instruction) {
    Stage1AirInst *op1 = instruction->op1->child;
    if (type_is_invalid(op1->value->type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *op2 = instruction->op2->child;
    if (type_is_invalid(op2->value->type))
        return ira->codegen->invalid_inst_gen;

    bool want_ptr_to_array = false;
    ZigType *array_type;
    ZigValue *array_val;
    if (op1->value->type->id == ZigTypeIdArray) {
        array_type = op1->value->type;
        array_val = ir_resolve_const(ira, op1, UndefOk);
        if (array_val == nullptr)
            return ira->codegen->invalid_inst_gen;
    } else if (op1->value->type->id == ZigTypeIdPointer &&
               op1->value->type->data.pointer.ptr_len == PtrLenSingle &&
               op1->value->type->data.pointer.child_type->id == ZigTypeIdArray)
    {
        array_type = op1->value->type->data.pointer.child_type;
        Stage1AirInst *array_inst = ir_get_deref(ira, op1->scope, op1->source_node, op1, nullptr);
        if (type_is_invalid(array_inst->value->type))
            return ira->codegen->invalid_inst_gen;
        array_val = ir_resolve_const(ira, array_inst, UndefOk);
        if (array_val == nullptr)
            return ira->codegen->invalid_inst_gen;
        want_ptr_to_array = true;
    } else if (is_tuple(op1->value->type)) {
        return ir_analyze_tuple_mult(ira, instruction->base.scope, instruction->base.source_node, op1, op2);
    } else {
        ir_add_error(ira, op1, buf_sprintf("expected array type, found '%s'", buf_ptr(&op1->value->type->name)));
        return ira->codegen->invalid_inst_gen;
    }

    uint64_t mult_amt;
    if (!ir_resolve_usize(ira, op2, &mult_amt))
        return ira->codegen->invalid_inst_gen;

    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_node(ira, instruction->base.source_node, buf_sprintf("operation results in overflow"));
        return ira->codegen->invalid_inst_gen;
    }

    ZigType *child_type = array_type->data.array.child_type;
    ZigType *result_array_type = get_array_type(ira->codegen, child_type, new_array_len,
            array_type->data.array.sentinel);

    Stage1AirInst *array_result;
    if (array_val->special == ConstValSpecialUndef || array_val->data.x_array.special == ConstArraySpecialUndef) {
        array_result = ir_const_undef(ira, instruction->base.scope, instruction->base.source_node, result_array_type);
    } else {
        array_result = ir_const(ira, instruction->base.scope, instruction->base.source_node, result_array_type);
        ZigValue *out_val = array_result->value;

        switch (type_has_one_possible_value(ira->codegen, result_array_type)) {
            case OnePossibleValueInvalid:
                return ira->codegen->invalid_inst_gen;
            case OnePossibleValueYes:
                goto skip_computation;
            case OnePossibleValueNo:
                break;
        }

        // TODO optimize the buf case
        expand_undef_array(ira->codegen, array_val);
        size_t extra_null_term = (array_type->data.array.sentinel != nullptr) ? 1 : 0;
        out_val->data.x_array.data.s_none.elements = ira->codegen->pass1_arena->allocate<ZigValue>(new_array_len + extra_null_term);

        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) {
                ZigValue *elem_dest_val = &out_val->data.x_array.data.s_none.elements[i];
                copy_const_val(ira->codegen, elem_dest_val, &array_val->data.x_array.data.s_none.elements[y]);
                elem_dest_val->parent.id = ConstParentIdArray;
                elem_dest_val->parent.data.p_array.array_val = out_val;
                elem_dest_val->parent.data.p_array.elem_index = i;
                i += 1;
            }
        }
        assert(i == new_array_len);

        if (array_type->data.array.sentinel != nullptr) {
            ZigValue *elem_dest_val = &out_val->data.x_array.data.s_none.elements[i];
            copy_const_val(ira->codegen, elem_dest_val, array_type->data.array.sentinel);
            elem_dest_val->parent.id = ConstParentIdArray;
            elem_dest_val->parent.data.p_array.array_val = out_val;
            elem_dest_val->parent.data.p_array.elem_index = i;
            i += 1;
        }
    }
skip_computation:
    if (want_ptr_to_array) {
        return ir_get_ref(ira, instruction->base.scope, instruction->base.source_node, array_result, true, false);
    } else {
        return array_result;
    }
}

static Stage1AirInst *ir_analyze_instruction_merge_err_sets(IrAnalyze *ira,
        Stage1ZirInstMergeErrSets *instruction)
{
    ZigType *op1_type = ir_resolve_error_set_type(ira, instruction->base.source_node, instruction->op1->child);
    if (type_is_invalid(op1_type))
        return ira->codegen->invalid_inst_gen;

    ZigType *op2_type = ir_resolve_error_set_type(ira, instruction->base.source_node, instruction->op2->child);
    if (type_is_invalid(op2_type))
        return ira->codegen->invalid_inst_gen;

    if (!resolve_inferred_error_set(ira->codegen, op1_type, instruction->op1->child->source_node)) {
        return ira->codegen->invalid_inst_gen;
    }

    if (!resolve_inferred_error_set(ira->codegen, op2_type, instruction->op2->child->source_node)) {
        return ira->codegen->invalid_inst_gen;
    }

    if (type_is_global_error_set(op1_type) ||
        type_is_global_error_set(op2_type))
    {
        return ir_const_type(ira, instruction->base.scope, instruction->base.source_node, ira->codegen->builtin_types.entry_global_error_set);
    }

    size_t errors_count = ira->codegen->errors_by_index.length;
    ErrorTableEntry **errors = heap::c_allocator.allocate<ErrorTableEntry *>(errors_count);
    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, instruction->type_name);
    heap::c_allocator.deallocate(errors, errors_count);

    return ir_const_type(ira, instruction->base.scope, instruction->base.source_node, result_type);
}


static Stage1AirInst *ir_analyze_instruction_bin_op(IrAnalyze *ira, Stage1ZirInstBinOp *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:
        case IrBinOpShlSat:
            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:
        case IrBinOpMaximum:
        case IrBinOpMinimum:
        case IrBinOpAddSat:
        case IrBinOpSubSat:
        case IrBinOpMultSat:
            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);
    }
    zig_unreachable();
}

static Stage1AirInst *ir_analyze_instruction_decl_var(IrAnalyze *ira, Stage1ZirInstDeclVar *decl_var_instruction) {
    Error err;
    ZigVar *var = decl_var_instruction->var;

    ZigType *explicit_type = nullptr;
    Stage1AirInst *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->decl_node->data.variable_declaration, proposed_type);
        if (type_is_invalid(explicit_type)) {
            var->var_type = ira->codegen->builtin_types.entry_invalid;
            return ira->codegen->invalid_inst_gen;
        }
    }

    AstNode *source_node = decl_var_instruction->base.source_node;

    bool is_comptime_var = ir_get_var_is_comptime(var);

    bool var_class_requires_const = false;

    Stage1AirInst *var_ptr = decl_var_instruction->ptr->child;
    // if this is null, a compiler error happened and did not initialize the variable.
    // if there are no compile errors there may be a missing ir_expr_wrap in pass1 IR generation.
    if (var_ptr == nullptr || type_is_invalid(var_ptr->value->type)) {
        src_assert(var_ptr != nullptr || ira->codegen->errors.length != 0,
                decl_var_instruction->base.source_node);
        var->var_type = ira->codegen->builtin_types.entry_invalid;
        return ira->codegen->invalid_inst_gen;
    }

    // The ir_build_var_decl_src call is supposed to pass a pointer to the allocation, not an initialization value.
    src_assert(var_ptr->value->type->id == ZigTypeIdPointer, decl_var_instruction->base.source_node);

    ZigType *result_type = var_ptr->value->type->data.pointer.child_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) {
        zig_unreachable();
    }

    ZigValue *init_val = nullptr;
    if (instr_is_comptime(var_ptr) && var_ptr->value->data.x_ptr.mut != ConstPtrMutRuntimeVar) {
        ZigValue *ptr_val = ir_resolve_const(ira, var_ptr, UndefBad);
        if (ptr_val == nullptr)
            return ira->codegen->invalid_inst_gen;

        init_val = const_ptr_pointee(ira, ira->codegen, ptr_val, decl_var_instruction->base.source_node);
        if (init_val == nullptr)
            return ira->codegen->invalid_inst_gen;

        if (is_comptime_var) {
            if (var->gen_is_const) {
                var->const_value = init_val;
            } else {
                var->const_value = ira->codegen->pass1_arena->create<ZigValue>();
                copy_const_val(ira->codegen, var->const_value, init_val);
            }
        }
    }

    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) {
            ErrorMsg *msg = ir_add_error_node(ira, source_node,
                buf_sprintf("variable of type '%s' must be const or comptime",
                    buf_ptr(&result_type->name)));
            if(result_type->id == ZigTypeIdComptimeInt || result_type -> id == ZigTypeIdComptimeFloat) {
                add_error_note(ira->codegen, msg, source_node, buf_sprintf("to modify this variable at runtime, it must be given an explicit fixed-size number type"));
            }
            result_type = ira->codegen->builtin_types.entry_invalid;
        }
        break;
    case ReqCompTimeNo:
        if (init_val != nullptr && value_is_comptime(init_val)) {
            if ((err = ir_resolve_const_val(ira->codegen, ira->new_irb.exec,
                    decl_var_instruction->base.source_node, init_val, UndefOk)))
            {
                result_type = ira->codegen->builtin_types.entry_invalid;
            } else if (init_val->type->id == ZigTypeIdFn &&
                init_val->special != ConstValSpecialUndef &&
                init_val->data.x_ptr.special == ConstPtrSpecialFunction &&
                init_val->data.x_ptr.data.fn.fn_entry->type_entry->data.fn.fn_type_id.cc == CallingConventionInline)
            {
                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 = init_val->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;
    }

    while (var->next_var != nullptr) {
        var = var->next_var;
    }

    // This must be done after possibly creating a new variable above
    var->ref_count = 0;

    var->ptr_instruction = var_ptr;
    var->var_type = result_type;
    assert(var->var_type);

    if (type_is_invalid(result_type)) {
        return ir_const_void(ira, decl_var_instruction->base.scope, decl_var_instruction->base.source_node);
    }

    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.scope, decl_var_instruction->base.source_node);
        }
        var->align_bytes = get_ptr_align(ira->codegen, var_ptr->value->type);
    } else {
        if (!ir_resolve_align(ira, decl_var_instruction->align_value->child, nullptr, &var->align_bytes)) {
            var->var_type = ira->codegen->builtin_types.entry_invalid;
        }
    }

    if (init_val != nullptr && value_is_comptime(init_val)) {
        // Resolve ConstPtrMutInfer
        if (var->gen_is_const) {
            var_ptr->value->data.x_ptr.mut = ConstPtrMutComptimeConst;
        } else if (is_comptime_var) {
            var_ptr->value->data.x_ptr.mut = ConstPtrMutComptimeVar;
        } else {
            // we need a runtime ptr but we have a comptime val.
            // since it's a comptime val there are no instructions for it.
            // we memcpy the init value here
            Stage1AirInst *deref = ir_get_deref(ira, var_ptr->scope, var_ptr->source_node, var_ptr, nullptr);
            if (type_is_invalid(deref->value->type)) {
                var->var_type = ira->codegen->builtin_types.entry_invalid;
                return ira->codegen->invalid_inst_gen;
            }
            // If this assertion trips, something is wrong with the IR instructions, because
            // we expected the above deref to return a constant value, but it created a runtime
            // instruction.
            assert(deref->value->special != ConstValSpecialRuntime);
            var_ptr->value->special = ConstValSpecialRuntime;
            ir_analyze_store_ptr(ira, var_ptr->scope, var_ptr->source_node, var_ptr, deref, false);
        }
        if (instr_is_comptime(var_ptr) && (is_comptime_var || (var_class_requires_const && var->gen_is_const))) {
            return ir_const_void(ira, decl_var_instruction->base.scope, decl_var_instruction->base.source_node);
        }
    } else if (is_comptime_var) {
        ir_add_error_node(ira, decl_var_instruction->base.source_node,
                buf_sprintf("cannot store runtime value in compile time variable"));
        var->var_type = ira->codegen->builtin_types.entry_invalid;
        return ira->codegen->invalid_inst_gen;
    }

    ZigFn *fn_entry = ira->fn;
    if (fn_entry)
        fn_entry->variable_list.append(var);

    return ir_build_var_decl_gen(ira, decl_var_instruction->base.scope, decl_var_instruction->base.source_node, var, var_ptr);
}

static Stage1AirInst *ir_analyze_instruction_export(IrAnalyze *ira, Stage1ZirInstExport *instruction) {
    Stage1AirInst *target = instruction->target->child;
    if (type_is_invalid(target->value->type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *options = instruction->options->child;
    if (type_is_invalid(options->value->type))
        return ira->codegen->invalid_inst_gen;

    ZigType *options_type = options->value->type;
    assert(options_type->id == ZigTypeIdStruct);

    TypeStructField *name_field = find_struct_type_field(options_type, buf_create_from_str("name"));
    src_assert(name_field != nullptr, instruction->base.source_node);
    Stage1AirInst *name_inst = ir_analyze_struct_value_field_value(ira, instruction->base.scope, instruction->base.source_node, options, name_field);
    if (type_is_invalid(name_inst->value->type))
        return ira->codegen->invalid_inst_gen;

    TypeStructField *linkage_field = find_struct_type_field(options_type, buf_create_from_str("linkage"));
    src_assert(linkage_field != nullptr, instruction->base.source_node);
    Stage1AirInst *linkage_inst = ir_analyze_struct_value_field_value(ira, instruction->base.scope, instruction->base.source_node, options, linkage_field);
    if (type_is_invalid(linkage_inst->value->type))
        return ira->codegen->invalid_inst_gen;

    TypeStructField *section_field = find_struct_type_field(options_type, buf_create_from_str("section"));
    src_assert(section_field != nullptr, instruction->base.source_node);
    Stage1AirInst *section_inst = ir_analyze_struct_value_field_value(ira, instruction->base.scope, instruction->base.source_node, options, section_field);
    if (type_is_invalid(section_inst->value->type))
        return ira->codegen->invalid_inst_gen;

    // The `section` field is optional, we have to unwrap it first
    Stage1AirInst *non_null_check = ir_analyze_test_non_null(ira, instruction->base.scope, instruction->base.source_node, section_inst);
    bool is_non_null;
    if (!ir_resolve_bool(ira, non_null_check, &is_non_null))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *section_str_inst = nullptr;
    if (is_non_null) {
        section_str_inst = ir_analyze_optional_value_payload_value(ira, instruction->base.scope, instruction->base.source_node, section_inst, false);
        if (type_is_invalid(section_str_inst->value->type))
            return ira->codegen->invalid_inst_gen;
    }

    // Resolve all the comptime values
    Buf *symbol_name = ir_resolve_str(ira, name_inst);
    if (!symbol_name)
        return ira->codegen->invalid_inst_gen;

    if (buf_len(symbol_name) < 1) {
        ir_add_error(ira, name_inst,
            buf_sprintf("exported symbol name cannot be empty"));
        return ira->codegen->invalid_inst_gen;
    }

    GlobalLinkageId global_linkage_id;
    if (!ir_resolve_global_linkage(ira, linkage_inst, &global_linkage_id))
        return ira->codegen->invalid_inst_gen;

    Buf *section_name = nullptr;
    if (section_str_inst != nullptr && !(section_name = ir_resolve_str(ira, section_str_inst)))
        return ira->codegen->invalid_inst_gen;

    // 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 = heap::c_allocator.create<TldFn>();
    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_node(ira, instruction->base.source_node,
                buf_sprintf("exported symbol collision: '%s'", buf_ptr(symbol_name)));
        add_error_note(ira->codegen, msg, other_export_node, buf_sprintf("other symbol here"));
        return ira->codegen->invalid_inst_gen;
    }

    Error err;
    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 CallingConventionInline: {
                    ErrorMsg *msg = ir_add_error(ira, target,
                        buf_sprintf("exported function cannot be inline"));
                    add_error_note(ira->codegen, msg, fn_entry->proto_node, buf_sprintf("declared here"));
                } break;
                case CallingConventionC:
                case CallingConventionNaked:
                case CallingConventionInterrupt:
                case CallingConventionSignal:
                case CallingConventionStdcall:
                case CallingConventionFastcall:
                case CallingConventionVectorcall:
                case CallingConventionThiscall:
                case CallingConventionAPCS:
                case CallingConventionAAPCS:
                case CallingConventionAAPCSVFP:
                case CallingConventionSysV:
                    add_fn_export(ira->codegen, fn_entry, buf_ptr(symbol_name), global_linkage_id, cc);
                    fn_entry->section_name = section_name;
                    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 ((err = type_resolve(ira->codegen, target->value->type, ResolveStatusZeroBitsKnown)))
                return ira->codegen->invalid_inst_gen;
            if (!target->value->type->data.enumeration.has_explicit_tag_type) {
                ErrorMsg *msg = ir_add_error(ira, target,
                    buf_sprintf("exported enum value without explicit integer tag type"));
                add_error_note(ira->codegen, msg, target->value->type->data.enumeration.decl_node, buf_sprintf("declared here"));
            } else {
                want_var_export = true;
            }
            break;
        case ZigTypeIdArray: {
            bool ok_type;
            if ((err = type_allowed_in_extern(ira->codegen, target->value->type->data.array.child_type, ExternPositionOther, &ok_type)))
                return ira->codegen->invalid_inst_gen;

            if (!ok_type) {
                ir_add_error(ira, target,
                    buf_sprintf("array element type '%s' not extern-compatible",
                        buf_ptr(&target->value->type->data.array.child_type->name)));
            } 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 ((err = type_resolve(ira->codegen, type_value, ResolveStatusZeroBitsKnown)))
                        return ira->codegen->invalid_inst_gen;
                    if (!type_value->data.enumeration.has_explicit_tag_type) {
                        ErrorMsg *msg = ir_add_error(ira, target,
                            buf_sprintf("exported enum without explicit integer tag type"));
                        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 ZigTypeIdEnumLiteral:
                case ZigTypeIdUndefined:
                case ZigTypeIdNull:
                case ZigTypeIdOptional:
                case ZigTypeIdErrorUnion:
                case ZigTypeIdErrorSet:
                case ZigTypeIdBoundFn:
                case ZigTypeIdOpaque:
                case ZigTypeIdFnFrame:
                case ZigTypeIdAnyFrame:
                    ir_add_error(ira, target,
                        buf_sprintf("invalid export target '%s'", buf_ptr(&type_value->name)));
                    break;
            }
        } break;
        case ZigTypeIdInt:
            want_var_export = true;
            break;
        case ZigTypeIdVoid:
        case ZigTypeIdBool:
        case ZigTypeIdFloat:
        case ZigTypeIdPointer:
        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 ZigTypeIdBoundFn:
        case ZigTypeIdOpaque:
        case ZigTypeIdEnumLiteral:
        case ZigTypeIdFnFrame:
        case ZigTypeIdAnyFrame:
            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 == Stage1AirInstIdLoadPtr) {
        Stage1AirInstLoadPtr *load_ptr = reinterpret_cast<Stage1AirInstLoadPtr *>(target);
        if (load_ptr->ptr->id == Stage1AirInstIdVarPtr) {
            Stage1AirInstVarPtr *var_ptr = reinterpret_cast<Stage1AirInstVarPtr *>(load_ptr->ptr);
            ZigVar *var = var_ptr->var;
            add_var_export(ira->codegen, var, buf_ptr(symbol_name), global_linkage_id);
            var->section_name = section_name;
        }
    }

    return ir_const_void(ira, instruction->base.scope, instruction->base.source_node);
}

static void add_link_lib_symbol(IrAnalyze *ira, Buf *lib_name, Buf *symbol_name, AstNode *source_node);

static Stage1AirInst *ir_analyze_instruction_extern(IrAnalyze *ira, Stage1ZirInstExtern *instruction) {
    Stage1AirInst *type_inst = instruction->type->child;
    if (type_is_invalid(type_inst->value->type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *options = instruction->options->child;
    if (type_is_invalid(options->value->type))
        return ira->codegen->invalid_inst_gen;

    ZigType *options_type = options->value->type;
    assert(options_type->id == ZigTypeIdStruct);

    TypeStructField *name_field = find_struct_type_field(options_type, buf_create_from_str("name"));
    src_assert(name_field != nullptr, instruction->base.source_node);
    Stage1AirInst *name_inst = ir_analyze_struct_value_field_value(ira, instruction->base.scope, instruction->base.source_node, options, name_field);
    if (type_is_invalid(name_inst->value->type))
        return ira->codegen->invalid_inst_gen;

    TypeStructField *linkage_field = find_struct_type_field(options_type, buf_create_from_str("linkage"));
    src_assert(linkage_field != nullptr, instruction->base.source_node);
    Stage1AirInst *linkage_inst = ir_analyze_struct_value_field_value(ira, instruction->base.scope, instruction->base.source_node, options, linkage_field);
    if (type_is_invalid(linkage_inst->value->type))
        return ira->codegen->invalid_inst_gen;

    TypeStructField *is_thread_local_field = find_struct_type_field(options_type, buf_create_from_str("is_thread_local"));
    src_assert(is_thread_local_field != nullptr, instruction->base.source_node);
    Stage1AirInst *is_thread_local_inst = ir_analyze_struct_value_field_value(ira, instruction->base.scope, instruction->base.source_node, options, is_thread_local_field);
    if (type_is_invalid(is_thread_local_inst->value->type))
        return ira->codegen->invalid_inst_gen;

    TypeStructField *library_name_field = find_struct_type_field(options_type, buf_create_from_str("library_name"));
    src_assert(library_name_field != nullptr, instruction->base.source_node);
    Stage1AirInst *library_name_inst = ir_analyze_struct_value_field_value(ira, instruction->base.scope, instruction->base.source_node, options, library_name_field);
    if (type_is_invalid(library_name_inst->value->type))
        return ira->codegen->invalid_inst_gen;

    // The `library_name` field is optional, we have to unwrap it first
    Stage1AirInst *non_null_check = ir_analyze_test_non_null(ira, instruction->base.scope, instruction->base.source_node, library_name_inst);
    bool is_non_null;
    if (!ir_resolve_bool(ira, non_null_check, &is_non_null))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *library_name_val_inst = nullptr;
    if (is_non_null) {
        library_name_val_inst = ir_analyze_optional_value_payload_value(ira, instruction->base.scope, instruction->base.source_node, library_name_inst, false);
        if (type_is_invalid(library_name_val_inst->value->type))
            return ira->codegen->invalid_inst_gen;
    }

    // Resolve all the comptime values
    ZigType *value_type = ir_resolve_type(ira, type_inst);
    if (type_is_invalid(value_type))
        return ira->codegen->invalid_inst_gen;

    if (get_src_ptr_type(value_type) == nullptr) {
        ir_add_error(ira, name_inst,
            buf_sprintf("expected (optional) pointer type or function"));
        return ira->codegen->invalid_inst_gen;
    }

    Buf *symbol_name = ir_resolve_str(ira, name_inst);
    if (!symbol_name)
        return ira->codegen->invalid_inst_gen;

    if (buf_len(symbol_name) == 0) {
        ir_add_error(ira, name_inst,
            buf_sprintf("extern symbol name cannot be empty"));
        return ira->codegen->invalid_inst_gen;
    }

    Buf *library_name = nullptr;
    if (library_name_val_inst) {
        library_name = ir_resolve_str(ira, library_name_val_inst);
        if (!library_name)
            return ira->codegen->invalid_inst_gen;

        if (buf_len(library_name) == 0) {
            ir_add_error(ira, library_name_inst,
                buf_sprintf("library name name cannot be empty"));
            return ira->codegen->invalid_inst_gen;
        }

        add_link_lib_symbol(ira, library_name, symbol_name, instruction->base.source_node);

        buf_destroy(library_name);
    }

    GlobalLinkageId global_linkage_id;
    if (!ir_resolve_global_linkage(ira, linkage_inst, &global_linkage_id))
        return ira->codegen->invalid_inst_gen;

    bool is_thread_local;
    if (!ir_resolve_bool(ira, is_thread_local_inst, &is_thread_local))
        return ira->codegen->invalid_inst_gen;

    ZigType *expr_type = value_type;
    if (global_linkage_id == GlobalLinkageIdWeak && value_type->id != ZigTypeIdOptional)
        expr_type = get_optional_type(ira->codegen, expr_type);

    // Create a bogus Tld object to keep track of the extern symbol.
    // XXX: Find a better way to do this (in stage2).
    TldFn *tld_fn = heap::c_allocator.create<TldFn>();
    tld_fn->base.id = TldIdFn;
    tld_fn->base.source_node = instruction->base.source_node;

    auto entry = ira->codegen->external_symbol_names.put_unique(symbol_name, &tld_fn->base);
    if (entry) {
        AstNode *other_extern_node = entry->value->source_node;
        ErrorMsg *msg = ir_add_error_node(ira, instruction->base.source_node,
                buf_sprintf("extern symbol collision: '%s'", buf_ptr(symbol_name)));
        add_error_note(ira->codegen, msg, other_extern_node, buf_sprintf("other symbol here"));
        return ira->codegen->invalid_inst_gen;
    }

    return ir_build_extern_gen(ira, instruction->base.scope, instruction->base.source_node, symbol_name, global_linkage_id,
            is_thread_local, expr_type);
}

static bool ira_has_err_ret_trace(IrAnalyze *ira) {
    ZigFn *fn = ira->fn;
    return fn != nullptr && fn->calls_or_awaits_errorable_fn && ira->codegen->have_err_ret_tracing;
}

static Stage1AirInst *ir_analyze_instruction_error_return_trace(IrAnalyze *ira,
        Stage1ZirInstErrorReturnTrace *instruction)
{
    ZigType *ptr_to_stack_trace_type = get_pointer_to_type(ira->codegen, get_stack_trace_type(ira->codegen), false);
    if (instruction->optional == IrInstErrorReturnTraceNull) {
        ZigType *optional_type = get_optional_type(ira->codegen, ptr_to_stack_trace_type);
        if (!ira_has_err_ret_trace(ira)) {
            Stage1AirInst *result = ir_const(ira, instruction->base.scope, instruction->base.source_node, optional_type);
            ZigValue *out_val = result->value;
            assert(get_src_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;
        }
        return ir_build_error_return_trace_gen(ira, instruction->base.scope,
                instruction->base.source_node, instruction->optional, optional_type);
    } else {
        assert(ira->codegen->have_err_ret_tracing);
        return ir_build_error_return_trace_gen(ira, instruction->base.scope,
                instruction->base.source_node, instruction->optional, ptr_to_stack_trace_type);
    }
}

static Stage1AirInst *ir_analyze_instruction_error_union(IrAnalyze *ira, Stage1ZirInstErrorUnion *instruction) {
    Stage1AirInst *result = ir_const(ira, instruction->base.scope, instruction->base.source_node, ira->codegen->builtin_types.entry_type);
    result->value->special = ConstValSpecialLazy;

    LazyValueErrUnionType *lazy_err_union_type = heap::c_allocator.create<LazyValueErrUnionType>();
    lazy_err_union_type->ira = ira; ira_ref(ira);
    result->value->data.x_lazy = &lazy_err_union_type->base;
    lazy_err_union_type->base.id = LazyValueIdErrUnionType;

    lazy_err_union_type->err_set_type = instruction->err_set->child;
    if (ir_resolve_type_lazy(ira, lazy_err_union_type->err_set_type) == nullptr)
        return ira->codegen->invalid_inst_gen;

    lazy_err_union_type->payload_type = instruction->payload->child;
    if (ir_resolve_type_lazy(ira, lazy_err_union_type->payload_type) == nullptr)
        return ira->codegen->invalid_inst_gen;

    return result;
}

static Stage1AirInst *ir_analyze_alloca(IrAnalyze *ira, Scope *scope, AstNode *source_node, ZigType *var_type,
        uint32_t align, const char *name_hint, bool force_comptime)
{
    Error err;

    ZigValue *pointee = ira->codegen->pass1_arena->create<ZigValue>();
    pointee->special = ConstValSpecialUndef;
    pointee->llvm_align = align;

    Stage1AirInstAlloca *result = ir_build_alloca_gen(ira, scope, source_node, align, name_hint);
    result->base.value->special = ConstValSpecialStatic;
    result->base.value->data.x_ptr.special = ConstPtrSpecialRef;
    result->base.value->data.x_ptr.mut = force_comptime ? ConstPtrMutComptimeVar : ConstPtrMutInfer;
    result->base.value->data.x_ptr.data.ref.pointee = pointee;

    bool var_type_has_bits;
    if ((err = type_has_bits2(ira->codegen, var_type, &var_type_has_bits)))
        return ira->codegen->invalid_inst_gen;
    if (align != 0) {
        if ((err = type_resolve(ira->codegen, var_type, ResolveStatusAlignmentKnown)))
            return ira->codegen->invalid_inst_gen;
        if (!var_type_has_bits) {
            ir_add_error_node(ira, source_node,
                buf_sprintf("variable '%s' of zero-bit type '%s' has no in-memory representation, it cannot be aligned",
                    name_hint, buf_ptr(&var_type->name)));
            return ira->codegen->invalid_inst_gen;
        }
    }
    assert(result->base.value->data.x_ptr.special != ConstPtrSpecialInvalid);

    pointee->type = var_type;
    result->base.value->type = get_pointer_to_type_extra(ira->codegen, var_type, false, false,
            PtrLenSingle, align, 0, 0, false);

    if (!force_comptime) {
        ZigFn *fn_entry = ira->fn;
        if (fn_entry != nullptr) {
            fn_entry->alloca_gen_list.append(result);
        }
    }
    return &result->base;
}

static ZigType *ir_result_loc_expected_type(IrAnalyze *ira, ResultLoc *result_loc) {
    switch (result_loc->id) {
        case ResultLocIdInvalid:
        case ResultLocIdPeerParent:
            zig_unreachable();
        case ResultLocIdNone:
        case ResultLocIdVar:
        case ResultLocIdBitCast:
        case ResultLocIdCast:
            return nullptr;
        case ResultLocIdInstruction:
            return result_loc->source_instruction->child->value->type;
        case ResultLocIdReturn:
            return ira->explicit_return_type;
        case ResultLocIdPeer:
            return reinterpret_cast<ResultLocPeer*>(result_loc)->parent->resolved_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 ZigTypeIdUnreachable:
        case ZigTypeIdComptimeFloat:
        case ZigTypeIdComptimeInt:
        case ZigTypeIdEnumLiteral:
        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 void set_up_result_loc_for_inferred_comptime(IrAnalyze *ira, Stage1AirInst *ptr) {
    ZigValue *undef_child = ira->codegen->pass1_arena->create<ZigValue>();
    undef_child->type = ptr->value->type->data.pointer.child_type;
    undef_child->special = ConstValSpecialUndef;
    ptr->value->special = ConstValSpecialStatic;
    ptr->value->data.x_ptr.mut = ConstPtrMutInfer;
    ptr->value->data.x_ptr.special = ConstPtrSpecialRef;
    ptr->value->data.x_ptr.data.ref.pointee = undef_child;
}

static Error ir_result_has_type(IrAnalyze *ira, ResultLoc *result_loc, bool *out) {
    switch (result_loc->id) {
        case ResultLocIdInvalid:
        case ResultLocIdPeerParent:
            zig_unreachable();
        case ResultLocIdNone:
        case ResultLocIdPeer:
            *out = false;
            return ErrorNone;
        case ResultLocIdReturn:
        case ResultLocIdInstruction:
        case ResultLocIdBitCast:
            *out = true;
            return ErrorNone;
        case ResultLocIdCast: {
            ResultLocCast *result_cast = reinterpret_cast<ResultLocCast *>(result_loc);
            ZigType *dest_type = ir_resolve_type(ira, result_cast->base.source_instruction->child);
            if (type_is_invalid(dest_type))
                return ErrorSemanticAnalyzeFail;
            *out = (dest_type != ira->codegen->builtin_types.entry_anytype);
            return ErrorNone;
        }
        case ResultLocIdVar:
            *out = reinterpret_cast<ResultLocVar *>(result_loc)->var->decl_node->data.variable_declaration.type != nullptr;
            return ErrorNone;
    }
    zig_unreachable();
}

static Stage1AirInst *ir_resolve_no_result_loc(IrAnalyze *ira, Stage1ZirInst *suspend_source_instr,
    ResultLoc *result_loc, ZigType *value_type)
{
    if (type_is_invalid(value_type))
        return ira->codegen->invalid_inst_gen;
    Stage1AirInstAlloca *alloca_gen = ir_build_alloca_gen(ira, suspend_source_instr->scope,
            suspend_source_instr->source_node, 0, "");
    alloca_gen->base.value->type = get_pointer_to_type_extra(ira->codegen, value_type, false, false,
            PtrLenSingle, 0, 0, 0, false);
    set_up_result_loc_for_inferred_comptime(ira, &alloca_gen->base);
    ZigFn *fn_entry = ira->fn;
    if (fn_entry != nullptr && get_scope_typeof(suspend_source_instr->scope) == nullptr) {
        fn_entry->alloca_gen_list.append(alloca_gen);
    }
    result_loc->written = true;
    result_loc->resolved_loc = &alloca_gen->base;
    return result_loc->resolved_loc;
}

static bool result_loc_is_discard(ResultLoc *result_loc_pass1) {
    if (result_loc_pass1->id == ResultLocIdInstruction &&
        result_loc_pass1->source_instruction->id == Stage1ZirInstIdConst)
    {
        Stage1ZirInstConst *const_inst = reinterpret_cast<Stage1ZirInstConst *>(result_loc_pass1->source_instruction);
        if (value_is_comptime(const_inst->value) &&
            const_inst->value->type->id == ZigTypeIdPointer &&
            const_inst->value->data.x_ptr.special == ConstPtrSpecialDiscard)
        {
            return true;
        }
    }
    return false;
}

// when calling this function, at the callsite must check for result type noreturn and propagate it up
static Stage1AirInst *ir_resolve_result_raw(IrAnalyze *ira, Stage1ZirInst *suspend_source_instr,
        ResultLoc *result_loc, ZigType *value_type, Stage1AirInst *value, bool force_runtime,
        bool allow_discard)
{
    Error err;
    if (result_loc->resolved_loc != nullptr) {
        // allow to redo the result location if the value is known and comptime and the previous one isn't
        if (value == nullptr || !instr_is_comptime(value) || instr_is_comptime(result_loc->resolved_loc)) {
            return result_loc->resolved_loc;
        }
    }
    result_loc->gen_instruction = value;
    result_loc->implicit_elem_type = value_type;
    switch (result_loc->id) {
        case ResultLocIdInvalid:
        case ResultLocIdPeerParent:
            zig_unreachable();
        case ResultLocIdNone: {
            if (value != nullptr) {
                return nullptr;
            }
            // need to return a result location and don't have one. use a stack allocation
            return ir_resolve_no_result_loc(ira, suspend_source_instr, result_loc, value_type);
        }
        case ResultLocIdVar: {
            ResultLocVar *result_loc_var = reinterpret_cast<ResultLocVar *>(result_loc);
            assert(result_loc->source_instruction->id == Stage1ZirInstIdAlloca);
            Stage1ZirInstAlloca *alloca_src = reinterpret_cast<Stage1ZirInstAlloca *>(result_loc->source_instruction);

            ZigVar *var = result_loc_var->var;
            if (var->var_type != nullptr && !ir_get_var_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,
                    buf_create_from_str(var->name), var->src_is_const, var->gen_is_const,
                    var->shadowable, var->is_comptime, true);
                new_var->align_bytes = var->align_bytes;

                var->next_var = new_var;
                var = new_var;
            }
            if (value_type->id == ZigTypeIdUnreachable || value_type->id == ZigTypeIdOpaque) {
                ir_add_error_node(ira, result_loc->source_instruction->source_node,
                    buf_sprintf("variable of type '%s' not allowed", buf_ptr(&value_type->name)));
                return ira->codegen->invalid_inst_gen;
            }
            if (alloca_src->base.child == nullptr || var->ptr_instruction == nullptr) {
                bool force_comptime;
                if (!ir_resolve_comptime(ira, alloca_src->is_comptime->child, &force_comptime))
                    return ira->codegen->invalid_inst_gen;
                uint32_t align = 0;
                if (alloca_src->align != nullptr && !ir_resolve_align(ira, alloca_src->align->child, nullptr, &align)) {
                    return ira->codegen->invalid_inst_gen;
                }
                Stage1AirInst *alloca_gen = ir_analyze_alloca(ira,
                        result_loc->source_instruction->scope,
                        result_loc->source_instruction->source_node, value_type,
                        align, alloca_src->name_hint, force_comptime);
                if (force_runtime) {
                    alloca_gen->value->data.x_ptr.mut = ConstPtrMutRuntimeVar;
                    alloca_gen->value->special = ConstValSpecialRuntime;
                }
                if (alloca_src->base.child != nullptr && !result_loc->written) {
                    alloca_src->base.child->ref_count = 0;
                }
                alloca_src->base.child = alloca_gen;
                var->ptr_instruction = alloca_gen;
            }
            result_loc->written = true;
            result_loc->resolved_loc = alloca_src->base.child;
            return alloca_src->base.child;
        }
        case ResultLocIdInstruction: {
            result_loc->written = true;
            result_loc->resolved_loc = result_loc->source_instruction->child;
            return result_loc->resolved_loc;
        }
        case ResultLocIdReturn: {
            if (value != nullptr) {
                reinterpret_cast<ResultLocReturn *>(result_loc)->implicit_return_type_done = true;
                ira->src_implicit_return_type_list.append(value);
            }
            result_loc->written = true;
            result_loc->resolved_loc = ira->return_ptr;
            return result_loc->resolved_loc;
        }
        case ResultLocIdPeer: {
            ResultLocPeer *result_peer = reinterpret_cast<ResultLocPeer *>(result_loc);
            ResultLocPeerParent *peer_parent = result_peer->parent;

            if (peer_parent->peers.length == 1) {
                Stage1AirInst *parent_result_loc = ir_resolve_result(ira, suspend_source_instr, peer_parent->parent,
                        value_type, value, force_runtime, true);
                result_peer->suspend_pos.basic_block_index = SIZE_MAX;
                result_peer->suspend_pos.instruction_index = SIZE_MAX;
                if (parent_result_loc == nullptr || type_is_invalid(parent_result_loc->value->type) ||
                    parent_result_loc->value->type->id == ZigTypeIdUnreachable)
                {
                    return parent_result_loc;
                }
                result_loc->written = true;
                result_loc->resolved_loc = parent_result_loc;
                return result_loc->resolved_loc;
            }

            bool is_condition_comptime;
            if (!ir_resolve_comptime(ira, peer_parent->is_comptime->child, &is_condition_comptime))
                return ira->codegen->invalid_inst_gen;
            if (is_condition_comptime) {
                peer_parent->skipped = true;
                return ir_resolve_result(ira, suspend_source_instr, peer_parent->parent,
                        value_type, value, force_runtime, true);
            }
            bool peer_parent_has_type;
            if ((err = ir_result_has_type(ira, peer_parent->parent, &peer_parent_has_type)))
                return ira->codegen->invalid_inst_gen;
            if (peer_parent_has_type) {
                peer_parent->skipped = true;
                Stage1AirInst *parent_result_loc = ir_resolve_result(ira, suspend_source_instr, peer_parent->parent,
                        value_type, value, force_runtime || !is_condition_comptime, true);
                if (parent_result_loc == nullptr || type_is_invalid(parent_result_loc->value->type) ||
                    parent_result_loc->value->type->id == ZigTypeIdUnreachable)
                {
                    return parent_result_loc;
                }
                peer_parent->parent->written = true;
                result_loc->written = true;
                result_loc->resolved_loc = parent_result_loc;
                return result_loc->resolved_loc;
            }

            if (peer_parent->resolved_type == nullptr) {
                if (peer_parent->end_bb->suspend_instruction_ref == nullptr) {
                    peer_parent->end_bb->suspend_instruction_ref = suspend_source_instr;
                }
                Stage1AirInst *unreach_inst = ira_suspend(ira, suspend_source_instr, result_peer->next_bb,
                        &result_peer->suspend_pos);
                if (result_peer->next_bb == nullptr) {
                    ir_start_next_bb(ira);
                }
                return unreach_inst;
            }

            Stage1AirInst *parent_result_loc = ir_resolve_result(ira, suspend_source_instr, peer_parent->parent,
                    peer_parent->resolved_type, nullptr, force_runtime, true);
            if (parent_result_loc == nullptr || type_is_invalid(parent_result_loc->value->type) ||
                parent_result_loc->value->type->id == ZigTypeIdUnreachable)
            {
                return parent_result_loc;
            }
            // because is_condition_comptime is false, we mark this a runtime pointer
            parent_result_loc->value->special = ConstValSpecialRuntime;
            result_loc->written = true;
            result_loc->resolved_loc = parent_result_loc;
            return result_loc->resolved_loc;
        }
        case ResultLocIdCast: {
            ResultLocCast *result_cast = reinterpret_cast<ResultLocCast *>(result_loc);
            ZigType *dest_type = ir_resolve_type(ira, result_cast->base.source_instruction->child);
            if (type_is_invalid(dest_type))
                return ira->codegen->invalid_inst_gen;

            if (dest_type == ira->codegen->builtin_types.entry_anytype) {
                return ir_resolve_no_result_loc(ira, suspend_source_instr, result_loc, value_type);
            }

            Stage1AirInst *casted_value;
            if (value != nullptr) {
                casted_value = ir_implicit_cast2(ira, suspend_source_instr->scope,
                        suspend_source_instr->source_node, value, dest_type);
                if (type_is_invalid(casted_value->value->type))
                    return ira->codegen->invalid_inst_gen;
                dest_type = casted_value->value->type;
            } else {
                casted_value = nullptr;
            }

            Stage1AirInst *parent_result_loc = ir_resolve_result(ira, suspend_source_instr, result_cast->parent,
                    dest_type, casted_value, force_runtime, true);
            if (parent_result_loc == nullptr || type_is_invalid(parent_result_loc->value->type) ||
                parent_result_loc->value->type->id == ZigTypeIdUnreachable)
            {
                return parent_result_loc;
            }

            ZigType *parent_ptr_type = parent_result_loc->value->type;
            assert(parent_ptr_type->id == ZigTypeIdPointer);

            if ((err = type_resolve(ira->codegen, parent_ptr_type->data.pointer.child_type,
                            ResolveStatusAlignmentKnown)))
            {
                return ira->codegen->invalid_inst_gen;
            }
            uint64_t parent_ptr_align = get_ptr_align(ira->codegen, parent_ptr_type);
            if ((err = type_resolve(ira->codegen, value_type, ResolveStatusAlignmentKnown))) {
                return ira->codegen->invalid_inst_gen;
            }
            if (!type_has_bits(ira->codegen, value_type)) {
                parent_ptr_align = 0;
            }
            // If we're casting from a sentinel-terminated array to a non-sentinel-terminated array,
            // we actually need the result location pointer to *not* have a sentinel. Otherwise the generated
            // memcpy will write an extra byte to the destination, and THAT'S NO GOOD.
            ZigType *ptr_elem_type;
            if (value_type->id == ZigTypeIdArray && value_type->data.array.sentinel != nullptr &&
                dest_type->id == ZigTypeIdArray && dest_type->data.array.sentinel == nullptr)
            {
                ptr_elem_type = get_array_type(ira->codegen, value_type->data.array.child_type,
                        value_type->data.array.len, nullptr);
            } else {
                ptr_elem_type = value_type;
            }
            ZigType *ptr_type = get_pointer_to_type_extra(ira->codegen, ptr_elem_type,
                    parent_ptr_type->data.pointer.is_const, parent_ptr_type->data.pointer.is_volatile, PtrLenSingle,
                    parent_ptr_align, 0, 0, parent_ptr_type->data.pointer.allow_zero);

            ConstCastOnly const_cast_result = types_match_const_cast_only(ira,
                    parent_result_loc->value->type, ptr_type,
                    result_cast->base.source_instruction->source_node, false);
            if (const_cast_result.id == ConstCastResultIdInvalid)
                return ira->codegen->invalid_inst_gen;
            if (const_cast_result.id != ConstCastResultIdOk) {
                if (allow_discard) {
                    return parent_result_loc;
                }
                // We will not be able to provide a result location for this value. Create
                // a new result location.
                result_cast->parent->written = false;
                return ir_resolve_no_result_loc(ira, suspend_source_instr, result_loc, value_type);
            }

            result_loc->written = true;
            result_loc->resolved_loc = ir_analyze_ptr_cast(ira, suspend_source_instr->scope,
                    suspend_source_instr->source_node, parent_result_loc,
                    parent_result_loc->source_node, ptr_type,
                    result_cast->base.source_instruction->source_node, false, false);
            return result_loc->resolved_loc;
        }
        case ResultLocIdBitCast: {
            ResultLocBitCast *result_bit_cast = reinterpret_cast<ResultLocBitCast *>(result_loc);
            ZigType *dest_type = ir_resolve_type(ira, result_bit_cast->base.source_instruction->child);
            if (type_is_invalid(dest_type))
                return ira->codegen->invalid_inst_gen;

            ZigType *dest_cg_ptr_type;
            if ((err = get_codegen_ptr_type(ira->codegen, dest_type, &dest_cg_ptr_type)))
                return ira->codegen->invalid_inst_gen;
            if (dest_cg_ptr_type != nullptr) {
                ir_add_error_node(ira, result_loc->source_instruction->source_node,
                        buf_sprintf("unable to @bitCast to pointer type '%s'", buf_ptr(&dest_type->name)));
                return ira->codegen->invalid_inst_gen;
            }

            if (!type_can_bit_cast(dest_type)) {
                ir_add_error_node(ira, result_loc->source_instruction->source_node,
                        buf_sprintf("unable to @bitCast to type '%s'", buf_ptr(&dest_type->name)));
                return ira->codegen->invalid_inst_gen;
            }

            ZigType *value_cg_ptr_type;
            if ((err = get_codegen_ptr_type(ira->codegen, value_type, &value_cg_ptr_type)))
                return ira->codegen->invalid_inst_gen;
            if (value_cg_ptr_type != nullptr) {
                ir_add_error_node(ira, suspend_source_instr->source_node,
                    buf_sprintf("unable to @bitCast from pointer type '%s'", buf_ptr(&value_type->name)));
                return ira->codegen->invalid_inst_gen;
            }

            if (!type_can_bit_cast(value_type)) {
                ir_add_error_node(ira, suspend_source_instr->source_node,
                        buf_sprintf("unable to @bitCast from type '%s'", buf_ptr(&value_type->name)));
                return ira->codegen->invalid_inst_gen;
            }

            Stage1AirInst *bitcasted_value;
            if (value != nullptr) {
                bitcasted_value = ir_analyze_bit_cast(ira, result_loc->source_instruction->scope,
                        result_loc->source_instruction->source_node, value, dest_type);
                dest_type = bitcasted_value->value->type;
            } else {
                bitcasted_value = nullptr;
            }

            if (bitcasted_value != nullptr && type_is_invalid(bitcasted_value->value->type)) {
                return bitcasted_value;
            }

            bool parent_was_written = result_bit_cast->parent->written;
            Stage1AirInst *parent_result_loc = ir_resolve_result(ira, suspend_source_instr, result_bit_cast->parent,
                    dest_type, bitcasted_value, force_runtime, true);
            if (parent_result_loc == nullptr || type_is_invalid(parent_result_loc->value->type) ||
                parent_result_loc->value->type->id == ZigTypeIdUnreachable)
            {
                return parent_result_loc;
            }
            ZigType *parent_ptr_type = parent_result_loc->value->type;
            assert(parent_ptr_type->id == ZigTypeIdPointer);
            ZigType *child_type = parent_ptr_type->data.pointer.child_type;

            if (result_loc_is_discard(result_bit_cast->parent)) {
                assert(allow_discard);
                return parent_result_loc;
            }

            if ((err = type_resolve(ira->codegen, child_type, ResolveStatusSizeKnown))) {
                return ira->codegen->invalid_inst_gen;
            }

            if ((err = type_resolve(ira->codegen, value_type, ResolveStatusSizeKnown))) {
                return ira->codegen->invalid_inst_gen;
            }

            if (child_type != ira->codegen->builtin_types.entry_anytype) {
                if (type_size(ira->codegen, child_type) != type_size(ira->codegen, value_type)) {
                    // pointer cast won't work; we need a temporary location.
                    result_bit_cast->parent->written = parent_was_written;
                    result_loc->written = true;
                    result_loc->resolved_loc = ir_resolve_result(ira, suspend_source_instr, no_result_loc(),
                            value_type, bitcasted_value, force_runtime, true);
                    return result_loc->resolved_loc;
                }
            }
            uint64_t parent_ptr_align = 0;
            if (type_has_bits(ira->codegen, value_type)) parent_ptr_align = get_ptr_align(ira->codegen, parent_ptr_type);
            ZigType *ptr_type = get_pointer_to_type_extra(ira->codegen, value_type,
                    parent_ptr_type->data.pointer.is_const, parent_ptr_type->data.pointer.is_volatile, PtrLenSingle,
                    parent_ptr_align, 0, 0, parent_ptr_type->data.pointer.allow_zero);

            result_loc->written = true;
            result_loc->resolved_loc = ir_analyze_ptr_cast(ira, suspend_source_instr->scope,
                    suspend_source_instr->source_node, parent_result_loc,
                    parent_result_loc->source_node, ptr_type,
                    result_bit_cast->base.source_instruction->source_node, false, false);
            return result_loc->resolved_loc;
        }
    }
    zig_unreachable();
}

static Stage1AirInst *ir_resolve_result(IrAnalyze *ira, Stage1ZirInst *suspend_source_instr,
        ResultLoc *result_loc_pass1, ZigType *value_type, Stage1AirInst *value, bool force_runtime,
        bool allow_discard)
{
    if (!allow_discard && result_loc_is_discard(result_loc_pass1)) {
        result_loc_pass1 = no_result_loc();
    }
    bool was_written = result_loc_pass1->written;
    Stage1AirInst *result_loc = ir_resolve_result_raw(ira, suspend_source_instr, result_loc_pass1, value_type,
            value, force_runtime, allow_discard);
    if (result_loc == nullptr || result_loc->value->type->id == ZigTypeIdUnreachable ||
            type_is_invalid(result_loc->value->type))
    {
        return result_loc;
    }

    if ((force_runtime || (value != nullptr && !instr_is_comptime(value))) &&
        result_loc_pass1->written && result_loc->value->data.x_ptr.mut == ConstPtrMutInfer)
    {
        result_loc->value->special = ConstValSpecialRuntime;
    }

    InferredStructField *isf = result_loc->value->type->data.pointer.inferred_struct_field;
    if (isf != nullptr) {
        TypeStructField *field;
        Stage1AirInst *casted_ptr;
        if (isf->already_resolved) {
            field = find_struct_type_field(isf->inferred_struct_type, isf->field_name);
            casted_ptr = result_loc;
        } else {
            isf->already_resolved = true;
            // Now it's time to add the field to the struct type.
            uint32_t old_field_count = isf->inferred_struct_type->data.structure.src_field_count;
            uint32_t new_field_count = old_field_count + 1;
            isf->inferred_struct_type->data.structure.src_field_count = new_field_count;
            isf->inferred_struct_type->data.structure.fields = realloc_type_struct_fields(
                    isf->inferred_struct_type->data.structure.fields, old_field_count, new_field_count);

            field = isf->inferred_struct_type->data.structure.fields[old_field_count];
            field->name = isf->field_name;
            field->type_entry = value_type;
            field->type_val = create_const_type(ira->codegen, field->type_entry);
            field->src_index = old_field_count;
            field->decl_node = value ? value->source_node : suspend_source_instr->source_node;
            if (value && instr_is_comptime(value)) {
                ZigValue *val = ir_resolve_const(ira, value, UndefOk);
                if (!val)
                    return ira->codegen->invalid_inst_gen;
                field->is_comptime = true;
                field->init_val = ira->codegen->pass1_arena->create<ZigValue>();
                copy_const_val(ira->codegen, field->init_val, val);
                return result_loc;
            }

            ZigType *struct_ptr_type = get_pointer_to_type(ira->codegen, isf->inferred_struct_type, false);
            if (instr_is_comptime(result_loc)) {
                casted_ptr = ir_const(ira, suspend_source_instr->scope,
                        suspend_source_instr->source_node, struct_ptr_type);
                copy_const_val(ira->codegen, casted_ptr->value, result_loc->value);
                casted_ptr->value->type = struct_ptr_type;
            } else {
                casted_ptr = result_loc;
            }
            if (instr_is_comptime(casted_ptr)) {
                ZigValue *ptr_val = ir_resolve_const(ira, casted_ptr, UndefBad);
                if (!ptr_val)
                    return ira->codegen->invalid_inst_gen;
                if (ptr_val->data.x_ptr.special != ConstPtrSpecialHardCodedAddr) {
                    ZigValue *struct_val = const_ptr_pointee(ira, ira->codegen, ptr_val,
                            suspend_source_instr->source_node);
                    struct_val->special = ConstValSpecialStatic;
                    struct_val->data.x_struct.fields = realloc_const_vals_ptrs(ira->codegen,
                            struct_val->data.x_struct.fields, old_field_count, new_field_count);

                    ZigValue *field_val = struct_val->data.x_struct.fields[old_field_count];
                    field_val->special = ConstValSpecialUndef;
                    field_val->type = field->type_entry;
                    field_val->parent.id = ConstParentIdStruct;
                    field_val->parent.data.p_struct.struct_val = struct_val;
                    field_val->parent.data.p_struct.field_index = old_field_count;
                }
            }
        }

        result_loc = ir_analyze_struct_field_ptr(ira, suspend_source_instr->scope,
                suspend_source_instr->source_node, field, casted_ptr,
                isf->inferred_struct_type, true);
        if (type_is_invalid(result_loc->value->type)) {
            return result_loc;
        }
        result_loc_pass1->resolved_loc = result_loc;
    }

    if (was_written) {
        return result_loc;
    }

    src_assert(result_loc->value->type->id == ZigTypeIdPointer, suspend_source_instr->source_node);
    ZigType *actual_elem_type = result_loc->value->type->data.pointer.child_type;
    if (actual_elem_type->id == ZigTypeIdOptional && value_type->id != ZigTypeIdOptional &&
            value_type->id != ZigTypeIdNull && value_type->id != ZigTypeIdUndefined)
    {
        bool same_comptime_repr = types_have_same_zig_comptime_repr(ira->codegen, actual_elem_type, value_type);
        if (!same_comptime_repr) {
            result_loc_pass1->written = was_written;
            return ir_analyze_unwrap_optional_payload(ira, suspend_source_instr->scope,
                    suspend_source_instr->source_node, result_loc, false, true);
        }
    } else if (actual_elem_type->id == ZigTypeIdErrorUnion && value_type->id != ZigTypeIdErrorUnion &&
            value_type->id != ZigTypeIdUndefined)
    {
        if (value_type->id == ZigTypeIdErrorSet) {
            return ir_analyze_unwrap_err_code(ira, suspend_source_instr->scope,
                    suspend_source_instr->source_node, result_loc, true);
        } else {
            Stage1AirInst *unwrapped_err_ptr = ir_analyze_unwrap_error_payload(ira,
                    suspend_source_instr->scope, suspend_source_instr->source_node,
                    result_loc, false, true);
            ZigType *actual_payload_type = actual_elem_type->data.error_union.payload_type;
            if (actual_payload_type->id == ZigTypeIdOptional && value_type->id != ZigTypeIdOptional &&
                value_type->id != ZigTypeIdNull && value_type->id != ZigTypeIdUndefined)
            {
                return ir_analyze_unwrap_optional_payload(ira, suspend_source_instr->scope,
                        suspend_source_instr->source_node, unwrapped_err_ptr, false, true);
            } else {
                return unwrapped_err_ptr;
            }
        }
    }
    return result_loc;
}

static Stage1AirInst *ir_analyze_instruction_resolve_result(IrAnalyze *ira, Stage1ZirInstResolveResult *instruction) {
    ZigType *implicit_elem_type;
    if (instruction->ty == nullptr) {
        if (instruction->result_loc->id == ResultLocIdCast) {
            implicit_elem_type = ir_resolve_type(ira,
                    instruction->result_loc->source_instruction->child);
            if (type_is_invalid(implicit_elem_type))
                return ira->codegen->invalid_inst_gen;
        } else if (instruction->result_loc->id == ResultLocIdReturn) {
            implicit_elem_type = ira->explicit_return_type;
            if (type_is_invalid(implicit_elem_type))
                return ira->codegen->invalid_inst_gen;
        } else {
            implicit_elem_type = ira->codegen->builtin_types.entry_anytype;
        }
        if (implicit_elem_type == ira->codegen->builtin_types.entry_anytype) {
            Buf *bare_name = buf_alloc();
            Buf *name = get_anon_type_name(ira->codegen, nullptr, container_string(ContainerKindStruct),
                    instruction->base.scope, instruction->base.source_node, bare_name, nullptr);

            StructSpecial struct_special = StructSpecialInferredStruct;
            if (instruction->base.source_node->type == NodeTypeContainerInitExpr &&
                instruction->base.source_node->data.container_init_expr.kind == ContainerInitKindArray)
            {
                struct_special = StructSpecialInferredTuple;
            }

            ZigType *inferred_struct_type = get_partial_container_type(ira->codegen,
                    instruction->base.scope, ContainerKindStruct, instruction->base.source_node,
                    buf_ptr(name), bare_name, ContainerLayoutAuto);
            inferred_struct_type->data.structure.special = struct_special;
            inferred_struct_type->data.structure.resolve_status = ResolveStatusBeingInferred;
            implicit_elem_type = inferred_struct_type;
        }
    } else {
        implicit_elem_type = ir_resolve_type(ira, instruction->ty->child);
        if (type_is_invalid(implicit_elem_type))
            return ira->codegen->invalid_inst_gen;
    }
    Stage1AirInst *result_loc = ir_resolve_result(ira, &instruction->base, instruction->result_loc,
            implicit_elem_type, nullptr, false, true);
    if (result_loc != nullptr)
        return result_loc;

    ZigFn *fn = ira->fn;
    if (fn != nullptr && fn->type_entry->data.fn.fn_type_id.cc == CallingConventionAsync &&
            instruction->result_loc->id == ResultLocIdReturn)
    {
        result_loc = ir_resolve_result(ira, &instruction->base, no_result_loc(),
                implicit_elem_type, nullptr, false, true);
        if (result_loc != nullptr &&
                (type_is_invalid(result_loc->value->type) || result_loc->value->type->id == ZigTypeIdUnreachable))
        {
            return result_loc;
        }
        result_loc->value->special = ConstValSpecialRuntime;
        return result_loc;
    }

    Stage1AirInst *result = ir_const(ira, instruction->base.scope, instruction->base.source_node, implicit_elem_type);
    result->value->special = ConstValSpecialUndef;
    Stage1AirInst *ptr = ir_get_ref(ira, instruction->base.scope, instruction->base.source_node, result, false, false);
    ptr->value->data.x_ptr.mut = ConstPtrMutComptimeVar;
    return ptr;
}

static void ir_reset_result(ResultLoc *result_loc) {
    result_loc->written = false;
    result_loc->resolved_loc = nullptr;
    result_loc->gen_instruction = nullptr;
    result_loc->implicit_elem_type = nullptr;
    switch (result_loc->id) {
        case ResultLocIdInvalid:
            zig_unreachable();
        case ResultLocIdPeerParent: {
            ResultLocPeerParent *peer_parent = reinterpret_cast<ResultLocPeerParent *>(result_loc);
            peer_parent->skipped = false;
            peer_parent->done_resuming = false;
            peer_parent->resolved_type = nullptr;
            for (size_t i = 0; i < peer_parent->peers.length; i += 1) {
                ir_reset_result(&peer_parent->peers.at(i)->base);
            }
            break;
        }
        case ResultLocIdVar: {
            Stage1ZirInstAlloca *alloca_src = reinterpret_cast<Stage1ZirInstAlloca *>(result_loc->source_instruction);
            alloca_src->base.child = nullptr;
            break;
        }
        case ResultLocIdReturn:
            reinterpret_cast<ResultLocReturn *>(result_loc)->implicit_return_type_done = false;
            break;
        case ResultLocIdPeer:
        case ResultLocIdNone:
        case ResultLocIdInstruction:
        case ResultLocIdBitCast:
        case ResultLocIdCast:
            break;
    }
}

static Stage1AirInst *ir_analyze_instruction_reset_result(IrAnalyze *ira, Stage1ZirInstResetResult *instruction) {
    ir_reset_result(instruction->result_loc);
    return ir_const_void(ira, instruction->base.scope, instruction->base.source_node);
}

static Stage1AirInst *get_async_call_result_loc(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        ZigType *fn_ret_type, bool is_async_call_builtin, Stage1AirInst **args_ptr, size_t args_len,
        Stage1AirInst *ret_ptr_uncasted)
{
    src_assert(is_async_call_builtin, source_node);
    if (type_is_invalid(ret_ptr_uncasted->value->type))
        return ira->codegen->invalid_inst_gen;
    if (ret_ptr_uncasted->value->type->id == ZigTypeIdVoid) {
        // Result location will be inside the async frame.
        return nullptr;
    }
    return ir_implicit_cast(ira, ret_ptr_uncasted, get_pointer_to_type(ira->codegen, fn_ret_type, false));
}

static Stage1AirInst *ir_analyze_async_call(IrAnalyze *ira, Scope *scope, AstNode *source_node, ZigFn *fn_entry,
        ZigType *fn_type, Stage1AirInst *fn_ref, Stage1AirInst **casted_args, size_t arg_count,
        Stage1AirInst *casted_new_stack, bool is_async_call_builtin, Stage1AirInst *ret_ptr_uncasted,
        ResultLoc *call_result_loc)
{
    if (fn_entry == nullptr) {
        if (fn_type->data.fn.fn_type_id.cc != CallingConventionAsync) {
            ir_add_error(ira, fn_ref,
                buf_sprintf("expected async function, found '%s'", buf_ptr(&fn_type->name)));
            return ira->codegen->invalid_inst_gen;
        }
        if (casted_new_stack == nullptr) {
            ir_add_error(ira, fn_ref, buf_sprintf("function is not comptime-known; @asyncCall required"));
            return ira->codegen->invalid_inst_gen;
        }
    }
    if (casted_new_stack != nullptr) {
        ZigType *fn_ret_type = fn_type->data.fn.fn_type_id.return_type;
        Stage1AirInst *ret_ptr = get_async_call_result_loc(ira, scope, source_node, fn_ret_type, is_async_call_builtin,
                casted_args, arg_count, ret_ptr_uncasted);
        if (ret_ptr != nullptr && type_is_invalid(ret_ptr->value->type))
            return ira->codegen->invalid_inst_gen;

        ZigType *anyframe_type = get_any_frame_type(ira->codegen, fn_ret_type);

        Stage1AirInstCall *call_gen = ir_build_call_gen(ira, scope, source_node, fn_entry, fn_ref,
                arg_count, casted_args, CallModifierAsync, casted_new_stack,
                is_async_call_builtin, ret_ptr, anyframe_type);
        return &call_gen->base;
    } else {
        ZigType *frame_type = get_fn_frame_type(ira->codegen, fn_entry);
        Stage1AirInst *result_loc = ir_resolve_result(ira, ira->suspend_source_instr, call_result_loc,
                frame_type, nullptr, true, false);
        if (type_is_invalid(result_loc->value->type) || result_loc->value->type->id == ZigTypeIdUnreachable) {
            return result_loc;
        }
        result_loc = ir_implicit_cast2(ira, scope, source_node, result_loc,
                get_pointer_to_type(ira->codegen, frame_type, false));
        if (type_is_invalid(result_loc->value->type))
            return ira->codegen->invalid_inst_gen;
        return &ir_build_call_gen(ira, scope, source_node, fn_entry, fn_ref, arg_count,
                casted_args, CallModifierAsync, casted_new_stack,
                is_async_call_builtin, result_loc, frame_type)->base;
    }
}
static bool ir_analyze_fn_call_inline_arg(IrAnalyze *ira, AstNode *fn_proto_node,
    Stage1AirInst *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);

    Stage1AirInst *casted_arg;
    if (param_decl_node->data.param_decl.anytype_token == 0) {
        AstNode *param_type_node = param_decl_node->data.param_decl.type;
        ZigType *param_type = ir_analyze_type_expr(ira, *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;
    }

    ZigValue *arg_val = ir_resolve_const(ira, casted_arg, UndefOk);
    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,
    Stage1AirInst *arg, AstNode *arg_src, Scope **child_scope, size_t *next_proto_i,
    GenericFnTypeId *generic_id, FnTypeId *fn_type_id, Stage1AirInst **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;
    Stage1AirInst *casted_arg;

    ZigType *param_info_type = nullptr;
    if (is_var_args) {
        arg_part_of_generic_id = true;
        casted_arg = arg;
        param_info_type = arg->value->type;
    } else {
        if (param_decl_node->data.param_decl.anytype_token == 0) {
            AstNode *param_type_node = param_decl_node->data.param_decl.type;
            ZigType *param_type = ir_analyze_type_expr(ira, *child_scope, param_type_node);
            if (type_is_invalid(param_type))
                return false;

            casted_arg = ir_implicit_cast2(ira, arg->scope, arg_src, arg, param_type);
            if (type_is_invalid(casted_arg->value->type))
                return false;

            param_info_type = param_type;
        } else {
            arg_part_of_generic_id = true;
            casted_arg = arg;
            param_info_type = arg->value->type;
        }
    }

    bool comptime_arg = param_decl_node->data.param_decl.is_comptime;
    if (!comptime_arg) {
        switch (type_requires_comptime(ira->codegen, casted_arg->value->type)) {
        case ReqCompTimeInvalid:
            return false;
        case ReqCompTimeYes:
            comptime_arg = true;
            break;
        case ReqCompTimeNo:
            break;
        }
    }

    ZigValue *arg_val;

    if (comptime_arg && !instr_is_comptime(casted_arg)) {
        ir_add_error(ira, casted_arg,
            buf_sprintf("runtime value cannot be passed to comptime arg"));
        return false;
    }
    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(ira->codegen, casted_arg->value->type);
    }
    if (arg_part_of_generic_id) {
        copy_const_val(ira->codegen, &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) {
        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 = param_info_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 Stage1AirInst *ir_get_var_ptr(IrAnalyze *ira, Scope *scope, AstNode *source_node, ZigVar *var) {
    while (var->next_var != nullptr) {
        var = var->next_var;
    }

    if (var->var_type == nullptr || type_is_invalid(var->var_type))
        return ira->codegen->invalid_inst_gen;

    bool is_volatile = false;
    ZigType *var_ptr_type = get_pointer_to_type_extra(ira->codegen, var->var_type,
            var->src_is_const, is_volatile, PtrLenSingle, var->align_bytes, 0, 0, false);

    if (var->ptr_instruction != nullptr) {
        return ir_implicit_cast(ira, var->ptr_instruction, var_ptr_type);
    }

    bool comptime_var_mem = ir_get_var_is_comptime(var);
    bool linkage_makes_it_runtime = var->decl_node->data.variable_declaration.is_extern;

    Stage1AirInst *result = ir_build_var_ptr_gen(ira, scope, source_node, var);
    result->value->type = var_ptr_type;

    if (!linkage_makes_it_runtime && !var->is_thread_local && value_is_comptime(var->const_value)) {
        ZigValue *val = var->const_value;
        switch (val->special) {
            case ConstValSpecialRuntime:
                break;
            case ConstValSpecialStatic: // fallthrough
            case ConstValSpecialLazy: // 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;
                }
                result->value->special = ConstValSpecialStatic;
                result->value->data.x_ptr.mut = ptr_mut;
                result->value->data.x_ptr.special = ConstPtrSpecialRef;
                result->value->data.x_ptr.data.ref.pointee = val;
                return result;
            }
        }
    }

    bool in_fn_scope = (scope_fn_entry(var->parent_scope) != nullptr);
    result->value->data.rh_ptr = in_fn_scope ? RuntimeHintPtrStack : RuntimeHintPtrNonStack;

    return result;
}

// This function is called when a comptime value becomes accessible at runtime.
static void mark_comptime_value_escape(IrAnalyze *ira, Scope *scope, AstNode *source_node, ZigValue *val) {
    src_assert(value_is_comptime(val), source_node);
    if (val->special == ConstValSpecialUndef)
        return;

    if (val->type->id == ZigTypeIdFn && val->type->data.fn.fn_type_id.cc == CallingConventionUnspecified) {
        src_assert(val->data.x_ptr.special == ConstPtrSpecialFunction, source_node);
        if (val->data.x_ptr.data.fn.fn_entry->non_async_node == nullptr) {
            val->data.x_ptr.data.fn.fn_entry->non_async_node = source_node;
        }
    }
}

static Stage1AirInst *ir_analyze_store_ptr(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        Stage1AirInst *ptr, Stage1AirInst *uncasted_value, bool allow_write_through_const)
{
    assert(ptr->value->type->id == ZigTypeIdPointer);

    if (ptr->value->data.x_ptr.special == ConstPtrSpecialDiscard) {
        if (uncasted_value->value->type->id == ZigTypeIdErrorUnion ||
            uncasted_value->value->type->id == ZigTypeIdErrorSet)
        {
            ir_add_error_node(ira, source_node, buf_sprintf("error is discarded. consider using `try`, `catch`, or `if`"));
            return ira->codegen->invalid_inst_gen;
        }
        return ir_const_void(ira, scope, source_node);
    }

    if (ptr->value->type->data.pointer.is_const && !allow_write_through_const) {
        ir_add_error_node(ira, source_node, buf_sprintf("cannot assign to constant"));
        return ira->codegen->invalid_inst_gen;
    }

    ZigType *child_type = ptr->value->type->data.pointer.child_type;
    Stage1AirInst *value = ir_implicit_cast(ira, uncasted_value, child_type);
    if (type_is_invalid(value->value->type))
        return ira->codegen->invalid_inst_gen;

    switch (type_has_one_possible_value(ira->codegen, child_type)) {
        case OnePossibleValueInvalid:
            return ira->codegen->invalid_inst_gen;
        case OnePossibleValueYes:
            return ir_const_void(ira, scope, source_node);
        case OnePossibleValueNo:
            break;
    }

    if (instr_is_comptime(ptr) && ptr->value->data.x_ptr.special != ConstPtrSpecialHardCodedAddr) {
        if (!allow_write_through_const && ptr->value->data.x_ptr.mut == ConstPtrMutComptimeConst) {
            ir_add_error_node(ira, source_node, buf_sprintf("cannot assign to constant"));
            return ira->codegen->invalid_inst_gen;
        }
        if ((allow_write_through_const && ptr->value->data.x_ptr.mut == ConstPtrMutComptimeConst) ||
            ptr->value->data.x_ptr.mut == ConstPtrMutComptimeVar ||
            ptr->value->data.x_ptr.mut == ConstPtrMutInfer)
        {
            if (instr_is_comptime(value)) {
                ZigValue *dest_val = const_ptr_pointee(ira, ira->codegen, ptr->value, source_node);
                if (dest_val == nullptr)
                    return ira->codegen->invalid_inst_gen;
                if (dest_val->special != ConstValSpecialRuntime) {
                    copy_const_val(ira->codegen, dest_val, value->value);

                    if (ptr->value->data.x_ptr.mut == ConstPtrMutComptimeVar &&
                        ira->new_irb.current_basic_block->must_be_comptime_source_node == nullptr)
                    {
                        ira->new_irb.current_basic_block->must_be_comptime_source_node = source_node;
                    }
                    return ir_const_void(ira, scope, source_node);
                }
            }
            if (ptr->value->data.x_ptr.mut == ConstPtrMutInfer) {
                ptr->value->special = ConstValSpecialRuntime;
            } else {
                ir_add_error_node(ira, source_node,
                        buf_sprintf("cannot store runtime value in compile time variable"));
                ZigValue *dest_val = const_ptr_pointee_unchecked(ira->codegen, ptr->value);
                dest_val->type = ira->codegen->builtin_types.entry_invalid;

                return ira->codegen->invalid_inst_gen;
            }
        }
    }

    if (ptr->value->type->data.pointer.inferred_struct_field != nullptr &&
        child_type == ira->codegen->builtin_types.entry_anytype)
    {
        child_type = ptr->value->type->data.pointer.inferred_struct_field->inferred_struct_type;
    }

    switch (type_requires_comptime(ira->codegen, child_type)) {
        case ReqCompTimeInvalid:
            return ira->codegen->invalid_inst_gen;
        case ReqCompTimeYes:
            switch (type_has_one_possible_value(ira->codegen, ptr->value->type)) {
                case OnePossibleValueInvalid:
                    return ira->codegen->invalid_inst_gen;
                case OnePossibleValueNo:
                    ir_add_error_node(ira, source_node,
                            buf_sprintf("cannot store runtime value in type '%s'", buf_ptr(&child_type->name)));
                    return ira->codegen->invalid_inst_gen;
                case OnePossibleValueYes:
                    return ir_const_void(ira, scope, source_node);
            }
            zig_unreachable();
        case ReqCompTimeNo:
            break;
    }

    if (instr_is_comptime(value)) {
        mark_comptime_value_escape(ira, scope, source_node, value->value);
    }

    // If this is a store to a pointer with a runtime-known vector index,
    // we have to figure out the Stage1AirInst which represents the index and
    // emit a Stage1AirInstVectorStoreElem, or emit a compile error
    // explaining why it is impossible for this store to work. Which is that
    // the pointer address is of the vector; without the element index being known
    // we cannot properly perform the insertion.
    if (ptr->value->type->data.pointer.vector_index == VECTOR_INDEX_RUNTIME) {
        if (ptr->id == Stage1AirInstIdElemPtr) {
            Stage1AirInstElemPtr *elem_ptr = (Stage1AirInstElemPtr *)ptr;
            return ir_build_vector_store_elem(ira, scope, source_node, elem_ptr->array_ptr,
                    elem_ptr->elem_index, value);
        }
        ir_add_error(ira, ptr,
            buf_sprintf("unable to determine vector element index of type '%s'",
                buf_ptr(&ptr->value->type->name)));
        return ira->codegen->invalid_inst_gen;
    }

    return ir_build_store_ptr_gen(ira, scope, source_node, ptr, value);
}

static Stage1AirInst *analyze_casted_new_stack(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        Stage1AirInst *new_stack, AstNode *new_stack_src, bool is_async_call_builtin, ZigFn *fn_entry)
{
    if (new_stack == nullptr)
        return nullptr;

    if (!is_async_call_builtin &&
        arch_stack_pointer_register_name(ira->codegen->zig_target->arch) == nullptr)
    {
        ir_add_error_node(ira, source_node,
            buf_sprintf("target arch '%s' does not support calling with a new stack",
                target_arch_name(ira->codegen->zig_target->arch)));
    }

    if (is_async_call_builtin &&
        fn_entry != nullptr && new_stack->value->type->id == ZigTypeIdPointer &&
        new_stack->value->type->data.pointer.child_type->id == ZigTypeIdFnFrame)
    {
        ZigType *needed_frame_type = get_pointer_to_type(ira->codegen,
                get_fn_frame_type(ira->codegen, fn_entry), false);
        return ir_implicit_cast(ira, new_stack, needed_frame_type);
    } else {
        // XXX The stack alignment is hardcoded to 16 here and in
        // std.Target.stack_align.
        const uint32_t required_align = is_async_call_builtin ?
                get_async_frame_align_bytes(ira->codegen) : 16;
        ZigType *u8_ptr = get_pointer_to_type_extra(ira->codegen, ira->codegen->builtin_types.entry_u8,
                false, false, PtrLenUnknown, required_align, 0, 0, false);
        ZigType *u8_slice = get_slice_type(ira->codegen, u8_ptr);
        ira->codegen->need_frame_size_prefix_data = true;
        return ir_implicit_cast2(ira, new_stack->scope, new_stack_src, new_stack, u8_slice);
    }
}

static Stage1AirInst *ir_analyze_fn_call(IrAnalyze *ira, Scope *scope, AstNode *source_node,
    ZigFn *fn_entry, ZigType *fn_type, Stage1AirInst *fn_ref,
    Stage1AirInst *first_arg_ptr, AstNode *first_arg_ptr_src, CallModifier modifier,
    Stage1AirInst *new_stack, AstNode *new_stack_src, bool is_async_call_builtin,
    Stage1AirInst **args_ptr, size_t args_len, Stage1AirInst *ret_ptr, ResultLoc *call_result_loc)
{
    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 = args_len + first_arg_1_or_0;

    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_inst_gen;
    }

    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 " argument(s), 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_inst_gen;
        }
    } else if (src_param_count != call_param_count) {
        ErrorMsg *msg = ir_add_error_node(ira, source_node,
            buf_sprintf("expected %" ZIG_PRI_usize " argument(s), 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_inst_gen;
    }

    if (modifier == CallModifierCompileTime) {
        // If we are evaluating an extern function in a TypeOf call, we can return an undefined value
        // of its return type.
        if (fn_entry != nullptr && get_scope_typeof(scope) != nullptr &&
            fn_proto_node->data.fn_proto.is_extern) {

            assert(fn_entry->body_node == nullptr);
            AstNode *return_type_node = fn_proto_node->data.fn_proto.return_type;
            ZigType *return_type = ir_analyze_type_expr(ira, scope, return_type_node);
            if (type_is_invalid(return_type))
                return ira->codegen->invalid_inst_gen;

            return ir_const_undef(ira, scope, source_node, return_type);
        }

        // 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_inst_gen;
        }

        if (!ir_emit_backward_branch(ira, source_node))
            return ira->codegen->invalid_inst_gen;

        // 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_inst_gen;
                first_arg_known_bare = param_type->id != ZigTypeIdPointer;
            }

            Stage1AirInst *first_arg;
            if (!first_arg_known_bare) {
                first_arg = first_arg_ptr;
            } else {
                first_arg = ir_get_deref(ira, first_arg_ptr->scope, first_arg_ptr->source_node, first_arg_ptr, nullptr);
                if (type_is_invalid(first_arg->value->type))
                    return ira->codegen->invalid_inst_gen;
            }

            if (!ir_analyze_fn_call_inline_arg(ira, fn_proto_node, first_arg, &exec_scope, &next_proto_i))
                return ira->codegen->invalid_inst_gen;
        }

        for (size_t call_i = 0; call_i < args_len; call_i += 1) {
            Stage1AirInst *old_arg = args_ptr[call_i];

            if (!ir_analyze_fn_call_inline_arg(ira, fn_proto_node, old_arg, &exec_scope, &next_proto_i))
                return ira->codegen->invalid_inst_gen;
        }

        AstNode *return_type_node = fn_proto_node->data.fn_proto.return_type;
        if (return_type_node == nullptr) {
            ir_add_error(ira, fn_ref,
                buf_sprintf("TODO implement inferred return types https://github.com/ziglang/zig/issues/447"));
            return ira->codegen->invalid_inst_gen;
        }
        ZigType *specified_return_type = ir_analyze_type_expr(ira, exec_scope, return_type_node);
        if (type_is_invalid(specified_return_type))
            return ira->codegen->invalid_inst_gen;
        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_inst_gen;
            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);
        ZigValue *result = nullptr;
        if (cacheable) {
            // We are about to put ZigValues into a hash map. The hash of a lazy value and a
            // fully resolved value must equal, and so we must resolve the lazy values here.
            // The hash function asserts that none of the values are lazy.
            {
                Scope *scope = exec_scope;
                while (scope) {
                    if (scope->id == ScopeIdVarDecl) {
                        ScopeVarDecl *var_scope = (ScopeVarDecl *)scope;
                        if ((err = ir_resolve_lazy_recurse(
                            var_scope->var->decl_node,
                            var_scope->var->const_value)))
                        {
                            return ira->codegen->invalid_inst_gen;
                        }
                    } else if (scope->id == ScopeIdFnDef) {
                        break;
                    } else {
                        zig_unreachable();
                    }
                    scope = scope->parent;
                }
            }

            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;
            ZigValue *result_ptr;
            create_result_ptr(ira->codegen, return_type, &result, &result_ptr);

            if ((err = ir_eval_const_value(ira->codegen, exec_scope, body_node, result_ptr,
                ira->backward_branch_count, ira->backward_branch_quota,
                fn_entry, nullptr, source_node, nullptr, ira->new_irb.exec, return_type_node,
                UndefOk)))
            {
                return ira->codegen->invalid_inst_gen;
            }

            if (inferred_err_set_type != nullptr) {
                inferred_err_set_type->data.error_set.incomplete = false;
                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 = heap::c_allocator.create<ErrorTableEntry *>();
                        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_inst_gen;
            }
        }

        Stage1AirInst *new_instruction = ir_const_move(ira, scope, source_node, result);
        return ir_finish_anal(ira, new_instruction);
    }

    if (fn_type->data.fn.is_generic) {
        if (!fn_entry) {
            ir_add_error(ira, fn_ref,
                buf_sprintf("calling a generic function requires compile-time known function value"));
            return ira->codegen->invalid_inst_gen;
        }

        size_t new_fn_arg_count = first_arg_1_or_0 + args_len;

        Stage1AirInst **casted_args = heap::c_allocator.allocate<Stage1AirInst *>(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 = heap::c_allocator.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, fn_type_id->cc, 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 = heap::c_allocator.create<GenericFnTypeId>();
        generic_id->fn_entry = fn_entry;
        generic_id->param_count = 0;
        generic_id->params = ira->codegen->pass1_arena->allocate<ZigValue>(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_inst_gen;
                first_arg_known_bare = param_type->id != ZigTypeIdPointer;
            }

            Stage1AirInst *first_arg;
            if (!first_arg_known_bare) {
                first_arg = first_arg_ptr;
            } else {
                first_arg = ir_get_deref(ira, first_arg_ptr->scope, first_arg_ptr->source_node,
                        first_arg_ptr, nullptr);
                if (type_is_invalid(first_arg->value->type))
                    return ira->codegen->invalid_inst_gen;
            }

            if (!ir_analyze_fn_call_generic_arg(ira, fn_proto_node, first_arg, first_arg_ptr_src,
                    &impl_fn->child_scope, &next_proto_i, generic_id, &inst_fn_type_id, casted_args, impl_fn))
            {
                return ira->codegen->invalid_inst_gen;
            }
        }

        ZigFn *parent_fn_entry = ira->fn;
        assert(parent_fn_entry);
        for (size_t call_i = 0; call_i < args_len; call_i += 1) {
            Stage1AirInst *arg = args_ptr[call_i];

            AstNode *param_decl_node = fn_proto_node->data.fn_proto.params.at(next_proto_i);
            assert(param_decl_node->type == NodeTypeParamDecl);

            if (!ir_analyze_fn_call_generic_arg(ira, fn_proto_node, arg, arg->source_node,
                        &impl_fn->child_scope,
                &next_proto_i, generic_id, &inst_fn_type_id, casted_args, impl_fn))
            {
                return ira->codegen->invalid_inst_gen;
            }
        }

        if (fn_proto_node->data.fn_proto.align_expr != nullptr) {
            ZigValue *align_result;
            ZigValue *result_ptr;
            create_result_ptr(ira->codegen, get_align_amt_type(ira->codegen), &align_result, &result_ptr);
            if ((err = ir_eval_const_value(ira->codegen, impl_fn->child_scope,
                fn_proto_node->data.fn_proto.align_expr, result_ptr,
                ira->backward_branch_count, ira->backward_branch_quota,
                nullptr, nullptr, fn_proto_node->data.fn_proto.align_expr, nullptr, ira->new_irb.exec,
                nullptr, UndefBad)))
            {
                return ira->codegen->invalid_inst_gen;
            }
            Stage1AirInstConst *const_instruction = ir_create_inst_noval<Stage1AirInstConst>(&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, nullptr, &align_bytes);
            impl_fn->align_bytes = align_bytes;
            inst_fn_type_id.alignment = align_bytes;
        }

        AstNode *return_type_node = fn_proto_node->data.fn_proto.return_type;
        ZigType *specified_return_type = ir_analyze_type_expr(ira, impl_fn->child_scope, return_type_node);
        if (type_is_invalid(specified_return_type))
            return ira->codegen->invalid_inst_gen;

        if(!is_valid_return_type(specified_return_type)){
            ErrorMsg *msg = ir_add_error_node(ira, source_node,
                buf_sprintf("call to generic function with %s return type '%s' not allowed", type_id_name(specified_return_type->id), buf_ptr(&specified_return_type->name)));
            add_error_note(ira->codegen, msg, fn_proto_node, buf_sprintf("function declared here"));

            Tld *tld = find_decl(ira->codegen, &fn_entry->fndef_scope->base, &specified_return_type->name);
            if (tld != nullptr) {
                add_error_note(ira->codegen, msg, tld->source_node, buf_sprintf("type declared here"));
            }
            return ira->codegen->invalid_inst_gen;
        }

        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_inst_gen;
            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, scope, source_node, fn_entry, fn_type, fn_ref, first_arg_ptr,
                    first_arg_ptr_src, CallModifierCompileTime, new_stack, new_stack_src, is_async_call_builtin,
                    args_ptr, args_len, ret_ptr, call_result_loc);
        case ReqCompTimeInvalid:
            return ira->codegen->invalid_inst_gen;
        case ReqCompTimeNo:
            break;
        }

        // We are about to put ZigValues into a hash map. The hash of a lazy value and a
        // fully resolved value must equal, and so we must resolve the lazy values here.
        // The hash function asserts that none of the values are lazy.
        for (size_t i = 0; i < generic_id->param_count; i += 1) {
            ZigValue *generic_param = &generic_id->params[i];
            if (generic_param->special != ConstValSpecialRuntime) {
                if ((err = ir_resolve_lazy_recurse(source_node, generic_param))) {
                    return ira->codegen->invalid_inst_gen;
                }
            }
        }

        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_inst_gen;

            impl_fn->analyzed_executable.source_node = source_node;
            impl_fn->analyzed_executable.parent_exec = ira->new_irb.exec;
            impl_fn->branch_quota = *ira->backward_branch_quota;

            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;
        }

        Stage1AirInst *casted_new_stack = analyze_casted_new_stack(ira, scope, source_node, new_stack,
                new_stack_src, is_async_call_builtin, impl_fn);
        if (casted_new_stack != nullptr && type_is_invalid(casted_new_stack->value->type))
            return ira->codegen->invalid_inst_gen;

        size_t impl_param_count = impl_fn_type_id->param_count;
        if (modifier == CallModifierAsync) {
            Stage1AirInst *result = ir_analyze_async_call(ira, scope, source_node, impl_fn, impl_fn->type_entry,
                nullptr, casted_args, impl_param_count, casted_new_stack, is_async_call_builtin, ret_ptr,
                call_result_loc);
            return ir_finish_anal(ira, result);
        }

        Stage1AirInst *result_loc;
        if (handle_is_ptr(ira->codegen, impl_fn_type_id->return_type)) {
            result_loc = ir_resolve_result(ira, ira->suspend_source_instr, call_result_loc,
                    impl_fn_type_id->return_type, nullptr, true, false);
            if (result_loc != nullptr) {
                if (type_is_invalid(result_loc->value->type) || result_loc->value->type->id == ZigTypeIdUnreachable) {
                    return result_loc;
                }
                if (result_loc->value->type->data.pointer.is_const) {
                    ir_add_error_node(ira, source_node, buf_sprintf("cannot assign to constant"));
                    return ira->codegen->invalid_inst_gen;
                }

                Stage1AirInst *dummy_value = ir_const(ira, scope, source_node, impl_fn_type_id->return_type);
                dummy_value->value->special = ConstValSpecialRuntime;
                Stage1AirInst *dummy_result = ir_implicit_cast2(ira, scope, source_node,
                        dummy_value, result_loc->value->type->data.pointer.child_type);
                if (type_is_invalid(dummy_result->value->type))
                    return ira->codegen->invalid_inst_gen;
                ZigType *res_child_type = result_loc->value->type->data.pointer.child_type;
                if (res_child_type == ira->codegen->builtin_types.entry_anytype) {
                    res_child_type = impl_fn_type_id->return_type;
                }
                if (!handle_is_ptr(ira->codegen, res_child_type)) {
                    ir_reset_result(call_result_loc);
                    result_loc = nullptr;
                }
            }
        } else if (is_async_call_builtin) {
            result_loc = get_async_call_result_loc(ira, scope, source_node, impl_fn_type_id->return_type,
                    is_async_call_builtin, args_ptr, args_len, ret_ptr);
            if (result_loc != nullptr && type_is_invalid(result_loc->value->type))
                return ira->codegen->invalid_inst_gen;
        } else {
            result_loc = nullptr;
        }

        if (impl_fn_type_id->cc == CallingConventionAsync &&
            parent_fn_entry->inferred_async_node == nullptr &&
            modifier != CallModifierNoSuspend)
        {
            parent_fn_entry->inferred_async_node = fn_ref->source_node;
            parent_fn_entry->inferred_async_fn = impl_fn;
        }

        Stage1AirInstCall *new_call_instruction = ir_build_call_gen(ira, scope, source_node,
                impl_fn, nullptr, impl_param_count, casted_args, modifier, casted_new_stack,
                is_async_call_builtin, result_loc, impl_fn_type_id->return_type);

        if (get_scope_typeof(scope) == nullptr) {
            parent_fn_entry->call_list.append(new_call_instruction);
        }

        return ir_finish_anal(ira, &new_call_instruction->base);
    }

    ZigFn *parent_fn_entry = ira->fn;
    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;
    }


    Stage1AirInst **casted_args = heap::c_allocator.allocate<Stage1AirInst *>(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_inst_gen;

        Stage1AirInst *first_arg;
        if (param_type->id == ZigTypeIdPointer) {
            first_arg = first_arg_ptr;
        } else {
            first_arg = ir_get_deref(ira, first_arg_ptr->scope, first_arg_ptr->source_node,
                    first_arg_ptr, nullptr);
            if (type_is_invalid(first_arg->value->type))
                return ira->codegen->invalid_inst_gen;
        }

        Stage1AirInst *casted_arg = ir_implicit_cast2(ira, first_arg->scope, first_arg_ptr_src, first_arg, param_type);
        if (type_is_invalid(casted_arg->value->type))
            return ira->codegen->invalid_inst_gen;

        casted_args[next_arg_index] = casted_arg;
        next_arg_index += 1;
    }
    for (size_t call_i = 0; call_i < args_len; call_i += 1) {
        Stage1AirInst *old_arg = args_ptr[call_i];
        if (type_is_invalid(old_arg->value->type))
            return ira->codegen->invalid_inst_gen;

        Stage1AirInst *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_inst_gen;
            casted_arg = ir_implicit_cast(ira, old_arg, param_type);
            if (type_is_invalid(casted_arg->value->type))
                return ira->codegen->invalid_inst_gen;
        } 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_inst_gen;

    if (fn_entry != nullptr && fn_type_id->cc == CallingConventionInline && modifier == CallModifierNeverInline) {
        ir_add_error_node(ira, source_node,
            buf_sprintf("no-inline call of inline function"));
        return ira->codegen->invalid_inst_gen;
    }

    Stage1AirInst *casted_new_stack = analyze_casted_new_stack(ira, scope, source_node, new_stack, new_stack_src,
            is_async_call_builtin, fn_entry);
    if (casted_new_stack != nullptr && type_is_invalid(casted_new_stack->value->type))
        return ira->codegen->invalid_inst_gen;

    if (modifier == CallModifierAsync) {
        Stage1AirInst *result = ir_analyze_async_call(ira, scope, source_node, fn_entry, fn_type, fn_ref,
                casted_args, call_param_count, casted_new_stack, is_async_call_builtin, ret_ptr, call_result_loc);
        return ir_finish_anal(ira, result);
    }

    if (fn_type_id->cc == CallingConventionAsync &&
        parent_fn_entry->inferred_async_node == nullptr &&
        modifier != CallModifierNoSuspend)
    {
        parent_fn_entry->inferred_async_node = fn_ref->source_node;
        parent_fn_entry->inferred_async_fn = fn_entry;
    }

    Stage1AirInst *result_loc;
    if (handle_is_ptr(ira->codegen, return_type)) {
        result_loc = ir_resolve_result(ira, ira->suspend_source_instr, call_result_loc,
                return_type, nullptr, true, false);
        if (result_loc != nullptr) {
            if (type_is_invalid(result_loc->value->type) || result_loc->value->type->id == ZigTypeIdUnreachable) {
                return result_loc;
            }
            if (result_loc->value->type->data.pointer.is_const) {
                ir_add_error_node(ira, source_node, buf_sprintf("cannot assign to constant"));
                return ira->codegen->invalid_inst_gen;
            }

            ZigType *expected_return_type = result_loc->value->type->data.pointer.child_type;

            Stage1AirInst *dummy_value = ir_const(ira, scope, source_node, return_type);
            dummy_value->value->special = ConstValSpecialRuntime;
            Stage1AirInst *dummy_result = ir_implicit_cast2(ira, scope, source_node,
                    dummy_value, expected_return_type);
            if (type_is_invalid(dummy_result->value->type)) {
                if ((return_type->id == ZigTypeIdErrorUnion || return_type->id == ZigTypeIdErrorSet) &&
                    expected_return_type->id != ZigTypeIdErrorUnion && expected_return_type->id != ZigTypeIdErrorSet)
                {
                    if (call_result_loc->id == ResultLocIdReturn) {
                        add_error_note(ira->codegen, ira->new_irb.exec->first_err_trace_msg,
                            ira->explicit_return_type_source_node, buf_sprintf("function cannot return an error"));
                    } else {
                        add_error_note(ira->codegen, ira->new_irb.exec->first_err_trace_msg, result_loc->source_node,
                            buf_sprintf("cannot store an error in type '%s'", buf_ptr(&expected_return_type->name)));
                    }
                }
                return ira->codegen->invalid_inst_gen;
            }
            if (expected_return_type == ira->codegen->builtin_types.entry_anytype) {
                expected_return_type = return_type;
            }
            if (!handle_is_ptr(ira->codegen, expected_return_type)) {
                ir_reset_result(call_result_loc);
                result_loc = nullptr;
            }
        }
    } else if (is_async_call_builtin) {
        result_loc = get_async_call_result_loc(ira, scope, source_node, return_type, is_async_call_builtin,
                args_ptr, args_len, ret_ptr);
        if (result_loc != nullptr && type_is_invalid(result_loc->value->type))
            return ira->codegen->invalid_inst_gen;
    } else {
        result_loc = nullptr;
    }

    Stage1AirInstCall *new_call_instruction = ir_build_call_gen(ira, scope, source_node, fn_entry, fn_ref,
            call_param_count, casted_args, modifier, casted_new_stack,
            is_async_call_builtin, result_loc, return_type);
    if (get_scope_typeof(scope) == nullptr) {
        parent_fn_entry->call_list.append(new_call_instruction);
    }
    return ir_finish_anal(ira, &new_call_instruction->base);
}

static Stage1AirInst *ir_analyze_fn_call_src(IrAnalyze *ira, Stage1ZirInstCall *call_instruction,
    ZigFn *fn_entry, ZigType *fn_type, Stage1AirInst *fn_ref,
    Stage1AirInst *first_arg_ptr, AstNode *first_arg_ptr_src, CallModifier modifier)
{
    Stage1AirInst *new_stack = nullptr;
    AstNode *new_stack_src = nullptr;
    if (call_instruction->new_stack) {
        new_stack = call_instruction->new_stack->child;
        if (type_is_invalid(new_stack->value->type))
            return ira->codegen->invalid_inst_gen;
        new_stack_src = call_instruction->new_stack->source_node;
    }
    Stage1AirInst **args_ptr = heap::c_allocator.allocate<Stage1AirInst *>(call_instruction->arg_count);
    for (size_t i = 0; i < call_instruction->arg_count; i += 1) {
        args_ptr[i] = call_instruction->args[i]->child;
        if (type_is_invalid(args_ptr[i]->value->type))
            return ira->codegen->invalid_inst_gen;
    }
    Stage1AirInst *ret_ptr = nullptr;
    if (call_instruction->ret_ptr != nullptr) {
        ret_ptr = call_instruction->ret_ptr->child;
        if (type_is_invalid(ret_ptr->value->type))
            return ira->codegen->invalid_inst_gen;
    }
    Stage1AirInst *result = ir_analyze_fn_call(ira, call_instruction->base.scope,
            call_instruction->base.source_node, fn_entry, fn_type, fn_ref,
            first_arg_ptr, first_arg_ptr_src, modifier, new_stack, new_stack_src,
            call_instruction->is_async_call_builtin, args_ptr, call_instruction->arg_count, ret_ptr,
            call_instruction->result_loc);
    heap::c_allocator.deallocate(args_ptr, call_instruction->arg_count);
    return result;
}

static Stage1AirInst *ir_analyze_call_extra(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        Stage1ZirInst *pass1_options, Stage1ZirInst *pass1_fn_ref, Stage1AirInst **args_ptr, size_t args_len,
        ResultLoc *result_loc)
{
    Stage1AirInst *options = pass1_options->child;
    if (type_is_invalid(options->value->type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *fn_ref = pass1_fn_ref->child;
    if (type_is_invalid(fn_ref->value->type))
        return ira->codegen->invalid_inst_gen;

    TypeStructField *modifier_field = find_struct_type_field(options->value->type, buf_create_from_str("modifier"));
    src_assert(modifier_field != nullptr, source_node);
    Stage1AirInst *modifier_inst = ir_analyze_struct_value_field_value(ira, scope, source_node, options, modifier_field);
    ZigValue *modifier_val = ir_resolve_const(ira, modifier_inst, UndefBad);
    if (modifier_val == nullptr)
        return ira->codegen->invalid_inst_gen;
    CallModifier modifier = (CallModifier)bigint_as_u32(&modifier_val->data.x_enum_tag);

    if (ir_should_inline(ira->zir, scope)) {
        switch (modifier) {
            case CallModifierBuiltin:
                zig_unreachable();
            case CallModifierAsync:
                ir_add_error_node(ira, source_node, buf_sprintf("TODO: comptime @call with async modifier"));
                return ira->codegen->invalid_inst_gen;
            case CallModifierCompileTime:
            case CallModifierNone:
            case CallModifierAlwaysInline:
            case CallModifierAlwaysTail:
            case CallModifierNoSuspend:
                modifier = CallModifierCompileTime;
                break;
            case CallModifierNeverInline:
                ir_add_error_node(ira, source_node,
                    buf_sprintf("unable to perform 'never_inline' call at compile-time"));
                return ira->codegen->invalid_inst_gen;
            case CallModifierNeverTail:
                ir_add_error_node(ira, source_node,
                    buf_sprintf("unable to perform 'never_tail' call at compile-time"));
                return ira->codegen->invalid_inst_gen;
        }
    }

    Stage1AirInst *first_arg_ptr = nullptr;
    AstNode *first_arg_ptr_src = nullptr;
    ZigFn *fn = nullptr;
    if (instr_is_comptime(fn_ref)) {
        if (fn_ref->value->type->id == ZigTypeIdBoundFn) {
            assert(fn_ref->value->special == ConstValSpecialStatic);
            fn = fn_ref->value->data.x_bound_fn.fn;
            first_arg_ptr = fn_ref->value->data.x_bound_fn.first_arg;
            first_arg_ptr_src = fn_ref->value->data.x_bound_fn.first_arg_src;
            if (type_is_invalid(first_arg_ptr->value->type))
                return ira->codegen->invalid_inst_gen;
        } else {
            fn = ir_resolve_fn(ira, fn_ref);
        }
    }

    // Some modifiers require the callee to be comptime-known
    switch (modifier) {
        case CallModifierCompileTime:
        case CallModifierAlwaysInline:
        case CallModifierAsync:
            if (fn == nullptr) {
                ir_add_error(ira, modifier_inst,
                    buf_sprintf("the specified modifier requires a comptime-known function"));
                return ira->codegen->invalid_inst_gen;
            }
            ZIG_FALLTHROUGH;
        default:
            break;
    }

    ZigType *fn_type = (fn != nullptr) ? fn->type_entry : fn_ref->value->type;

    TypeStructField *stack_field = find_struct_type_field(options->value->type, buf_create_from_str("stack"));
    src_assert(stack_field != nullptr, source_node);
    Stage1AirInst *opt_stack = ir_analyze_struct_value_field_value(ira, scope, source_node, options, stack_field);
    if (type_is_invalid(opt_stack->value->type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *stack_is_non_null_inst = ir_analyze_test_non_null(ira, scope, source_node, opt_stack);
    bool stack_is_non_null;
    if (!ir_resolve_bool(ira, stack_is_non_null_inst, &stack_is_non_null))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *stack = nullptr;
    AstNode *stack_src = nullptr;
    if (stack_is_non_null) {
        stack = ir_analyze_optional_value_payload_value(ira, scope, source_node, opt_stack, false);
        if (type_is_invalid(stack->value->type))
            return ira->codegen->invalid_inst_gen;
        stack_src = stack->source_node;
    }

    return ir_analyze_fn_call(ira, scope, source_node, fn, fn_type, fn_ref, first_arg_ptr, first_arg_ptr_src,
        modifier, stack, stack_src, false, args_ptr, args_len, nullptr, result_loc);
}

static Stage1AirInst *ir_analyze_async_call_extra(IrAnalyze *ira, Scope *scope, AstNode *source_node, CallModifier modifier,
        Stage1ZirInst *pass1_fn_ref, Stage1ZirInst *ret_ptr, Stage1ZirInst *new_stack, Stage1AirInst **args_ptr, size_t args_len, ResultLoc *result_loc)
{
    Stage1AirInst *fn_ref = pass1_fn_ref->child;
    if (type_is_invalid(fn_ref->value->type))
        return ira->codegen->invalid_inst_gen;

    if (ir_should_inline(ira->zir, scope)) {
        ir_add_error_node(ira, source_node, buf_sprintf("TODO: comptime @asyncCall"));
            return ira->codegen->invalid_inst_gen;
    }

    Stage1AirInst *first_arg_ptr = nullptr;
    AstNode *first_arg_ptr_src = nullptr;
    ZigFn *fn = nullptr;
    if (instr_is_comptime(fn_ref)) {
        if (fn_ref->value->type->id == ZigTypeIdBoundFn) {
            assert(fn_ref->value->special == ConstValSpecialStatic);
            fn = fn_ref->value->data.x_bound_fn.fn;
            first_arg_ptr = fn_ref->value->data.x_bound_fn.first_arg;
            first_arg_ptr_src = fn_ref->value->data.x_bound_fn.first_arg_src;
            if (type_is_invalid(first_arg_ptr->value->type))
                return ira->codegen->invalid_inst_gen;
        } else {
            fn = ir_resolve_fn(ira, fn_ref);
        }
    }

    Stage1AirInst *ret_ptr_uncasted = nullptr;
    if (ret_ptr != nullptr) {
        ret_ptr_uncasted = ret_ptr->child;
        if (type_is_invalid(ret_ptr_uncasted->value->type))
            return ira->codegen->invalid_inst_gen;
    }

    ZigType *fn_type = (fn != nullptr) ? fn->type_entry : fn_ref->value->type;
    Stage1AirInst *casted_new_stack = analyze_casted_new_stack(ira, scope, source_node,
            new_stack->child, new_stack->source_node, true, fn);
    if (casted_new_stack != nullptr && type_is_invalid(casted_new_stack->value->type))
        return ira->codegen->invalid_inst_gen;

    return ir_analyze_fn_call(ira, scope, source_node, fn, fn_type, fn_ref, first_arg_ptr,
            first_arg_ptr_src, modifier, casted_new_stack, new_stack->source_node, true, args_ptr,
            args_len, ret_ptr_uncasted, result_loc);
}

static bool ir_extract_tuple_call_args(IrAnalyze *ira, Scope *scope, AstNode *source_node, Stage1AirInst *args, Stage1AirInst ***args_ptr, size_t *args_len) {
    ZigType *args_type = args->value->type;
    if (type_is_invalid(args_type))
        return false;

    if (args_type->id != ZigTypeIdStruct) {
        ir_add_error(ira, args,
            buf_sprintf("expected tuple or struct, found '%s'", buf_ptr(&args_type->name)));
        return false;
    }

    if (is_tuple(args_type)) {
        *args_len = args_type->data.structure.src_field_count;
        *args_ptr = heap::c_allocator.allocate<Stage1AirInst *>(*args_len);
        for (size_t i = 0; i < *args_len; i += 1) {
            TypeStructField *arg_field = args_type->data.structure.fields[i];
            (*args_ptr)[i] = ir_analyze_struct_value_field_value(ira, scope, source_node, args, arg_field);
            if (type_is_invalid((*args_ptr)[i]->value->type))
                return false;
        }
    } else {
        ir_add_error(ira, args, buf_sprintf("TODO: struct args"));
        return false;
    }
    return true;
}

static Stage1AirInst *ir_analyze_instruction_call_extra(IrAnalyze *ira, Stage1ZirInstCallExtra *instruction) {
    Stage1AirInst *args = instruction->args->child;
    Stage1AirInst **args_ptr = nullptr;
    size_t args_len = 0;
    if (!ir_extract_tuple_call_args(ira, instruction->base.scope, instruction->base.source_node, args, &args_ptr, &args_len)) {
        return ira->codegen->invalid_inst_gen;
    }

    Stage1AirInst *result = ir_analyze_call_extra(ira, instruction->base.scope, instruction->base.source_node, instruction->options,
            instruction->fn_ref, args_ptr, args_len, instruction->result_loc);
    heap::c_allocator.deallocate(args_ptr, args_len);
    return result;
}

static Stage1AirInst *ir_analyze_instruction_async_call_extra(IrAnalyze *ira, Stage1ZirInstAsyncCallExtra *instruction) {
    Stage1AirInst *args = instruction->args->child;
    Stage1AirInst **args_ptr = nullptr;
    size_t args_len = 0;
    if (!ir_extract_tuple_call_args(ira, instruction->base.scope, instruction->base.source_node, args, &args_ptr, &args_len)) {
        return ira->codegen->invalid_inst_gen;
    }

    Stage1AirInst *result = ir_analyze_async_call_extra(ira, instruction->base.scope, instruction->base.source_node, instruction->modifier,
            instruction->fn_ref, instruction->ret_ptr, instruction->new_stack, args_ptr, args_len, instruction->result_loc);
    heap::c_allocator.deallocate(args_ptr, args_len);
    return result;
}

static Stage1AirInst *ir_analyze_instruction_call_args(IrAnalyze *ira, Stage1ZirInstCallArgs *instruction) {
    Stage1AirInst **args_ptr = heap::c_allocator.allocate<Stage1AirInst *>(instruction->args_len);
    for (size_t i = 0; i < instruction->args_len; i += 1) {
        args_ptr[i] = instruction->args_ptr[i]->child;
        if (type_is_invalid(args_ptr[i]->value->type))
            return ira->codegen->invalid_inst_gen;
    }

    Stage1AirInst *result = ir_analyze_call_extra(ira, instruction->base.scope, instruction->base.source_node, instruction->options,
            instruction->fn_ref, args_ptr, instruction->args_len, instruction->result_loc);
    heap::c_allocator.deallocate(args_ptr, instruction->args_len);
    return result;
}

static Stage1AirInst *ir_analyze_instruction_call(IrAnalyze *ira, Stage1ZirInstCall *call_instruction) {
    Stage1AirInst *fn_ref = call_instruction->fn_ref->child;
    if (type_is_invalid(fn_ref->value->type))
        return ira->codegen->invalid_inst_gen;

    bool is_comptime = (call_instruction->modifier == CallModifierCompileTime) ||
        ir_should_inline(ira->zir, call_instruction->base.scope);
    CallModifier modifier = is_comptime ? CallModifierCompileTime : call_instruction->modifier;

    if (is_comptime || instr_is_comptime(fn_ref)) {
        if (fn_ref->value->type->id == ZigTypeIdMetaType) {
            ZigType *ty = ir_resolve_type(ira, fn_ref);
            if (ty == nullptr)
                return ira->codegen->invalid_inst_gen;
            ErrorMsg *msg = ir_add_error(ira, fn_ref,
                buf_sprintf("type '%s' not a function", buf_ptr(&ty->name)));
            add_error_note(ira->codegen, msg, call_instruction->base.source_node,
                buf_sprintf("use @as builtin for type coercion"));
            return ira->codegen->invalid_inst_gen;
        } else if (fn_ref->value->type->id == ZigTypeIdFn) {
            ZigFn *fn_table_entry = ir_resolve_fn(ira, fn_ref);
            ZigType *fn_type = fn_table_entry ? fn_table_entry->type_entry : fn_ref->value->type;
            CallModifier modifier = is_comptime ? CallModifierCompileTime : call_instruction->modifier;
            return ir_analyze_fn_call_src(ira, call_instruction, fn_table_entry, fn_type,
                fn_ref, nullptr, nullptr, modifier);
        } 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;
            Stage1AirInst *first_arg_ptr = fn_ref->value->data.x_bound_fn.first_arg;
            AstNode *first_arg_ptr_src = fn_ref->value->data.x_bound_fn.first_arg_src;
            CallModifier modifier = is_comptime ? CallModifierCompileTime : call_instruction->modifier;
            return ir_analyze_fn_call_src(ira, call_instruction, fn_table_entry, fn_table_entry->type_entry,
                fn_ref, first_arg_ptr, first_arg_ptr_src, modifier);
        } else {
            ir_add_error(ira, fn_ref,
                buf_sprintf("type '%s' not a function", buf_ptr(&fn_ref->value->type->name)));
            return ira->codegen->invalid_inst_gen;
        }
    }

    if (fn_ref->value->type->id == ZigTypeIdFn) {
        return ir_analyze_fn_call_src(ira, call_instruction, nullptr, fn_ref->value->type,
            fn_ref, nullptr, nullptr, modifier);
    } else {
        ir_add_error(ira, fn_ref,
            buf_sprintf("type '%s' not a function", buf_ptr(&fn_ref->value->type->name)));
        return ira->codegen->invalid_inst_gen;
    }
}

// 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,
        ZigValue *out_val, ZigValue *ptr_val)
{
    Error err;
    assert(out_val->type != nullptr);

    ZigValue *pointee = const_ptr_pointee_unchecked(codegen, ptr_val);
    src_assert(pointee->type != nullptr, source_node);

    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(codegen, out_val->type, pointee->type)) {
            copy_const_val(codegen, out_val, pointee);
            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;
        buf_deinit(&buf);
        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 ConstPtrSpecialSubArray: {
            ZigValue *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: ir_read_const_ptr ConstPtrSpecialSubArray !ConstArraySpecialNone");
            if (dst_size > src_size) {
                size_t elem_index = ptr_val->data.x_ptr.data.base_array.elem_index;
                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_size = src_size;
            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) {
                ZigValue *elem_val = &array_val->data.x_array.data.s_none.elements[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;
            buf_deinit(&buf);
            return ErrorNone;
        }
        case ConstPtrSpecialBaseArray: {
            ZigValue *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: ir_read_const_ptr ConstPtrSpecialBaseArray !ConstArraySpecialNone");
            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) {
                ZigValue *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;
            buf_deinit(&buf);
            return ErrorNone;
        }
        case ConstPtrSpecialBaseStruct:
        case ConstPtrSpecialBaseErrorUnionCode:
        case ConstPtrSpecialBaseErrorUnionPayload:
        case ConstPtrSpecialBaseOptionalPayload:
        case ConstPtrSpecialDiscard:
        case ConstPtrSpecialHardCodedAddr:
        case ConstPtrSpecialFunction:
            zig_panic("TODO: ir_read_const_ptr");
    }
    zig_unreachable();
}

static Stage1AirInst *ir_analyze_optional_type(IrAnalyze *ira, Stage1ZirInstUnOp *instruction) {
    Stage1AirInst *result = ir_const(ira, instruction->base.scope, instruction->base.source_node, ira->codegen->builtin_types.entry_type);
    result->value->special = ConstValSpecialLazy;

    LazyValueOptType *lazy_opt_type = heap::c_allocator.create<LazyValueOptType>();
    lazy_opt_type->ira = ira; ira_ref(ira);
    result->value->data.x_lazy = &lazy_opt_type->base;
    lazy_opt_type->base.id = LazyValueIdOptType;

    lazy_opt_type->payload_type = instruction->value->child;
    if (ir_resolve_type_lazy(ira, lazy_opt_type->payload_type) == nullptr)
        return ira->codegen->invalid_inst_gen;

    return result;
}

static ErrorMsg *ir_eval_negation_scalar(IrAnalyze *ira, Scope *scope, AstNode *source_node, ZigType *scalar_type,
        ZigValue *operand_val, ZigValue *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->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_node(ira, source_node, 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, scalar_type->data.integral.is_signed);
    } 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_node(ira, source_node, buf_sprintf("negation caused overflow"));
    }
    return nullptr;
}

static Stage1AirInst *ir_analyze_negation(IrAnalyze *ira, Stage1ZirInstUnOp *instruction) {
    Stage1AirInst *value = instruction->value->child;
    ZigType *expr_type = value->value->type;
    if (type_is_invalid(expr_type))
        return ira->codegen->invalid_inst_gen;

    bool is_wrap_op = (instruction->op_id == IrUnOpNegationWrap);

    ZigType *scalar_type = (expr_type->id == ZigTypeIdVector) ?
            expr_type->data.vector.elem_type : expr_type;

    switch (scalar_type->id) {
        case ZigTypeIdComptimeInt:
        case ZigTypeIdFloat:
        case ZigTypeIdComptimeFloat:
            break;
        case ZigTypeIdInt:
            if (is_wrap_op || scalar_type->data.integral.is_signed)
                break;
            ZIG_FALLTHROUGH;
        default:
            ir_add_error_node(ira, instruction->base.source_node,
                buf_sprintf("negation of type '%s'", buf_ptr(&scalar_type->name)));
            return ira->codegen->invalid_inst_gen;
    }

    if (instr_is_comptime(value)) {
        ZigValue *operand_val = ir_resolve_const(ira, value, UndefBad);
        if (!operand_val)
            return ira->codegen->invalid_inst_gen;

        Stage1AirInst *result_instruction = ir_const(ira, instruction->base.scope, instruction->base.source_node, expr_type);
        ZigValue *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) {
                ZigValue *scalar_operand_val = &operand_val->data.x_array.data.s_none.elements[i];
                ZigValue *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.scope, instruction->base.source_node, 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_inst_gen;
                }
            }
            out_val->type = expr_type;
            out_val->special = ConstValSpecialStatic;
        } else {
            if (ir_eval_negation_scalar(ira, instruction->base.scope, instruction->base.source_node, scalar_type, operand_val, out_val,
                        is_wrap_op) != nullptr)
            {
                return ira->codegen->invalid_inst_gen;
            }
        }
        return result_instruction;
    }

    return ir_build_negation(ira, instruction->base.scope, instruction->base.source_node, value, expr_type, is_wrap_op);
}

static Stage1AirInst *ir_analyze_bin_not(IrAnalyze *ira, Stage1ZirInstUnOp *instruction) {
    Stage1AirInst *value = instruction->value->child;
    ZigType *expr_type = value->value->type;
    if (type_is_invalid(expr_type))
        return ira->codegen->invalid_inst_gen;

    ZigType *scalar_type = (expr_type->id == ZigTypeIdVector) ?
        expr_type->data.vector.elem_type : expr_type;

    if (scalar_type->id != ZigTypeIdInt) {
        ir_add_error_node(ira, instruction->base.source_node,
            buf_sprintf("unable to perform binary not operation on type '%s'", buf_ptr(&expr_type->name)));
        return ira->codegen->invalid_inst_gen;
    }

    if (instr_is_comptime(value)) {
        ZigValue *expr_val = ir_resolve_const(ira, value, UndefBad);
        if (expr_val == nullptr)
            return ira->codegen->invalid_inst_gen;

        Stage1AirInst *result = ir_const(ira, instruction->base.scope, instruction->base.source_node, expr_type);

        if (expr_type->id == ZigTypeIdVector) {
            expand_undef_array(ira->codegen, expr_val);
            result->value->special = ConstValSpecialUndef;
            expand_undef_array(ira->codegen, result->value);

            for (size_t i = 0; i < expr_type->data.vector.len; i++) {
                ZigValue *src_val = &expr_val->data.x_array.data.s_none.elements[i];
                ZigValue *dst_val = &result->value->data.x_array.data.s_none.elements[i];

                dst_val->type = scalar_type;
                dst_val->special = ConstValSpecialStatic;
                bigint_not(&dst_val->data.x_bigint, &src_val->data.x_bigint,
                    scalar_type->data.integral.bit_count, scalar_type->data.integral.is_signed);
            }
        } else {
            bigint_not(&result->value->data.x_bigint, &expr_val->data.x_bigint,
                scalar_type->data.integral.bit_count, scalar_type->data.integral.is_signed);
        }

        return result;
    }

    return ir_build_binary_not(ira, instruction->base.scope, instruction->base.source_node, value, expr_type);
}

static Stage1AirInst *ir_analyze_instruction_un_op(IrAnalyze *ira, Stage1ZirInstUnOp *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: {
            Stage1AirInst *ptr = instruction->value->child;
            if (type_is_invalid(ptr->value->type))
                return ira->codegen->invalid_inst_gen;
            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_inst_gen;
            }

            Stage1AirInst *result = ir_get_deref(ira, instruction->base.scope,
                    instruction->base.source_node, ptr, instruction->result_loc);
            if (type_is_invalid(result->value->type))
                return ira->codegen->invalid_inst_gen;

            // If the result needs to be an lvalue, type check it
            if (instruction->lval != LValNone && result->value->type->id != ZigTypeIdPointer) {
                ir_add_error_node(ira, instruction->base.source_node,
                    buf_sprintf("attempt to dereference non-pointer type '%s'", buf_ptr(&result->value->type->name)));
                return ira->codegen->invalid_inst_gen;
            }

            return result;
        }
        case IrUnOpOptional:
            return ir_analyze_optional_type(ira, instruction);
    }
    zig_unreachable();
}

static void ir_push_resume(IrAnalyze *ira, IrSuspendPosition pos) {
    Stage1ZirBasicBlock *old_bb = ira->zir->basic_block_list.at(pos.basic_block_index);
    if (old_bb->in_resume_stack) return;
    ira->resume_stack.append(pos);
    old_bb->in_resume_stack = true;
}

static void ir_push_resume_block(IrAnalyze *ira, Stage1ZirBasicBlock *old_bb) {
    if (ira->resume_stack.length != 0) {
        ir_push_resume(ira, {old_bb->index, 0});
    }
}

static Stage1AirInst *ir_analyze_instruction_br(IrAnalyze *ira, Stage1ZirInstBr *br_instruction) {
    Stage1ZirBasicBlock *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 && old_dest_block->suspend_instruction_ref == nullptr))
        return ir_inline_bb(ira, br_instruction->base.source_node, old_dest_block);

    Stage1AirBasicBlock *new_bb = ir_get_new_bb_runtime(ira, old_dest_block, &br_instruction->base);
    if (new_bb == nullptr)
        return ir_unreach_error(ira);

    ir_push_resume_block(ira, old_dest_block);

    Stage1AirInst *result = ir_build_br_gen(ira, br_instruction->base.scope, br_instruction->base.source_node, new_bb);
    return ir_finish_anal(ira, result);
}

static Stage1AirInst *ir_analyze_instruction_cond_br(IrAnalyze *ira, Stage1ZirInstCondBr *cond_br_instruction) {
    Stage1AirInst *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;
    Stage1AirInst *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);

        Stage1ZirBasicBlock *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 && old_dest_block->suspend_instruction_ref == nullptr))
            return ir_inline_bb(ira, cond_br_instruction->base.source_node, old_dest_block);

        Stage1AirBasicBlock *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);

        ir_push_resume_block(ira, old_dest_block);

        Stage1AirInst *result = ir_build_br_gen(ira, cond_br_instruction->base.scope,
                cond_br_instruction->base.source_node, new_dest_block);
        return ir_finish_anal(ira, result);
    }

    assert(cond_br_instruction->then_block != cond_br_instruction->else_block);
    Stage1AirBasicBlock *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);

    Stage1AirBasicBlock *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);

    ir_push_resume_block(ira, cond_br_instruction->else_block);
    ir_push_resume_block(ira, cond_br_instruction->then_block);

    Stage1AirInst *result = ir_build_cond_br_gen(ira, cond_br_instruction->base.scope,
            cond_br_instruction->base.source_node, casted_condition, new_then_block,
            new_else_block);
    return ir_finish_anal(ira, result);
}

static Stage1AirInst *ir_analyze_instruction_unreachable(IrAnalyze *ira,
        Stage1ZirInstUnreachable *unreachable_instruction)
{
    if (ir_should_inline(ira->zir, unreachable_instruction->base.scope)) {
        ir_add_error_node(ira, unreachable_instruction->base.source_node,
                buf_sprintf("reached unreachable code"));
        return ir_unreach_error(ira);
    }

    Stage1AirInst *result = ir_build_unreachable_gen(ira, unreachable_instruction->base.scope,
            unreachable_instruction->base.source_node);
    return ir_finish_anal(ira, result);
}

static Stage1AirInst *ir_analyze_instruction_phi(IrAnalyze *ira, Stage1ZirInstPhi *phi_instruction) {
    Error err;

    if (ira->const_predecessor_bb) {
        for (size_t i = 0; i < phi_instruction->incoming_count; i += 1) {
            Stage1ZirBasicBlock *predecessor = phi_instruction->incoming_blocks[i];
            if (predecessor != ira->const_predecessor_bb)
                continue;
            Stage1AirInst *value = phi_instruction->incoming_values[i]->child;
            assert(value->value->type);
            if (type_is_invalid(value->value->type))
                return ira->codegen->invalid_inst_gen;

            if (value->value->special != ConstValSpecialRuntime) {
                Stage1AirInst *result = ir_const(ira, phi_instruction->base.scope,
                        phi_instruction->base.source_node, nullptr);
                copy_const_val(ira->codegen, result->value, value->value);
                return result;
            } else {
                return value;
            }
        }
        zig_unreachable();
    }

    ResultLocPeerParent *peer_parent = phi_instruction->peer_parent;
    if (peer_parent != nullptr && !peer_parent->skipped && !peer_parent->done_resuming &&
        peer_parent->peers.length >= 2)
    {
        if (peer_parent->resolved_type == nullptr) {
            Stage1AirInst **instructions = heap::c_allocator.allocate<Stage1AirInst *>(peer_parent->peers.length);
            for (size_t i = 0; i < peer_parent->peers.length; i += 1) {
                ResultLocPeer *this_peer = peer_parent->peers.at(i);

                Stage1AirInst *gen_instruction = this_peer->base.gen_instruction;
                if (gen_instruction == nullptr) {
                    // unreachable instructions will cause implicit_elem_type to be null
                    if (this_peer->base.implicit_elem_type == nullptr) {
                        instructions[i] = ir_const_unreachable(ira, this_peer->base.source_instruction->scope, this_peer->base.source_instruction->source_node);
                    } else {
                        instructions[i] = ir_const(ira, this_peer->base.source_instruction->scope,
                                this_peer->base.source_instruction->source_node,
                                this_peer->base.implicit_elem_type);
                        instructions[i]->value->special = ConstValSpecialRuntime;
                    }
                } else {
                    instructions[i] = gen_instruction;
                }

            }
            ZigType *expected_type = ir_result_loc_expected_type(ira, peer_parent->parent);
            peer_parent->resolved_type = ir_resolve_peer_types(ira,
                    peer_parent->base.source_instruction->source_node, expected_type, instructions,
                    peer_parent->peers.length);
            if (type_is_invalid(peer_parent->resolved_type))
                return ira->codegen->invalid_inst_gen;

            // the logic below assumes there are no instructions in the new current basic block yet
            src_assert(ira->new_irb.current_basic_block->instruction_list.length == 0,
                    phi_instruction->base.source_node);

            // In case resolving the parent activates a suspend, do it now
            Stage1AirInst *parent_result_loc = ir_resolve_result(ira, &phi_instruction->base, peer_parent->parent,
                    peer_parent->resolved_type, nullptr, false, true);
            if (parent_result_loc != nullptr &&
                (type_is_invalid(parent_result_loc->value->type) || parent_result_loc->value->type->id == ZigTypeIdUnreachable))
            {
                return parent_result_loc;
            }
            // If the above code generated any instructions in the current basic block, we need
            // to move them to the peer parent predecessor.
            ZigList<Stage1AirInst *> instrs_to_move = {};
            while (ira->new_irb.current_basic_block->instruction_list.length != 0) {
                instrs_to_move.append(ira->new_irb.current_basic_block->instruction_list.pop());
            }
            if (instrs_to_move.length != 0) {
                Stage1AirBasicBlock *predecessor = peer_parent->base.source_instruction->owner_bb->child;
                Stage1AirInst *branch_instruction = predecessor->instruction_list.pop();
                src_assert(branch_instruction->value->type->id == ZigTypeIdUnreachable,
                        phi_instruction->base.source_node);
                while (instrs_to_move.length != 0) {
                    predecessor->instruction_list.append(instrs_to_move.pop());
                }
                predecessor->instruction_list.append(branch_instruction);
                instrs_to_move.deinit();
            }
        }

        IrSuspendPosition suspend_pos;
        ira_suspend(ira, &phi_instruction->base, nullptr, &suspend_pos);
        ir_push_resume(ira, suspend_pos);

        for (size_t i = 0; i < peer_parent->peers.length; i += 1) {
            ResultLocPeer *opposite_peer = peer_parent->peers.at(peer_parent->peers.length - i - 1);
            if (opposite_peer->base.implicit_elem_type != nullptr &&
                opposite_peer->base.implicit_elem_type->id != ZigTypeIdUnreachable)
            {
                ir_push_resume(ira, opposite_peer->suspend_pos);
            }
        }

        peer_parent->done_resuming = true;
        return ira_resume(ira);
    }

    ZigList<Stage1AirBasicBlock*> new_incoming_blocks = {0};
    ZigList<Stage1AirInst*> new_incoming_values = {0};

    for (size_t i = 0; i < phi_instruction->incoming_count; i += 1) {
        Stage1ZirBasicBlock *predecessor = phi_instruction->incoming_blocks[i];
        if (predecessor->ref_count == 0)
            continue;


        Stage1ZirInst *old_value = phi_instruction->incoming_values[i];
        assert(old_value);
        Stage1AirInst *new_value = old_value->child;
        if (!new_value || new_value->value->type->id == ZigTypeIdUnreachable || predecessor->child == nullptr)
            continue;

        if (type_is_invalid(new_value->value->type))
            return ira->codegen->invalid_inst_gen;


        assert(predecessor->child);
        new_incoming_blocks.append(predecessor->child);
        new_incoming_values.append(new_value);
    }

    if (new_incoming_blocks.length == 0) {
        Stage1AirInst *result = ir_build_unreachable_gen(ira, phi_instruction->base.scope,
                phi_instruction->base.source_node);
        return ir_finish_anal(ira, result);
    }

    if (new_incoming_blocks.length == 1) {
        Stage1AirInst *incoming_value = new_incoming_values.at(0);
        new_incoming_blocks.deinit();
        new_incoming_values.deinit();
        return incoming_value;
    }

    ZigType *resolved_type = nullptr;
    if (peer_parent != nullptr) {
        bool peer_parent_has_type;
        if ((err = ir_result_has_type(ira, peer_parent->parent, &peer_parent_has_type)))
            return ira->codegen->invalid_inst_gen;
        if (peer_parent_has_type) {
            if (peer_parent->parent->id == ResultLocIdReturn) {
                resolved_type = ira->explicit_return_type;
            } else if (peer_parent->parent->id == ResultLocIdCast) {
                resolved_type = ir_resolve_type(ira, peer_parent->parent->source_instruction->child);
            } else if (peer_parent->parent->resolved_loc) {
                ZigType *resolved_loc_ptr_type = peer_parent->parent->resolved_loc->value->type;
                src_assert(resolved_loc_ptr_type->id == ZigTypeIdPointer,
                        phi_instruction->base.source_node);
                resolved_type = resolved_loc_ptr_type->data.pointer.child_type;
            }

            if (resolved_type != nullptr && type_is_invalid(resolved_type))
                return ira->codegen->invalid_inst_gen;
        }
    }

    if (resolved_type == nullptr) {
        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_inst_gen;
    }

    switch (type_has_one_possible_value(ira->codegen, resolved_type)) {
    case OnePossibleValueInvalid:
        return ira->codegen->invalid_inst_gen;
    case OnePossibleValueYes:
        return ir_const_move(ira, phi_instruction->base.scope, phi_instruction->base.source_node,
                 get_the_one_possible_value(ira->codegen, resolved_type));
    case OnePossibleValueNo:
        break;
    }

    switch (type_requires_comptime(ira->codegen, resolved_type)) {
    case ReqCompTimeInvalid:
        return ira->codegen->invalid_inst_gen;
    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_inst_gen;
    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.
    Stage1AirBasicBlock *cur_bb = ira->new_irb.current_basic_block;
    for (size_t i = 0; i < new_incoming_values.length; i += 1) {
        Stage1AirInst *new_value = new_incoming_values.at(i);
        Stage1AirBasicBlock *predecessor = new_incoming_blocks.at(i);
        src_assert(predecessor->instruction_list.length != 0, phi_instruction->base.source_node);
        Stage1AirInst *branch_instruction = predecessor->instruction_list.pop();
        ir_set_cursor_at_end_gen(&ira->new_irb, predecessor);
        Stage1AirInst *casted_value = ir_implicit_cast(ira, new_value, resolved_type);
        if (type_is_invalid(casted_value->value->type)) {
            return ira->codegen->invalid_inst_gen;
        }
        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_gen(&ira->new_irb, cur_bb);

    Stage1AirInst *result = ir_build_phi_gen(ira, phi_instruction->base.scope,
            phi_instruction->base.source_node, new_incoming_blocks.length,
            new_incoming_blocks.items, new_incoming_values.items, resolved_type);

    if (all_stack_ptrs) {
        assert(result->value->special == ConstValSpecialRuntime);
        result->value->data.rh_ptr = RuntimeHintPtrStack;
    }

    return result;
}

static Stage1AirInst *ir_analyze_instruction_var_ptr(IrAnalyze *ira, Stage1ZirInstVarPtr *instruction) {
    ZigVar *var = instruction->var;
    Stage1AirInst *result = ir_get_var_ptr(ira, instruction->base.scope, instruction->base.source_node, var);
    if (instruction->crossed_fndef_scope != nullptr && !instr_is_comptime(result)) {
        ErrorMsg *msg = ir_add_error_node(ira, instruction->base.source_node,
            buf_sprintf("'%s' not accessible from inner function", 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_inst_gen;
    }
    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_extra2(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,
            ptr_type->data.pointer.allow_zero,
            ptr_type->data.pointer.vector_index,
            ptr_type->data.pointer.inferred_struct_field,
            ptr_type->data.pointer.sentinel);
}

static ZigType *adjust_ptr_sentinel(CodeGen *g, ZigType *ptr_type, ZigValue *new_sentinel) {
    assert(ptr_type->id == ZigTypeIdPointer);
    return get_pointer_to_type_extra2(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,
            ptr_type->data.pointer.explicit_alignment,
            ptr_type->data.pointer.bit_offset_in_host, ptr_type->data.pointer.host_int_bytes,
            ptr_type->data.pointer.allow_zero,
            ptr_type->data.pointer.vector_index,
            ptr_type->data.pointer.inferred_struct_field,
            new_sentinel);
}

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_extra2(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,
            ptr_type->data.pointer.allow_zero,
            ptr_type->data.pointer.vector_index,
            ptr_type->data.pointer.inferred_struct_field,
            (ptr_len != PtrLenUnknown) ? nullptr : ptr_type->data.pointer.sentinel);
}

static ZigType *adjust_ptr_allow_zero(CodeGen *g, ZigType *ptr_type, bool allow_zero) {
    assert(ptr_type->id == ZigTypeIdPointer);
    return get_pointer_to_type_extra2(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,
            ptr_type->data.pointer.explicit_alignment,
            ptr_type->data.pointer.bit_offset_in_host, ptr_type->data.pointer.host_int_bytes,
            allow_zero,
            ptr_type->data.pointer.vector_index,
            ptr_type->data.pointer.inferred_struct_field,
            ptr_type->data.pointer.sentinel);
}

static ZigType *adjust_ptr_const(CodeGen *g, ZigType *ptr_type, bool is_const) {
    assert(ptr_type->id == ZigTypeIdPointer);
    return get_pointer_to_type_extra2(g,
            ptr_type->data.pointer.child_type,
            is_const, ptr_type->data.pointer.is_volatile,
            ptr_type->data.pointer.ptr_len,
            ptr_type->data.pointer.explicit_alignment,
            ptr_type->data.pointer.bit_offset_in_host, ptr_type->data.pointer.host_int_bytes,
            ptr_type->data.pointer.allow_zero,
            ptr_type->data.pointer.vector_index,
            ptr_type->data.pointer.inferred_struct_field,
            ptr_type->data.pointer.sentinel);
}

static Error compute_elem_align(IrAnalyze *ira, ZigType *elem_type, uint32_t base_ptr_align,
        uint64_t elem_index, uint32_t *result)
{
    Error err;

    if (base_ptr_align == 0) {
        *result = 0;
        return ErrorNone;
    }

    // figure out the largest alignment possible
    if ((err = type_resolve(ira->codegen, elem_type, ResolveStatusSizeKnown)))
        return err;

    uint64_t elem_size = type_size(ira->codegen, elem_type);
    uint64_t abi_align = get_abi_alignment(ira->codegen, elem_type);
    uint64_t ptr_align = base_ptr_align;

    uint64_t chosen_align = abi_align;
    if (ptr_align >= abi_align) {
        while (ptr_align > abi_align) {
            if ((elem_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();
    }

    *result = chosen_align;
    return ErrorNone;
}

static Stage1AirInst *ir_analyze_instruction_elem_ptr(IrAnalyze *ira, Stage1ZirInstElemPtr *elem_ptr_instruction) {
    Error err;
    Stage1AirInst *array_ptr = elem_ptr_instruction->array_ptr->child;
    if (type_is_invalid(array_ptr->value->type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *elem_index = elem_ptr_instruction->elem_index->child;
    if (type_is_invalid(elem_index->value->type))
        return ira->codegen->invalid_inst_gen;

    ZigValue *orig_array_ptr_val = array_ptr->value;

    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_inst_gen;

    if (array_type->id == ZigTypeIdPointer &&
        array_type->data.pointer.ptr_len == PtrLenSingle &&
        array_type->data.pointer.child_type->id == ZigTypeIdArray)
    {
        Stage1AirInst *ptr_value = ir_get_deref(ira, elem_ptr_instruction->base.scope,
                elem_ptr_instruction->base.source_node, array_ptr, nullptr);
        if (type_is_invalid(ptr_value->value->type))
            return ira->codegen->invalid_inst_gen;

        array_type = array_type->data.pointer.child_type;
        ptr_type = ptr_type->data.pointer.child_type;

        orig_array_ptr_val = ptr_value->value;
    }

    if (array_type->id == ZigTypeIdArray) {
        if(array_type->data.array.len == 0 && array_type->data.array.sentinel == nullptr){
            ir_add_error_node(ira, elem_ptr_instruction->base.source_node,
                    buf_sprintf("accessing a zero length array is not allowed"));
            return ira->codegen->invalid_inst_gen;
        }

        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, false);
        } else {
            uint64_t elem_val_scalar;
            if (!ir_resolve_usize(ira, elem_index, &elem_val_scalar))
                return ira->codegen->invalid_inst_gen;

            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, false);
        }
    } 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_inst_gen;
        }
        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 == ZigTypeIdVector) {
        // This depends on whether the element index is comptime, so it is computed later.
        return_type = nullptr;
    } else if (elem_ptr_instruction->init_array_type_source_node != nullptr &&
        array_type->id == ZigTypeIdStruct &&
        array_type->data.structure.resolve_status == ResolveStatusBeingInferred)
    {
        ZigType *usize = ira->codegen->builtin_types.entry_usize;
        Stage1AirInst *casted_elem_index = ir_implicit_cast(ira, elem_index, usize);
        if (type_is_invalid(casted_elem_index->value->type))
            return ira->codegen->invalid_inst_gen;
        src_assert(instr_is_comptime(casted_elem_index), elem_ptr_instruction->base.source_node);
        Buf *field_name = buf_alloc();
        bigint_append_buf(field_name, &casted_elem_index->value->data.x_bigint, 10);
        return ir_analyze_inferred_field_ptr(ira, field_name, elem_ptr_instruction->base.scope, elem_ptr_instruction->base.source_node,
                array_ptr, array_type);
    } else if (is_tuple(array_type)) {
        uint64_t elem_index_scalar;
        if (!ir_resolve_usize(ira, elem_index, &elem_index_scalar))
            return ira->codegen->invalid_inst_gen;
        if (elem_index_scalar >= array_type->data.structure.src_field_count) {
            ir_add_error_node(ira, elem_ptr_instruction->base.source_node, buf_sprintf(
                "field index %" ZIG_PRI_u64 " outside tuple '%s' which has %" PRIu32 " fields",
                elem_index_scalar, buf_ptr(&array_type->name),
                array_type->data.structure.src_field_count));
            return ira->codegen->invalid_inst_gen;
        }
        TypeStructField *field = array_type->data.structure.fields[elem_index_scalar];
        return ir_analyze_struct_field_ptr(ira, elem_ptr_instruction->base.scope, elem_ptr_instruction->base.source_node, field, array_ptr,
                array_type, false);
    } 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_inst_gen;
    }

    ZigType *usize = ira->codegen->builtin_types.entry_usize;
    Stage1AirInst *casted_elem_index = ir_implicit_cast(ira, elem_index, usize);
    if (type_is_invalid(casted_elem_index->value->type))
        return ira->codegen->invalid_inst_gen;

    bool safety_check_on = elem_ptr_instruction->safety_check_on;
    if (instr_is_comptime(casted_elem_index)) {
        ZigValue *index_val = ir_resolve_const(ira, casted_elem_index, UndefBad);
        if (index_val == nullptr)
            return ira->codegen->invalid_inst_gen;
        uint64_t index = bigint_as_u64(&index_val->data.x_bigint);

        if (array_type->id == ZigTypeIdArray) {
            uint64_t array_len = array_type->data.array.len +
                (array_type->data.array.sentinel != nullptr);
            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_inst_gen;
            }
            safety_check_on = false;
        } else if (array_type->id == ZigTypeIdVector) {
            uint64_t vector_len = array_type->data.vector.len;
            if (index >= vector_len) {
                ir_add_error_node(ira, elem_ptr_instruction->base.source_node,
                    buf_sprintf("index %" ZIG_PRI_u64 " outside vector of size %" ZIG_PRI_u64,
                            index, vector_len));
                return ira->codegen->invalid_inst_gen;
            }
            safety_check_on = false;
        }

        if (array_type->id == ZigTypeIdVector) {
            ZigType *elem_type = array_type->data.vector.elem_type;
            uint32_t host_vec_len = array_type->data.vector.len;
            return_type = get_pointer_to_type_extra2(ira->codegen, elem_type,
                ptr_type->data.pointer.is_const, ptr_type->data.pointer.is_volatile,
                elem_ptr_instruction->ptr_len,
                get_ptr_align(ira->codegen, ptr_type), 0, host_vec_len, false, (uint32_t)index,
                nullptr, nullptr);
        } else if (return_type->data.pointer.explicit_alignment != 0) {
            uint32_t chosen_align;
            if ((err = compute_elem_align(ira, return_type->data.pointer.child_type,
                return_type->data.pointer.explicit_alignment, index, &chosen_align)))
            {
                return ira->codegen->invalid_inst_gen;
            }
            return_type = adjust_ptr_align(ira->codegen, return_type, chosen_align);
        }

        // TODO The `array_type->id == ZigTypeIdArray` exception here should not be an exception;
        // the `orig_array_ptr_val->data.x_ptr.mut != ConstPtrMutRuntimeVar` clause should be omitted completely.
        // However there are bugs to fix before this improvement can be made.
        if (orig_array_ptr_val->special != ConstValSpecialRuntime &&
            orig_array_ptr_val->data.x_ptr.special != ConstPtrSpecialHardCodedAddr &&
            (orig_array_ptr_val->data.x_ptr.mut != ConstPtrMutRuntimeVar || array_type->id == ZigTypeIdArray))
        {
            if ((err = ir_resolve_const_val(ira->codegen, ira->new_irb.exec,
                elem_ptr_instruction->base.source_node, orig_array_ptr_val, UndefBad)))
            {
                return ira->codegen->invalid_inst_gen;
            }

            ZigValue *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_inst_gen;

            if (array_ptr_val->special == ConstValSpecialUndef &&
                elem_ptr_instruction->init_array_type_source_node != nullptr)
            {
                if (array_type->id == ZigTypeIdArray || array_type->id == ZigTypeIdVector) {
                    array_ptr_val->data.x_array.special = ConstArraySpecialNone;
                    array_ptr_val->data.x_array.data.s_none.elements = ira->codegen->pass1_arena->allocate<ZigValue>(array_type->data.array.len);
                    array_ptr_val->special = ConstValSpecialStatic;
                    for (size_t i = 0; i < array_type->data.array.len; i += 1) {
                        ZigValue *elem_val = &array_ptr_val->data.x_array.data.s_none.elements[i];
                        elem_val->special = ConstValSpecialUndef;
                        elem_val->type = array_type->data.array.child_type;
                        elem_val->parent.id = ConstParentIdArray;
                        elem_val->parent.data.p_array.array_val = array_ptr_val;
                        elem_val->parent.data.p_array.elem_index = i;
                    }
                } else if (is_slice(array_type)) {
                    src_assert(array_ptr->value->type->id == ZigTypeIdPointer, elem_ptr_instruction->base.source_node);
                    ZigType *actual_array_type = array_ptr->value->type->data.pointer.child_type;

                    if (type_is_invalid(actual_array_type))
                        return ira->codegen->invalid_inst_gen;
                    if (actual_array_type->id != ZigTypeIdArray) {
                        ir_add_error_node(ira, elem_ptr_instruction->init_array_type_source_node,
                            buf_sprintf("array literal requires address-of operator to coerce to slice type '%s'",
                                buf_ptr(&actual_array_type->name)));
                        return ira->codegen->invalid_inst_gen;
                    }

                    ZigValue *array_init_val = ira->codegen->pass1_arena->create<ZigValue>();
                    array_init_val->special = ConstValSpecialStatic;
                    array_init_val->type = actual_array_type;
                    array_init_val->data.x_array.special = ConstArraySpecialNone;
                    array_init_val->data.x_array.data.s_none.elements = ira->codegen->pass1_arena->allocate<ZigValue>(actual_array_type->data.array.len);
                    array_init_val->special = ConstValSpecialStatic;
                    for (size_t i = 0; i < actual_array_type->data.array.len; i += 1) {
                        ZigValue *elem_val = &array_init_val->data.x_array.data.s_none.elements[i];
                        elem_val->special = ConstValSpecialUndef;
                        elem_val->type = actual_array_type->data.array.child_type;
                        elem_val->parent.id = ConstParentIdArray;
                        elem_val->parent.data.p_array.array_val = array_init_val;
                        elem_val->parent.data.p_array.elem_index = i;
                    }

                    init_const_slice(ira->codegen, array_ptr_val, array_init_val, 0, actual_array_type->data.array.len,
                            false, nullptr);
                    array_ptr_val->data.x_struct.fields[slice_ptr_index]->data.x_ptr.mut = ConstPtrMutInfer;
                } else {
                    ir_add_error_node(ira, elem_ptr_instruction->init_array_type_source_node,
                        buf_sprintf("expected array type or [_], found '%s'",
                            buf_ptr(&array_type->name)));
                    return ira->codegen->invalid_inst_gen;
                }
            }

            if (array_ptr_val->special != ConstValSpecialRuntime &&
                (array_type->id != ZigTypeIdPointer ||
                    array_ptr_val->data.x_ptr.special != ConstPtrSpecialHardCodedAddr))
            {
                if ((err = ir_resolve_const_val(ira->codegen, ira->new_irb.exec,
                    elem_ptr_instruction->base.source_node, array_ptr_val, UndefOk)))
                {
                    return ira->codegen->invalid_inst_gen;
                }
                if (array_type->id == ZigTypeIdPointer) {
                    Stage1AirInst *result = ir_const(ira, elem_ptr_instruction->base.scope, elem_ptr_instruction->base.source_node, return_type);
                    ZigValue *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:
                            if (array_ptr_val->data.x_ptr.data.ref.pointee->type->id == ZigTypeIdArray) {
                                ZigValue *array_val = array_ptr_val->data.x_ptr.data.ref.pointee;
                                new_index = index;
                                ZigType *array_type = array_val->type;
                                mem_size = array_type->data.array.len;
                                if (array_type->data.array.sentinel != nullptr) {
                                    mem_size += 1;
                                }
                                old_size = mem_size;

                                out_val->data.x_ptr.special = ConstPtrSpecialBaseArray;
                                out_val->data.x_ptr.data.base_array.array_val = array_val;
                                out_val->data.x_ptr.data.base_array.elem_index = new_index;
                            } else {
                                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:
                        case ConstPtrSpecialSubArray:
                            {
                                size_t offset = array_ptr_val->data.x_ptr.data.base_array.elem_index;
                                new_index = offset + index;
                                ZigType *array_type = array_ptr_val->data.x_ptr.data.base_array.array_val->type;
                                mem_size = array_type->data.array.len;
                                if (array_type->data.array.sentinel != nullptr) {
                                    mem_size += 1;
                                }
                                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;

                                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_inst_gen;
                    }
                    return result;
                } else if (is_slice(array_type)) {
                    expand_undef_struct(ira->codegen, array_ptr_val);

                    ZigValue *ptr_field = array_ptr_val->data.x_struct.fields[slice_ptr_index];
                    src_assert(ptr_field != nullptr, elem_ptr_instruction->base.source_node);
                    if (ptr_field->data.x_ptr.special == ConstPtrSpecialHardCodedAddr) {
                        return ir_build_elem_ptr_gen(ira, elem_ptr_instruction->base.scope,
                                elem_ptr_instruction->base.source_node, array_ptr, casted_elem_index, false,
                                return_type);
                    }
                    ZigValue *len_field = array_ptr_val->data.x_struct.fields[slice_len_index];
                    Stage1AirInst *result = ir_const(ira, elem_ptr_instruction->base.scope,
                            elem_ptr_instruction->base.source_node, return_type);
                    ZigValue *out_val = result->value;
                    ZigType *slice_ptr_type = array_type->data.structure.fields[slice_ptr_index]->type_entry;
                    uint64_t slice_len = bigint_as_u64(&len_field->data.x_bigint);
                    uint64_t full_slice_len = slice_len +
                        ((slice_ptr_type->data.pointer.sentinel != nullptr) ? 1 : 0);
                    if (index >= full_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_inst_gen;
                    }
                    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 ConstPtrSpecialSubArray:
                        case ConstPtrSpecialBaseArray:
                            {
                                uint64_t array_len = ptr_field->data.x_ptr.data.base_array.array_val->type->data.array.len;
                                if (ptr_field->data.x_ptr.data.base_array.array_val->type->data.array.sentinel != nullptr) {
                                    array_len += 1;
                                }
                                size_t offset = ptr_field->data.x_ptr.data.base_array.elem_index;
                                uint64_t new_index = offset + index;
                                if (ptr_field->data.x_ptr.data.base_array.array_val->data.x_array.special !=
                                        ConstArraySpecialBuf)
                                {
                                    if (new_index >= array_len) {
                                        ir_add_error_node(ira, elem_ptr_instruction->base.source_node, buf_sprintf("out of bounds slice"));
                                        return ira->codegen->invalid_inst_gen;
                                    }
                                }
                                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;
                                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 || array_type->id == ZigTypeIdVector) {
                    expand_undef_array(ira->codegen, array_ptr_val);

                    Stage1AirInst *result;
                    if (orig_array_ptr_val->data.x_ptr.mut == ConstPtrMutInfer) {
                        result = ir_build_elem_ptr_gen(ira, elem_ptr_instruction->base.scope,
                                elem_ptr_instruction->base.source_node, array_ptr, casted_elem_index,
                                false, return_type);
                        result->value->special = ConstValSpecialStatic;
                    } else {
                        result = ir_const(ira, elem_ptr_instruction->base.scope, elem_ptr_instruction->base.source_node, return_type);
                    }
                    ZigValue *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 if (array_type->id == ZigTypeIdVector) {
        // runtime known element index
        ZigType *elem_type = array_type->data.vector.elem_type;
        uint32_t host_vec_len = array_type->data.vector.len;
        return_type = get_pointer_to_type_extra2(ira->codegen, elem_type,
            ptr_type->data.pointer.is_const, ptr_type->data.pointer.is_volatile,
            elem_ptr_instruction->ptr_len,
            get_ptr_align(ira->codegen, ptr_type), 0, host_vec_len, false, VECTOR_INDEX_RUNTIME,
            nullptr, nullptr);
    } 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_inst_gen;
        case ReqCompTimeInvalid:
            return ira->codegen->invalid_inst_gen;
        case ReqCompTimeNo:
            break;
        }

        if (return_type->data.pointer.explicit_alignment != 0) {
            if ((err = type_resolve(ira->codegen, return_type->data.pointer.child_type, ResolveStatusSizeKnown)))
                return ira->codegen->invalid_inst_gen;

            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 (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);
            }
        }
    }

    return ir_build_elem_ptr_gen(ira, elem_ptr_instruction->base.scope,
            elem_ptr_instruction->base.source_node, array_ptr, casted_elem_index, safety_check_on, return_type);
}

static Stage1AirInst *ir_analyze_container_member_access_inner(IrAnalyze *ira,
    ZigType *bare_struct_type, Buf *field_name, Scope *scope, AstNode *source_node,
    Stage1AirInst *container_ptr, AstNode *container_ptr_src, ZigType *container_type)
{
    if (!is_slice(bare_struct_type)) {
        ScopeDecls *container_scope = get_container_scope(bare_struct_type);
        assert(container_scope != nullptr);
        auto tld = find_container_decl(ira->codegen, container_scope, field_name);
        if (tld) {
            if (tld->id == TldIdFn) {
                resolve_top_level_decl(ira->codegen, tld, source_node, false);
                if (tld->resolution == TldResolutionInvalid)
                    return ira->codegen->invalid_inst_gen;
                if (tld->resolution == TldResolutionResolving)
                    return ir_error_dependency_loop(ira, source_node);

                if (tld->visib_mod == VisibModPrivate &&
                    tld->import != get_scope_import(scope))
                {
                    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_inst_gen;
                }

                TldFn *tld_fn = (TldFn *)tld;
                ZigFn *fn_entry = tld_fn->fn_entry;
                assert(fn_entry != nullptr);

                if (type_is_invalid(fn_entry->type_entry))
                    return ira->codegen->invalid_inst_gen;

                Stage1AirInst *bound_fn_value = ir_const_bound_fn(ira, scope, source_node, fn_entry, container_ptr,
                        container_ptr_src);
                return ir_get_ref(ira, scope, source_node, bound_fn_value, true, false);
            } else if (tld->id == TldIdVar) {
                resolve_top_level_decl(ira->codegen, tld, source_node, false);
                if (tld->resolution == TldResolutionInvalid)
                    return ira->codegen->invalid_inst_gen;
                if (tld->resolution == TldResolutionResolving)
                    return ir_error_dependency_loop(ira, source_node);

                TldVar *tld_var = (TldVar *)tld;
                ZigVar *var = tld_var->var;
                assert(var != nullptr);

                if (type_is_invalid(var->var_type))
                    return ira->codegen->invalid_inst_gen;

                if (var->const_value->type->id == ZigTypeIdFn) {
                    src_assert(var->const_value->data.x_ptr.special == ConstPtrSpecialFunction, source_node);
                    ZigFn *fn = var->const_value->data.x_ptr.data.fn.fn_entry;
                    Stage1AirInst *bound_fn_value = ir_const_bound_fn(ira, scope, source_node, fn, container_ptr,
                            container_ptr_src);
                    return ir_get_ref(ira, scope, source_node, 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 if (bare_struct_type->id == ZigTypeIdOpaque) {
        prefix_name = "opaque type ";
    } else {
        prefix_name = "";
    }
    ir_add_error_node(ira, 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_inst_gen;
}

static void memoize_field_init_val(CodeGen *codegen, ZigType *container_type, TypeStructField *field) {
    if (field->init_val != nullptr) return;
    if (field->decl_node == nullptr) return;
    if (field->decl_node->type != NodeTypeStructField) return;
    AstNode *init_node = field->decl_node->data.struct_field.value;
    if (init_node == nullptr) return;
    // scope is not the scope of the struct init, it's the scope of the struct type decl
    Scope *analyze_scope = &get_container_scope(container_type)->base;
    // memoize it
    field->init_val = analyze_const_value(codegen, analyze_scope, init_node,
            field->type_entry, nullptr, UndefOk);
}

static Stage1AirInst *ir_analyze_struct_field_ptr(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        TypeStructField *field, Stage1AirInst *struct_ptr, ZigType *struct_type, bool initializing)
{
    Error err;
    ZigType *field_type = resolve_struct_field_type(ira->codegen, field);
    if (field_type == nullptr)
        return ira->codegen->invalid_inst_gen;
    if (field->is_comptime) {
        Stage1AirInst *elem = ir_const(ira, scope, source_node, field_type);
        memoize_field_init_val(ira->codegen, struct_type, field);
        if(field->init_val != nullptr && type_is_invalid(field->init_val->type)){
            return ira->codegen->invalid_inst_gen;
        }
        copy_const_val(ira->codegen, elem->value, field->init_val);
        return ir_get_ref2(ira, scope, source_node, elem, field_type, true, false);
    }
    switch (type_has_one_possible_value(ira->codegen, field_type)) {
        case OnePossibleValueInvalid:
            return ira->codegen->invalid_inst_gen;
        case OnePossibleValueYes: {
            Stage1AirInst *elem = ir_const_move(ira, scope, source_node,
                 get_the_one_possible_value(ira->codegen, field_type));
            return ir_get_ref(ira, scope, source_node, elem,
                struct_ptr->value->type->data.pointer.is_const,
                struct_ptr->value->type->data.pointer.is_volatile);
        }
        case OnePossibleValueNo:
            break;
    }
    bool is_const = struct_ptr->value->type->data.pointer.is_const;
    bool is_volatile = struct_ptr->value->type->data.pointer.is_volatile;
    ZigType *ptr_type;
    if (is_anon_container(struct_type)) {
        ptr_type = get_pointer_to_type_extra(ira->codegen, field_type,
                is_const, is_volatile, PtrLenSingle, 0, 0, 0, false);
    } else {
        ResolveStatus needed_resolve_status =
            (struct_type->data.structure.layout == ContainerLayoutAuto) ?
                ResolveStatusZeroBitsKnown : ResolveStatusSizeKnown;
        if ((err = type_resolve(ira->codegen, struct_type, needed_resolve_status)))
            return ira->codegen->invalid_inst_gen;
        assert(struct_ptr->value->type->id == ZigTypeIdPointer);
        uint32_t ptr_bit_offset = struct_ptr->value->type->data.pointer.bit_offset_in_host;
        uint32_t ptr_host_int_bytes = struct_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, struct_type, field) : ptr_host_int_bytes;
        ptr_type = get_pointer_to_type_extra(ira->codegen, field_type,
                is_const, is_volatile, PtrLenSingle, field->align,
                (uint32_t)(ptr_bit_offset + field->bit_offset_in_host),
                (uint32_t)host_int_bytes_for_result_type, false);
    }
    if (instr_is_comptime(struct_ptr)) {
        ZigValue *ptr_val = ir_resolve_const(ira, struct_ptr, UndefBad);
        if (!ptr_val)
            return ira->codegen->invalid_inst_gen;

        if (ptr_val->data.x_ptr.special != ConstPtrSpecialHardCodedAddr) {
            ZigValue *struct_val = const_ptr_pointee(ira, ira->codegen, ptr_val, source_node);
            if (struct_val == nullptr)
                return ira->codegen->invalid_inst_gen;
            if (type_is_invalid(struct_val->type))
                return ira->codegen->invalid_inst_gen;

            // This to allow lazy values to be resolved.
            if ((err = ir_resolve_const_val(ira->codegen, ira->new_irb.exec,
                source_node, struct_val, UndefOk)))
            {
                return ira->codegen->invalid_inst_gen;
            }
            if (initializing && struct_val->special == ConstValSpecialUndef) {
                struct_val->data.x_struct.fields = alloc_const_vals_ptrs(ira->codegen, struct_type->data.structure.src_field_count);
                struct_val->special = ConstValSpecialStatic;
                for (size_t i = 0; i < struct_type->data.structure.src_field_count; i += 1) {
                    if (struct_type->data.structure.fields[i]->is_comptime)
                        continue;
                    ZigValue *field_val = struct_val->data.x_struct.fields[i];
                    field_val->special = ConstValSpecialUndef;
                    field_val->type = resolve_struct_field_type(ira->codegen,
                            struct_type->data.structure.fields[i]);
                    field_val->parent.id = ConstParentIdStruct;
                    field_val->parent.data.p_struct.struct_val = struct_val;
                    field_val->parent.data.p_struct.field_index = i;
                }
            }
            Stage1AirInst *result;
            if (ptr_val->data.x_ptr.mut == ConstPtrMutInfer) {
                result = ir_build_struct_field_ptr(ira, scope, source_node, struct_ptr, field, ptr_type);
                result->value->special = ConstValSpecialStatic;
            } else {
                result = ir_const(ira, scope, source_node, ptr_type);
            }
            ZigValue *const_val = result->value;
            const_val->data.x_ptr.special = ConstPtrSpecialBaseStruct;
            const_val->data.x_ptr.mut = ptr_val->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;
        }
    }
    return ir_build_struct_field_ptr(ira, scope, source_node, struct_ptr, field, ptr_type);
}

static Stage1AirInst *ir_analyze_inferred_field_ptr(IrAnalyze *ira, Buf *field_name,
    Scope *scope, AstNode *source_node, Stage1AirInst *container_ptr, ZigType *container_type)
{
    // The type of the field is not available until a store using this pointer happens.
    // So, here we create a special pointer type which has the inferred struct type and
    // field name encoded in the type. Later, when there is a store via this pointer,
    // the field type will then be available, and the field will be added to the inferred
    // struct.

    ZigType *container_ptr_type = container_ptr->value->type;
    src_assert(container_ptr_type->id == ZigTypeIdPointer, source_node);

    InferredStructField *inferred_struct_field = heap::c_allocator.create<InferredStructField>();
    inferred_struct_field->inferred_struct_type = container_type;
    inferred_struct_field->field_name = field_name;

    ZigType *elem_type = ira->codegen->builtin_types.entry_anytype;
    ZigType *field_ptr_type = get_pointer_to_type_extra2(ira->codegen, elem_type,
        container_ptr_type->data.pointer.is_const, container_ptr_type->data.pointer.is_volatile,
        PtrLenSingle, 0, 0, 0, false, VECTOR_INDEX_NONE, inferred_struct_field, nullptr);

    if (instr_is_comptime(container_ptr)) {
        ZigValue *ptr_val = ir_resolve_const(ira, container_ptr, UndefBad);
        if (ptr_val == nullptr)
            return ira->codegen->invalid_inst_gen;

        Stage1AirInst *result;
        if (ptr_val->data.x_ptr.mut == ConstPtrMutInfer) {
            result = ir_build_cast(ira, scope, source_node, container_ptr_type, container_ptr, CastOpNoop);
        } else {
            result = ir_const(ira, scope, source_node, field_ptr_type);
        }
        copy_const_val(ira->codegen, result->value, ptr_val);
        result->value->type = field_ptr_type;
        return result;
    }

    return ir_build_cast(ira, scope, source_node, field_ptr_type, container_ptr, CastOpNoop);
}

static Stage1AirInst *ir_analyze_container_field_ptr(IrAnalyze *ira, Buf *field_name,
    Scope *scope, AstNode *source_node, Stage1AirInst *container_ptr, AstNode *container_ptr_src,
    ZigType *container_type, bool initializing)
{
    Error err;

    ZigType *bare_type = container_ref_type(container_type);

    if (initializing && bare_type->id == ZigTypeIdStruct &&
        bare_type->data.structure.resolve_status == ResolveStatusBeingInferred)
    {
        return ir_analyze_inferred_field_ptr(ira, field_name, scope, source_node, container_ptr, bare_type);
    }

    // Tracks whether we should return an undefined value of the correct type.
    // We do this if the container pointer is undefined and we are in a TypeOf call.
    bool return_undef = container_ptr->value->special == ConstValSpecialUndef && \
                         get_scope_typeof(scope) != nullptr;

    if ((err = type_resolve(ira->codegen, bare_type, ResolveStatusZeroBitsKnown)))
        return ira->codegen->invalid_inst_gen;

    assert(container_ptr->value->type->id == ZigTypeIdPointer);
    if (bare_type->id == ZigTypeIdStruct) {
        TypeStructField *field = find_struct_type_field(bare_type, field_name);
        if (field != nullptr) {
            if (return_undef) {
                ZigType *field_ptr_type = get_pointer_to_type(ira->codegen, resolve_struct_field_type(ira->codegen, field),
                                                              container_ptr->value->type->data.pointer.is_const);
                return ir_const_undef(ira, scope, source_node, field_ptr_type);
            }

            return ir_analyze_struct_field_ptr(ira, scope, source_node, field, container_ptr, bare_type, initializing);
        } else {
            return ir_analyze_container_member_access_inner(ira, bare_type, field_name,
                scope, source_node, container_ptr, container_ptr_src, container_type);
        }
    }

    if (bare_type->id == ZigTypeIdEnum || bare_type->id == ZigTypeIdOpaque) {
        return ir_analyze_container_member_access_inner(ira, bare_type, field_name,
            scope, source_node, container_ptr, container_ptr_src, container_type);
    }

    if (bare_type->id == ZigTypeIdUnion) {
        bool is_const = container_ptr->value->type->data.pointer.is_const;
        bool is_volatile = container_ptr->value->type->data.pointer.is_volatile;

        TypeUnionField *field = find_union_type_field(bare_type, field_name);
        if (field == nullptr) {
            return ir_analyze_container_member_access_inner(ira, bare_type, field_name,
                scope, source_node, container_ptr, container_ptr_src, container_type);
        }

        ZigType *field_type = resolve_union_field_type(ira->codegen, field);
        if (field_type == nullptr)
            return ira->codegen->invalid_inst_gen;

        ZigType *ptr_type = get_pointer_to_type_extra(ira->codegen, field_type,
                is_const, is_volatile, PtrLenSingle, 0, 0, 0, false);
        if (instr_is_comptime(container_ptr)) {
            ZigValue *ptr_val = ir_resolve_const(ira, container_ptr, UndefBad);
            if (!ptr_val)
                return ira->codegen->invalid_inst_gen;

            if (ptr_val->data.x_ptr.mut != ConstPtrMutRuntimeVar &&
                ptr_val->data.x_ptr.special != ConstPtrSpecialHardCodedAddr) {
                ZigValue *union_val = const_ptr_pointee(ira, ira->codegen, ptr_val, source_node);
                if (union_val == nullptr)
                    return ira->codegen->invalid_inst_gen;
                if (type_is_invalid(union_val->type))
                    return ira->codegen->invalid_inst_gen;

                // Reject undefined values unless we're initializing the union:
                // a undefined union means also the tag is undefined, accessing
                // its payload slot is UB.
                const UndefAllowed allow_undef = initializing ? UndefOk : UndefBad;
                if ((err = ir_resolve_const_val(ira->codegen, ira->new_irb.exec,
                                source_node, union_val, allow_undef)))
                {
                    return ira->codegen->invalid_inst_gen;
                }

                if (initializing) {
                    ZigValue *payload_val = ira->codegen->pass1_arena->create<ZigValue>();
                    payload_val->special = ConstValSpecialUndef;
                    payload_val->type = field_type;
                    payload_val->parent.id = ConstParentIdUnion;
                    payload_val->parent.data.p_union.union_val = union_val;

                    union_val->special = ConstValSpecialStatic;
                    bigint_init_bigint(&union_val->data.x_union.tag, &field->enum_field->value);
                    union_val->data.x_union.payload = payload_val;
                } else if (bare_type->data.unionation.layout != ContainerLayoutExtern) {
                    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_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_inst_gen;
                    }
                }

                ZigValue *payload_val = union_val->data.x_union.payload;
                assert(payload_val);

                Stage1AirInst *result;
                if (ptr_val->data.x_ptr.mut == ConstPtrMutInfer) {
                    result = ir_build_union_field_ptr(ira, scope, source_node, container_ptr, field, true,
                            initializing, ptr_type);
                    result->value->special = ConstValSpecialStatic;
                } else {
                    result = ir_const(ira, scope, source_node, ptr_type);
                }
                ZigValue *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;
            }
        }

        return ir_build_union_field_ptr(ira, scope, source_node, container_ptr, field, true, initializing, ptr_type);
    }

    zig_unreachable();
}

static void add_link_lib_symbol(IrAnalyze *ira, Buf *lib_name, Buf *symbol_name, AstNode *source_node) {
    const char *msg = stage2_add_link_lib(&ira->codegen->stage1, buf_ptr(lib_name), buf_len(lib_name),
            buf_ptr(symbol_name), buf_len(symbol_name));
    if (msg != nullptr) {
        ir_add_error_node(ira, source_node, buf_create_from_str(msg));
        ira->codegen->reported_bad_link_libc_error = true;
    }
}

static Stage1AirInst *ir_error_dependency_loop(IrAnalyze *ira, AstNode* source_node) {
    ir_add_error_node(ira, source_node, buf_sprintf("dependency loop detected"));
    return ira->codegen->invalid_inst_gen;
}

static Stage1AirInst *ir_analyze_decl_ref(IrAnalyze *ira, Scope *scope, AstNode *source_node, Tld *tld) {
    resolve_top_level_decl(ira->codegen, tld, source_node, true);
    if (tld->resolution == TldResolutionInvalid) {
        return ira->codegen->invalid_inst_gen;
    }
    if (tld->resolution == TldResolutionResolving)
        return ir_error_dependency_loop(ira, source_node);

    switch (tld->id) {
        case TldIdContainer:
        case TldIdCompTime:
        case TldIdUsingNamespace:
            zig_unreachable();
        case TldIdVar: {
            TldVar *tld_var = (TldVar *)tld;
            ZigVar *var = tld_var->var;
            assert(var != nullptr);

            if (tld_var->extern_lib_name != nullptr) {
                add_link_lib_symbol(ira, tld_var->extern_lib_name, buf_create_from_str(var->name),
                        source_node);
            }

            return ir_get_var_ptr(ira, scope, source_node, var);
        }
        case TldIdFn: {
            TldFn *tld_fn = (TldFn *)tld;
            ZigFn *fn_entry = tld_fn->fn_entry;
            assert(fn_entry->type_entry != nullptr);

            if (type_is_invalid(fn_entry->type_entry))
                return ira->codegen->invalid_inst_gen;

            if (tld_fn->extern_lib_name != nullptr) {
                add_link_lib_symbol(ira, tld_fn->extern_lib_name, &fn_entry->symbol_name, source_node);
            }

            Stage1AirInst *fn_inst = ir_const_fn(ira, scope, source_node, fn_entry);
            return ir_get_ref(ira, scope, source_node, fn_inst, true, false);
        }
    }
    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 Stage1AirInst *ir_analyze_instruction_field_ptr(IrAnalyze *ira, Stage1ZirInstFieldPtr *field_ptr_instruction) {
    Error err;
    Stage1AirInst *container_ptr = field_ptr_instruction->container_ptr->child;
    if (type_is_invalid(container_ptr->value->type))
        return ira->codegen->invalid_inst_gen;

    ZigType *container_type = container_ptr->value->type->data.pointer.child_type;

    Buf *field_name = field_ptr_instruction->field_name_buffer;
    if (!field_name) {
        Stage1AirInst *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_inst_gen;
    }


    AstNode *source_node = field_ptr_instruction->base.source_node;

    if (type_is_invalid(container_type)) {
        return ira->codegen->invalid_inst_gen;
    } else if (is_tuple(container_type) && !field_ptr_instruction->initializing && buf_eql_str(field_name, "len")) {
        Stage1AirInst *len_inst = ir_const_unsigned(ira, field_ptr_instruction->base.scope, field_ptr_instruction->base.source_node,
                container_type->data.structure.src_field_count);
        return ir_get_ref(ira, field_ptr_instruction->base.scope, field_ptr_instruction->base.source_node, len_inst, true, false);
    } else if (is_slice(container_type) || 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);
            Stage1AirInst *container_child = ir_get_deref(ira, field_ptr_instruction->base.scope,
                    field_ptr_instruction->base.source_node, container_ptr, nullptr);
            Stage1AirInst *result = ir_analyze_container_field_ptr(ira, field_name,
                    field_ptr_instruction->base.scope, field_ptr_instruction->base.source_node,
                    container_child, field_ptr_instruction->container_ptr->source_node, bare_type,
                    field_ptr_instruction->initializing);
            return result;
        } else {
            Stage1AirInst *result = ir_analyze_container_field_ptr(ira, field_name,
                    field_ptr_instruction->base.scope, field_ptr_instruction->base.source_node,
                    container_ptr, field_ptr_instruction->container_ptr->source_node,
                    container_type, field_ptr_instruction->initializing);
            return result;
        }
    } else if (is_array_ref(container_type) && !field_ptr_instruction->initializing) {
        if (buf_eql_str(field_name, "len")) {
            ZigValue *len_val = ira->codegen->pass1_arena->create<ZigValue>();
            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.scope, field_ptr_instruction->base.source_node, len_val,
                    usize, ConstPtrMutComptimeConst, ptr_is_const, ptr_is_volatile, 0);
        } else {
            ir_add_error_node(ira, source_node,
                buf_sprintf("no field named '%s' in '%s'", buf_ptr(field_name),
                    buf_ptr(&container_type->name)));
            return ira->codegen->invalid_inst_gen;
        }
    } else if (container_type->id == ZigTypeIdMetaType) {
        ZigValue *container_ptr_val = ir_resolve_const(ira, container_ptr, UndefBad);
        if (!container_ptr_val)
            return ira->codegen->invalid_inst_gen;

        assert(container_ptr->value->type->id == ZigTypeIdPointer);
        ZigValue *child_val = const_ptr_pointee(ira, ira->codegen, container_ptr_val, source_node);
        if (child_val == nullptr)
            return ira->codegen->invalid_inst_gen;
        if ((err = ir_resolve_const_val(ira->codegen, ira->new_irb.exec,
            field_ptr_instruction->base.source_node, child_val, UndefBad)))
        {
            return ira->codegen->invalid_inst_gen;
        }
        ZigType *child_type = child_val->data.x_type;

        if (type_is_invalid(child_type)) {
            return ira->codegen->invalid_inst_gen;
        } else if (is_container(child_type)) {
            if (child_type->id == ZigTypeIdEnum) {
                if ((err = type_resolve(ira->codegen, child_type, ResolveStatusSizeKnown)))
                    return ira->codegen->invalid_inst_gen;

                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.scope, field_ptr_instruction->base.source_node,
                            create_const_enum(ira->codegen, child_type, &field->value), child_type,
                            ConstPtrMutComptimeConst, ptr_is_const, ptr_is_volatile, 0);
                }
            }
            ScopeDecls *container_scope = get_container_scope(child_type);
            Tld *tld = find_container_decl(ira->codegen, container_scope, field_name);
            if (tld) {
                if (tld->visib_mod == VisibModPrivate &&
                    tld->import != get_scope_import(field_ptr_instruction->base.scope))
                {
                    ErrorMsg *msg = ir_add_error_node(ira, field_ptr_instruction->base.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_inst_gen;
                }
                return ir_analyze_decl_ref(ira, field_ptr_instruction->base.scope, field_ptr_instruction->base.source_node, 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 = type_resolve(ira->codegen, child_type, ResolveStatusSizeKnown)))
                    return ira->codegen->invalid_inst_gen;
                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.scope, field_ptr_instruction->base.source_node,
                            create_const_enum(ira->codegen, enum_type, &field->enum_field->value), enum_type,
                            ConstPtrMutComptimeConst, ptr_is_const, ptr_is_volatile, 0);
                }
            }
            const char *container_name = (child_type == ira->codegen->root_import) ?
                "root source file" : buf_ptr(buf_sprintf("container '%s'", buf_ptr(&child_type->name)));
            ir_add_error_node(ira, field_ptr_instruction->base.source_node,
                buf_sprintf("%s has no member called '%s'",
                    container_name, buf_ptr(field_name)));
            return ira->codegen->invalid_inst_gen;
        } 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 = heap::c_allocator.create<ErrorTableEntry>();
                    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->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_inst_gen;
                }
                err_entry = find_err_table_entry(child_type, field_name);
                if (err_entry == nullptr) {
                    ir_add_error_node(ira, field_ptr_instruction->base.source_node,
                        buf_sprintf("no error named '%s' in '%s'", buf_ptr(field_name), buf_ptr(&child_type->name)));
                    return ira->codegen->invalid_inst_gen;
                }
                err_set_type = child_type;
            }
            ZigValue *const_val = ira->codegen->pass1_arena->create<ZigValue>();
            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.scope,
                    field_ptr_instruction->base.source_node, const_val,
                    err_set_type, ConstPtrMutComptimeConst, ptr_is_const, ptr_is_volatile, 0);
        } 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_inst_gen;
        }
    } else if (field_ptr_instruction->initializing) {
        ir_add_error_node(ira, field_ptr_instruction->base.source_node,
            buf_sprintf("type '%s' does not support struct initialization syntax", buf_ptr(&container_type->name)));
        return ira->codegen->invalid_inst_gen;
    } 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_inst_gen;
    }
}

static Stage1AirInst *ir_analyze_instruction_store_ptr(IrAnalyze *ira, Stage1ZirInstStorePtr *instruction) {
    Stage1AirInst *ptr = instruction->ptr->child;
    if (type_is_invalid(ptr->value->type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *value = instruction->value->child;
    if (type_is_invalid(value->value->type))
        return ira->codegen->invalid_inst_gen;

    return ir_analyze_store_ptr(ira, instruction->base.scope, instruction->base.source_node, ptr, value, instruction->allow_write_through_const);
}

static Stage1AirInst *ir_analyze_instruction_load_ptr(IrAnalyze *ira, Stage1ZirInstLoadPtr *instruction) {
    Stage1AirInst *ptr = instruction->ptr->child;
    if (type_is_invalid(ptr->value->type))
        return ira->codegen->invalid_inst_gen;
    return ir_get_deref(ira, instruction->base.scope, instruction->base.source_node, ptr, nullptr);
}

static Stage1AirInst *ir_analyze_instruction_typeof(IrAnalyze *ira, Stage1ZirInstTypeOf *typeof_instruction) {
    ZigType *type_entry;

    const size_t value_count = typeof_instruction->value_count;

    // Fast path for the common case of TypeOf with a single argument
    if (value_count < 2) {
        type_entry = typeof_instruction->value.scalar->child->value->type;
    } else {
        Stage1AirInst **args = heap::c_allocator.allocate<Stage1AirInst*>(value_count);
        for (size_t i = 0; i < value_count; i += 1) {
            Stage1AirInst *value = typeof_instruction->value.list[i]->child;
            if (type_is_invalid(value->value->type))
                return ira->codegen->invalid_inst_gen;
            args[i] = value;
        }

        type_entry = ir_resolve_peer_types(ira, typeof_instruction->base.source_node,
            nullptr, args, value_count);

        heap::c_allocator.deallocate(args, value_count);
    }

    if (type_is_invalid(type_entry))
        return ira->codegen->invalid_inst_gen;

    return ir_const_type(ira, typeof_instruction->base.scope, typeof_instruction->base.source_node, type_entry);
}

static Stage1AirInst *ir_analyze_instruction_set_cold(IrAnalyze *ira, Stage1ZirInstSetCold *instruction) {
    if (ira->new_irb.exec->is_inline) {
        // ignore setCold when running functions at compile time
        return ir_const_void(ira, instruction->base.scope, instruction->base.source_node);
    }

    Stage1AirInst *is_cold_value = instruction->is_cold->child;
    bool want_cold;
    if (!ir_resolve_bool(ira, is_cold_value, &want_cold))
        return ira->codegen->invalid_inst_gen;

    ZigFn *fn_entry = scope_fn_entry(instruction->base.scope);
    if (fn_entry == nullptr) {
        ir_add_error_node(ira, instruction->base.source_node, buf_sprintf("@setCold outside function"));
        return ira->codegen->invalid_inst_gen;
    }

    if (fn_entry->set_cold_node != nullptr) {
        ErrorMsg *msg = ir_add_error_node(ira, instruction->base.source_node, 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_inst_gen;
    }

    fn_entry->set_cold_node = instruction->base.source_node;
    fn_entry->is_cold = want_cold;

    return ir_const_void(ira, instruction->base.scope, instruction->base.source_node);
}

static Stage1AirInst *ir_analyze_instruction_set_runtime_safety(IrAnalyze *ira,
        Stage1ZirInstSetRuntimeSafety *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.scope,
                set_runtime_safety_instruction->base.source_node);
    }

    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);

    Stage1AirInst *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_inst_gen;

    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_inst_gen;
    }
    *safety_set_node_ptr = source_node;
    *safety_off_ptr = !want_runtime_safety;

    return ir_const_void(ira, set_runtime_safety_instruction->base.scope,
            set_runtime_safety_instruction->base.source_node);
}

static Stage1AirInst *ir_analyze_instruction_set_float_mode(IrAnalyze *ira,
        Stage1ZirInstSetFloatMode *instruction)
{
    if (ira->new_irb.exec->is_inline) {
        // ignore setFloatMode when running functions at compile time
        return ir_const_void(ira, instruction->base.scope, instruction->base.source_node);
    }

    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);

    Stage1AirInst *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_inst_gen;

    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_inst_gen;
    }
    *fast_math_set_node_ptr = source_node;
    *fast_math_on_ptr = (float_mode_scalar == FloatModeOptimized);

    return ir_const_void(ira, instruction->base.scope, instruction->base.source_node);
}

static Stage1AirInst *ir_analyze_instruction_any_frame_type(IrAnalyze *ira, Stage1ZirInstAnyFrameType *instruction) {
    ZigType *payload_type = nullptr;
    if (instruction->payload_type != nullptr) {
        payload_type = ir_resolve_type(ira, instruction->payload_type->child);
        if (type_is_invalid(payload_type))
            return ira->codegen->invalid_inst_gen;
    }

    ZigType *any_frame_type = get_any_frame_type(ira->codegen, payload_type);
    return ir_const_type(ira, instruction->base.scope, instruction->base.source_node, any_frame_type);
}

static Stage1AirInst *ir_analyze_instruction_slice_type(IrAnalyze *ira, Stage1ZirInstSliceType *slice_type_instruction) {
    Stage1AirInst *result = ir_const(ira, slice_type_instruction->base.scope,
            slice_type_instruction->base.source_node, ira->codegen->builtin_types.entry_type);
    result->value->special = ConstValSpecialLazy;

    LazyValueSliceType *lazy_slice_type = heap::c_allocator.create<LazyValueSliceType>();
    lazy_slice_type->ira = ira; ira_ref(ira);
    result->value->data.x_lazy = &lazy_slice_type->base;
    lazy_slice_type->base.id = LazyValueIdSliceType;

    if (slice_type_instruction->align_value != nullptr) {
        lazy_slice_type->align_inst = slice_type_instruction->align_value->child;
        if (ir_resolve_const(ira, lazy_slice_type->align_inst, LazyOk) == nullptr)
            return ira->codegen->invalid_inst_gen;
    }

    if (slice_type_instruction->sentinel != nullptr) {
        lazy_slice_type->sentinel = slice_type_instruction->sentinel->child;
        if (ir_resolve_const(ira, lazy_slice_type->sentinel, LazyOk) == nullptr)
            return ira->codegen->invalid_inst_gen;
    }

    lazy_slice_type->elem_type = slice_type_instruction->child_type->child;
    if (ir_resolve_type_lazy(ira, lazy_slice_type->elem_type) == nullptr)
        return ira->codegen->invalid_inst_gen;

    lazy_slice_type->is_const = slice_type_instruction->is_const;
    lazy_slice_type->is_volatile = slice_type_instruction->is_volatile;
    lazy_slice_type->is_allowzero = slice_type_instruction->is_allow_zero;

    return result;
}

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

static Stage1AirInst *ir_analyze_instruction_asm(IrAnalyze *ira, Stage1ZirInstAsm *asm_instruction) {
    Error err;

    assert(asm_instruction->base.source_node->type == NodeTypeAsmExpr);

    AstNode *node = asm_instruction->base.source_node;
    AstNodeAsmExpr *asm_expr = &asm_instruction->base.source_node->data.asm_expr;

    Buf *template_buf = ir_resolve_str(ira, asm_instruction->asm_template->child);
    if (template_buf == nullptr)
        return ira->codegen->invalid_inst_gen;

    if (asm_instruction->is_global) {
        buf_append_char(&ira->codegen->global_asm, '\n');
        buf_append_buf(&ira->codegen->global_asm, template_buf);

        return ir_const_void(ira, asm_instruction->base.scope, asm_instruction->base.source_node);
    }

    if (!ir_emit_global_runtime_side_effect(ira, &asm_instruction->base))
        return ira->codegen->invalid_inst_gen;

    ZigList<AsmToken> tok_list = {};
    if ((err = parse_asm_template(ira, node, template_buf, &tok_list))) {
        return ira->codegen->invalid_inst_gen;
    }

    for (size_t token_i = 0; token_i < tok_list.length; token_i += 1) {
        AsmToken asm_token = tok_list.at(token_i);
        if (asm_token.id == AsmTokenIdVar) {
            size_t index = find_asm_index(ira->codegen, node, &asm_token, template_buf);
            if (index == SIZE_MAX) {
                const char *ptr = buf_ptr(template_buf) + asm_token.start + 2;
                uint32_t len = asm_token.end - asm_token.start - 2;

                add_node_error(ira->codegen, node,
                    buf_sprintf("could not find '%.*s' in the inputs or outputs",
                        len, ptr));
                return ira->codegen->invalid_inst_gen;
            }
        }
    }

    // TODO validate the output types and variable types

    Stage1AirInst **input_list = heap::c_allocator.allocate<Stage1AirInst *>(asm_expr->input_list.length);
    Stage1AirInst **output_types = heap::c_allocator.allocate<Stage1AirInst *>(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_inst_gen;
        }
    }

    for (size_t i = 0; i < asm_expr->input_list.length; i += 1) {
        Stage1AirInst *const input_value = asm_instruction->input_list[i]->child;
        if (type_is_invalid(input_value->value->type))
            return ira->codegen->invalid_inst_gen;

        if (instr_is_comptime(input_value) &&
            (input_value->value->type->id == ZigTypeIdComptimeInt ||
            input_value->value->type->id == ZigTypeIdComptimeFloat)) {
            ir_add_error(ira, input_value,
                buf_sprintf("expected sized integer or sized float, found %s", buf_ptr(&input_value->value->type->name)));
            return ira->codegen->invalid_inst_gen;
        }

        input_list[i] = input_value;
    }

    return ir_build_asm_gen(ira, asm_instruction->base.scope, asm_instruction->base.source_node,
        template_buf, tok_list.items, tok_list.length,
        input_list, output_types, asm_instruction->output_vars, asm_instruction->return_count,
        asm_instruction->has_side_effects, return_type);
}

static Stage1AirInst *ir_analyze_instruction_array_type(IrAnalyze *ira, Stage1ZirInstArrayType *array_type_instruction) {
    Stage1AirInst *result = ir_const(ira, array_type_instruction->base.scope,
            array_type_instruction->base.source_node, ira->codegen->builtin_types.entry_type);
    result->value->special = ConstValSpecialLazy;

    LazyValueArrayType *lazy_array_type = heap::c_allocator.create<LazyValueArrayType>();
    lazy_array_type->ira = ira; ira_ref(ira);
    result->value->data.x_lazy = &lazy_array_type->base;
    lazy_array_type->base.id = LazyValueIdArrayType;

    lazy_array_type->elem_type = array_type_instruction->child_type->child;
    if (ir_resolve_type_lazy(ira, lazy_array_type->elem_type) == nullptr)
        return ira->codegen->invalid_inst_gen;

    if (!ir_resolve_usize(ira, array_type_instruction->size->child, &lazy_array_type->length))
        return ira->codegen->invalid_inst_gen;

    if (array_type_instruction->sentinel != nullptr) {
        lazy_array_type->sentinel = array_type_instruction->sentinel->child;
        if (ir_resolve_const(ira, lazy_array_type->sentinel, LazyOk) == nullptr)
            return ira->codegen->invalid_inst_gen;
    }

    return result;
}

static Stage1AirInst *ir_analyze_instruction_size_of(IrAnalyze *ira, Stage1ZirInstSizeOf *instruction) {
    Stage1AirInst *result = ir_const(ira, instruction->base.scope, instruction->base.source_node, ira->codegen->builtin_types.entry_num_lit_int);
    result->value->special = ConstValSpecialLazy;

    LazyValueSizeOf *lazy_size_of = heap::c_allocator.create<LazyValueSizeOf>();
    lazy_size_of->ira = ira; ira_ref(ira);
    result->value->data.x_lazy = &lazy_size_of->base;
    lazy_size_of->base.id = LazyValueIdSizeOf;
    lazy_size_of->bit_size = instruction->bit_size;

    lazy_size_of->target_type = instruction->type_value->child;
    if (ir_resolve_type_lazy(ira, lazy_size_of->target_type) == nullptr)
        return ira->codegen->invalid_inst_gen;

    return result;
}

static Stage1AirInst *ir_analyze_test_non_null(IrAnalyze *ira, Scope *scope, AstNode *source_node, Stage1AirInst *value) {
    ZigType *type_entry = value->value->type;

    if (type_entry->id == ZigTypeIdPointer && type_entry->data.pointer.allow_zero) {
        if (instr_is_comptime(value)) {
            ZigValue *c_ptr_val = ir_resolve_const(ira, value, UndefOk);
            if (c_ptr_val == nullptr)
                return ira->codegen->invalid_inst_gen;
            if (c_ptr_val->special == ConstValSpecialUndef)
                return ir_const_undef(ira, scope, source_node, ira->codegen->builtin_types.entry_bool);
            bool is_null = c_ptr_val->data.x_ptr.special == ConstPtrSpecialNull ||
                (c_ptr_val->data.x_ptr.special == ConstPtrSpecialHardCodedAddr &&
                    c_ptr_val->data.x_ptr.data.hard_coded_addr.addr == 0);
            return ir_const_bool(ira, scope, source_node, !is_null);
        }

        return ir_build_test_non_null_gen(ira, scope, source_node, value);
    } else if (type_entry->id == ZigTypeIdOptional) {
        if (instr_is_comptime(value)) {
            ZigValue *maybe_val = ir_resolve_const(ira, value, UndefOk);
            if (maybe_val == nullptr)
                return ira->codegen->invalid_inst_gen;
            if (maybe_val->special == ConstValSpecialUndef)
                return ir_const_undef(ira, scope, source_node, ira->codegen->builtin_types.entry_bool);

            return ir_const_bool(ira, scope, source_node, !optional_value_is_null(maybe_val));
        }

        return ir_build_test_non_null_gen(ira, scope, source_node, value);
    } else if (type_entry->id == ZigTypeIdNull) {
        return ir_const_bool(ira, scope, source_node, false);
    } else {
        return ir_const_bool(ira, scope, source_node, true);
    }
}

static Stage1AirInst *ir_analyze_instruction_test_non_null(IrAnalyze *ira, Stage1ZirInstTestNonNull *instruction) {
    Stage1AirInst *value = instruction->value->child;
    if (type_is_invalid(value->value->type))
        return ira->codegen->invalid_inst_gen;

    return ir_analyze_test_non_null(ira, instruction->base.scope, instruction->base.source_node, value);
}

static Stage1AirInst *ir_analyze_unwrap_optional_payload(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        Stage1AirInst *base_ptr, bool safety_check_on, bool initializing)
{
    Error err;

    ZigType *type_entry = get_ptr_elem_type(ira->codegen, base_ptr);
    if (type_is_invalid(type_entry))
        return ira->codegen->invalid_inst_gen;

    if (type_entry->id == ZigTypeIdPointer && type_entry->data.pointer.ptr_len == PtrLenC) {
        if (instr_is_comptime(base_ptr)) {
            ZigValue *val = ir_resolve_const(ira, base_ptr, UndefBad);
            if (!val)
                return ira->codegen->invalid_inst_gen;
            if (val->data.x_ptr.mut != ConstPtrMutRuntimeVar) {
                ZigValue *c_ptr_val = const_ptr_pointee(ira, ira->codegen, val, source_node);
                if (c_ptr_val == nullptr)
                    return ira->codegen->invalid_inst_gen;
                bool is_null = c_ptr_val->data.x_ptr.special == ConstPtrSpecialNull ||
                    (c_ptr_val->data.x_ptr.special == ConstPtrSpecialHardCodedAddr &&
                        c_ptr_val->data.x_ptr.data.hard_coded_addr.addr == 0);
                if (is_null) {
                    ir_add_error_node(ira, source_node, buf_sprintf("unable to unwrap null"));
                    return ira->codegen->invalid_inst_gen;
                }
                return base_ptr;
            }
        }
        if (!safety_check_on)
            return base_ptr;
        Stage1AirInst *c_ptr_val = ir_get_deref(ira, scope, source_node, base_ptr, nullptr);
        ir_build_assert_non_null(ira, scope, source_node, c_ptr_val);
        return base_ptr;
    }

    if (type_entry->id != ZigTypeIdOptional) {
        ir_add_error(ira, base_ptr,
                buf_sprintf("expected optional type, found '%s'", buf_ptr(&type_entry->name)));
        return ira->codegen->invalid_inst_gen;
    }

    ZigType *child_type = type_entry->data.maybe.child_type;
    ZigType *result_type = get_pointer_to_type_extra(ira->codegen, child_type,
            base_ptr->value->type->data.pointer.is_const, base_ptr->value->type->data.pointer.is_volatile,
            PtrLenSingle, 0, 0, 0, false);

    bool same_comptime_repr = types_have_same_zig_comptime_repr(ira->codegen, child_type, type_entry);

    if (instr_is_comptime(base_ptr)) {
        ZigValue *ptr_val = ir_resolve_const(ira, base_ptr, UndefBad);
        if (ptr_val == nullptr)
            return ira->codegen->invalid_inst_gen;
        if (ptr_val->data.x_ptr.mut != ConstPtrMutRuntimeVar) {
            ZigValue *optional_val = const_ptr_pointee(ira, ira->codegen, ptr_val, source_node);
            if (optional_val == nullptr)
                return ira->codegen->invalid_inst_gen;

            if (initializing) {
                switch (type_has_one_possible_value(ira->codegen, child_type)) {
                    case OnePossibleValueInvalid:
                        return ira->codegen->invalid_inst_gen;
                    case OnePossibleValueNo:
                        if (!same_comptime_repr) {
                            ZigValue *payload_val = ira->codegen->pass1_arena->create<ZigValue>();
                            payload_val->type = child_type;
                            payload_val->special = ConstValSpecialUndef;
                            payload_val->parent.id = ConstParentIdOptionalPayload;
                            payload_val->parent.data.p_optional_payload.optional_val = optional_val;

                            optional_val->data.x_optional = payload_val;
                            optional_val->special = ConstValSpecialStatic;
                        }
                        break;
                    case OnePossibleValueYes: {
                        optional_val->special = ConstValSpecialStatic;
                        optional_val->data.x_optional = get_the_one_possible_value(ira->codegen, child_type);
                        break;
                    }
                }
            } else {
                if ((err = ir_resolve_const_val(ira->codegen, ira->new_irb.exec,
                            source_node, optional_val, UndefBad)))
                    return ira->codegen->invalid_inst_gen;
                if (optional_value_is_null(optional_val)) {
                    ir_add_error_node(ira, source_node, buf_sprintf("unable to unwrap null"));
                    return ira->codegen->invalid_inst_gen;
                }
            }

            Stage1AirInst *result;
            if (ptr_val->data.x_ptr.mut == ConstPtrMutInfer) {
                result = ir_build_optional_unwrap_ptr_gen(ira, scope, source_node, base_ptr, false,
                        initializing, result_type);
                result->value->special = ConstValSpecialStatic;
            } else {
                result = ir_const(ira, scope, source_node, result_type);
            }
            ZigValue *result_val = result->value;
            result_val->data.x_ptr.special = ConstPtrSpecialRef;
            result_val->data.x_ptr.mut = ptr_val->data.x_ptr.mut;
            switch (type_has_one_possible_value(ira->codegen, child_type)) {
                case OnePossibleValueInvalid:
                    return ira->codegen->invalid_inst_gen;
                case OnePossibleValueNo:
                    if (same_comptime_repr) {
                        result_val->data.x_ptr.data.ref.pointee = optional_val;
                    } else {
                        assert(optional_val->data.x_optional != nullptr);
                        result_val->data.x_ptr.data.ref.pointee = optional_val->data.x_optional;
                    }
                    break;
                case OnePossibleValueYes:
                    assert(optional_val->data.x_optional != nullptr);
                    result_val->data.x_ptr.data.ref.pointee = optional_val->data.x_optional;
                    break;
            }
            return result;
        }
    }

    return ir_build_optional_unwrap_ptr_gen(ira, scope, source_node, base_ptr, safety_check_on,
            initializing, result_type);
}

static Stage1AirInst *ir_analyze_instruction_optional_unwrap_ptr(IrAnalyze *ira,
        Stage1ZirInstOptionalUnwrapPtr *instruction)
{
    Stage1AirInst *base_ptr = instruction->base_ptr->child;
    if (type_is_invalid(base_ptr->value->type))
        return ira->codegen->invalid_inst_gen;

    return ir_analyze_unwrap_optional_payload(ira, instruction->base.scope, instruction->base.source_node, base_ptr,
            instruction->safety_check_on, false);
}

static Stage1AirInst *ir_analyze_instruction_ctz(IrAnalyze *ira, Stage1ZirInstCtz *instruction) {
    Error err;

    ZigType *int_type = ir_resolve_int_type(ira, instruction->type->child);
    if (type_is_invalid(int_type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *uncasted_op = instruction->op->child;
    if (type_is_invalid(uncasted_op->value->type))
        return ira->codegen->invalid_inst_gen;

    uint32_t vector_len = UINT32_MAX; // means not a vector
    if (uncasted_op->value->type->id == ZigTypeIdArray) {
        bool can_be_vec_elem;
        if ((err = is_valid_vector_elem_type(ira->codegen, uncasted_op->value->type->data.array.child_type,
                        &can_be_vec_elem)))
        {
            return ira->codegen->invalid_inst_gen;
        }
        if (can_be_vec_elem) {
            vector_len = uncasted_op->value->type->data.array.len;
        }
    } else if (uncasted_op->value->type->id == ZigTypeIdVector) {
        vector_len = uncasted_op->value->type->data.vector.len;
    }

    bool is_vector = (vector_len != UINT32_MAX);
    ZigType *op_type = is_vector ? get_vector_type(ira->codegen, vector_len, int_type) : int_type;

    Stage1AirInst *op = ir_implicit_cast(ira, uncasted_op, op_type);
    if (type_is_invalid(op->value->type))
        return ira->codegen->invalid_inst_gen;

    if (int_type->data.integral.bit_count == 0)
        return ir_const_unsigned(ira, instruction->base.scope, instruction->base.source_node, 0);

    ZigType *smallest_type = get_smallest_unsigned_int_type(ira->codegen, int_type->data.integral.bit_count);

    if (instr_is_comptime(op)) {
        ZigValue *val = ir_resolve_const(ira, op, UndefOk);
        if (val == nullptr)
            return ira->codegen->invalid_inst_gen;
        if (val->special == ConstValSpecialUndef)
            return ir_const_undef(ira, instruction->base.scope, instruction->base.source_node, ira->codegen->builtin_types.entry_num_lit_int);

        if (is_vector) {
            ZigType *smallest_vec_type = get_vector_type(ira->codegen, vector_len, smallest_type);
            Stage1AirInst *result = ir_const(ira, instruction->base.scope, instruction->base.source_node, smallest_vec_type);
            expand_undef_array(ira->codegen, val);
            result->value->data.x_array.data.s_none.elements = ira->codegen->pass1_arena->allocate<ZigValue>(smallest_vec_type->data.vector.len);
            for (unsigned i = 0; i < smallest_vec_type->data.vector.len; i += 1) {
                ZigValue *op_elem_val = &val->data.x_array.data.s_none.elements[i];
                if ((err = ir_resolve_const_val(ira->codegen, ira->new_irb.exec, instruction->base.source_node,
                    op_elem_val, UndefOk)))
                {
                    return ira->codegen->invalid_inst_gen;
                }
                ZigValue *result_elem_val = &result->value->data.x_array.data.s_none.elements[i];
                result_elem_val->type = smallest_type;
                result_elem_val->special = op_elem_val->special;
                if (op_elem_val->special == ConstValSpecialUndef)
                    continue;
                size_t value = bigint_ctz(&op_elem_val->data.x_bigint, int_type->data.integral.bit_count);
                bigint_init_unsigned(&result->value->data.x_array.data.s_none.elements[i].data.x_bigint, value);
            }
            return result;
        } else {
            size_t result_usize = bigint_ctz(&op->value->data.x_bigint, int_type->data.integral.bit_count);
            return ir_const_unsigned(ira, instruction->base.scope, instruction->base.source_node, result_usize);
        }
    }

    ZigType *return_type = is_vector ? get_vector_type(ira->codegen, vector_len, smallest_type) : smallest_type;
    return ir_build_ctz_gen(ira, instruction->base.scope, instruction->base.source_node, return_type, op);
}

static Stage1AirInst *ir_analyze_instruction_clz(IrAnalyze *ira, Stage1ZirInstClz *instruction) {
    Error err;

    ZigType *int_type = ir_resolve_int_type(ira, instruction->type->child);
    if (type_is_invalid(int_type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *uncasted_op = instruction->op->child;
    if (type_is_invalid(uncasted_op->value->type))
        return ira->codegen->invalid_inst_gen;

    uint32_t vector_len = UINT32_MAX; // means not a vector
    if (uncasted_op->value->type->id == ZigTypeIdArray) {
        bool can_be_vec_elem;
        if ((err = is_valid_vector_elem_type(ira->codegen, uncasted_op->value->type->data.array.child_type,
                        &can_be_vec_elem)))
        {
            return ira->codegen->invalid_inst_gen;
        }
        if (can_be_vec_elem) {
            vector_len = uncasted_op->value->type->data.array.len;
        }
    } else if (uncasted_op->value->type->id == ZigTypeIdVector) {
        vector_len = uncasted_op->value->type->data.vector.len;
    }

    bool is_vector = (vector_len != UINT32_MAX);
    ZigType *op_type = is_vector ? get_vector_type(ira->codegen, vector_len, int_type) : int_type;

    Stage1AirInst *op = ir_implicit_cast(ira, uncasted_op, op_type);
    if (type_is_invalid(op->value->type))
        return ira->codegen->invalid_inst_gen;

    if (int_type->data.integral.bit_count == 0)
        return ir_const_unsigned(ira, instruction->base.scope, instruction->base.source_node, 0);

    ZigType *smallest_type = get_smallest_unsigned_int_type(ira->codegen, int_type->data.integral.bit_count);

    if (instr_is_comptime(op)) {
        ZigValue *val = ir_resolve_const(ira, op, UndefOk);
        if (val == nullptr)
            return ira->codegen->invalid_inst_gen;
        if (val->special == ConstValSpecialUndef)
            return ir_const_undef(ira, instruction->base.scope, instruction->base.source_node, ira->codegen->builtin_types.entry_num_lit_int);

        if (is_vector) {
            ZigType *smallest_vec_type = get_vector_type(ira->codegen, vector_len, smallest_type);
            Stage1AirInst *result = ir_const(ira, instruction->base.scope, instruction->base.source_node, smallest_vec_type);
            expand_undef_array(ira->codegen, val);
            result->value->data.x_array.data.s_none.elements = ira->codegen->pass1_arena->allocate<ZigValue>(smallest_vec_type->data.vector.len);
            for (unsigned i = 0; i < smallest_vec_type->data.vector.len; i += 1) {
                ZigValue *op_elem_val = &val->data.x_array.data.s_none.elements[i];
                if ((err = ir_resolve_const_val(ira->codegen, ira->new_irb.exec, instruction->base.source_node,
                    op_elem_val, UndefOk)))
                {
                    return ira->codegen->invalid_inst_gen;
                }
                ZigValue *result_elem_val = &result->value->data.x_array.data.s_none.elements[i];
                result_elem_val->type = smallest_type;
                result_elem_val->special = op_elem_val->special;
                if (op_elem_val->special == ConstValSpecialUndef)
                    continue;
                size_t value = bigint_clz(&op_elem_val->data.x_bigint, int_type->data.integral.bit_count);
                bigint_init_unsigned(&result->value->data.x_array.data.s_none.elements[i].data.x_bigint, value);
            }
            return result;
        } else {
            size_t result_usize = bigint_clz(&op->value->data.x_bigint, int_type->data.integral.bit_count);
            return ir_const_unsigned(ira, instruction->base.scope, instruction->base.source_node, result_usize);
        }
    }

    ZigType *return_type = is_vector ? get_vector_type(ira->codegen, vector_len, smallest_type) : smallest_type;
    return ir_build_clz_gen(ira, instruction->base.scope, instruction->base.source_node, return_type, op);
}

static Stage1AirInst *ir_analyze_instruction_pop_count(IrAnalyze *ira, Stage1ZirInstPopCount *instruction) {
    Error err;

    ZigType *int_type = ir_resolve_int_type(ira, instruction->type->child);
    if (type_is_invalid(int_type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *uncasted_op = instruction->op->child;
    if (type_is_invalid(uncasted_op->value->type))
        return ira->codegen->invalid_inst_gen;

    uint32_t vector_len = UINT32_MAX; // means not a vector
    if (uncasted_op->value->type->id == ZigTypeIdArray) {
        bool can_be_vec_elem;
        if ((err = is_valid_vector_elem_type(ira->codegen, uncasted_op->value->type->data.array.child_type,
                        &can_be_vec_elem)))
        {
            return ira->codegen->invalid_inst_gen;
        }
        if (can_be_vec_elem) {
            vector_len = uncasted_op->value->type->data.array.len;
        }
    } else if (uncasted_op->value->type->id == ZigTypeIdVector) {
        vector_len = uncasted_op->value->type->data.vector.len;
    }

    bool is_vector = (vector_len != UINT32_MAX);
    ZigType *op_type = is_vector ? get_vector_type(ira->codegen, vector_len, int_type) : int_type;

    Stage1AirInst *op = ir_implicit_cast(ira, uncasted_op, op_type);
    if (type_is_invalid(op->value->type))
        return ira->codegen->invalid_inst_gen;

    if (int_type->data.integral.bit_count == 0)
        return ir_const_unsigned(ira, instruction->base.scope, instruction->base.source_node, 0);

    ZigType *smallest_type = get_smallest_unsigned_int_type(ira->codegen, int_type->data.integral.bit_count);

    if (instr_is_comptime(op)) {
        ZigValue *val = ir_resolve_const(ira, op, UndefOk);
        if (val == nullptr)
            return ira->codegen->invalid_inst_gen;
        if (val->special == ConstValSpecialUndef)
            return ir_const_undef(ira, instruction->base.scope, instruction->base.source_node, ira->codegen->builtin_types.entry_num_lit_int);

        if (is_vector) {
            ZigType *smallest_vec_type = get_vector_type(ira->codegen, vector_len, smallest_type);
            Stage1AirInst *result = ir_const(ira, instruction->base.scope, instruction->base.source_node, smallest_vec_type);
            expand_undef_array(ira->codegen, val);
            result->value->data.x_array.data.s_none.elements = ira->codegen->pass1_arena->allocate<ZigValue>(smallest_vec_type->data.vector.len);
            for (unsigned i = 0; i < smallest_vec_type->data.vector.len; i += 1) {
                ZigValue *op_elem_val = &val->data.x_array.data.s_none.elements[i];
                if ((err = ir_resolve_const_val(ira->codegen, ira->new_irb.exec, instruction->base.source_node,
                    op_elem_val, UndefOk)))
                {
                    return ira->codegen->invalid_inst_gen;
                }
                ZigValue *result_elem_val = &result->value->data.x_array.data.s_none.elements[i];
                result_elem_val->type = smallest_type;
                result_elem_val->special = op_elem_val->special;
                if (op_elem_val->special == ConstValSpecialUndef)
                    continue;

                if (bigint_cmp_zero(&op_elem_val->data.x_bigint) != CmpLT) {
                    size_t value = bigint_popcount_unsigned(&op_elem_val->data.x_bigint);
                    bigint_init_unsigned(&result->value->data.x_array.data.s_none.elements[i].data.x_bigint, value);
                }
                size_t value = bigint_popcount_signed(&op_elem_val->data.x_bigint, int_type->data.integral.bit_count);
                bigint_init_unsigned(&result->value->data.x_array.data.s_none.elements[i].data.x_bigint, value);
            }
            return result;
        } else {
            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.scope, instruction->base.source_node, result);
            }
            size_t result = bigint_popcount_signed(&val->data.x_bigint, int_type->data.integral.bit_count);
            return ir_const_unsigned(ira, instruction->base.scope, instruction->base.source_node, result);
        }
    }

    ZigType *return_type = is_vector ? get_vector_type(ira->codegen, vector_len, smallest_type) : smallest_type;
    return ir_build_pop_count_gen(ira, instruction->base.scope, instruction->base.source_node, return_type, op);
}

static Stage1AirInst *ir_analyze_union_tag(IrAnalyze *ira, Scope *scope, AstNode *source_node, Stage1AirInst *value) {
    if (type_is_invalid(value->value->type))
        return ira->codegen->invalid_inst_gen;

    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_inst_gen;
    }
    if (!value->value->type->data.unionation.have_explicit_tag_type) {
        ErrorMsg *msg = ir_add_error_node(ira, source_node, 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_inst_gen;
    }

    ZigType *tag_type = value->value->type->data.unionation.tag_type;
    assert(tag_type->id == ZigTypeIdEnum);

    if (instr_is_comptime(value)) {
        ZigValue *val = ir_resolve_const(ira, value, UndefBad);
        if (!val)
            return ira->codegen->invalid_inst_gen;

        Stage1AirInstConst *const_instruction = ir_create_inst_gen<Stage1AirInstConst>(&ira->new_irb,
                scope, 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;
    }

    return ir_build_union_tag(ira, scope, source_node, value, tag_type);
}

static Stage1AirInst *ir_analyze_instruction_switch_br(IrAnalyze *ira,
        Stage1ZirInstSwitchBr *switch_br_instruction)
{
    Stage1AirInst *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_inst_gen;

    if (is_comptime || instr_is_comptime(target_value)) {
        ZigValue *target_val = ir_resolve_const(ira, target_value, UndefBad);
        if (!target_val)
            return ir_unreach_error(ira);

        Stage1ZirBasicBlock *old_dest_block = switch_br_instruction->else_block;
        for (size_t i = 0; i < case_count; i += 1) {
            Stage1ZirInstSwitchBrCase *old_case = &switch_br_instruction->cases[i];
            Stage1AirInst *case_value = old_case->value->child;
            if (type_is_invalid(case_value->value->type))
                return ir_unreach_error(ira);

            Stage1AirInst *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);

            ZigValue *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.source_node, old_dest_block);
        } else {
            Stage1AirBasicBlock *new_dest_block = ir_get_new_bb(ira, old_dest_block, &switch_br_instruction->base);
            Stage1AirInst *result = ir_build_br_gen(ira, switch_br_instruction->base.scope,
                    switch_br_instruction->base.source_node, new_dest_block);
            return ir_finish_anal(ira, result);
        }
    }

    Stage1AirInstSwitchBrCase *cases = heap::c_allocator.allocate<Stage1AirInstSwitchBrCase>(case_count);
    for (size_t i = 0; i < case_count; i += 1) {
        Stage1ZirInstSwitchBrCase *old_case = &switch_br_instruction->cases[i];
        Stage1AirInstSwitchBrCase *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_inst_gen;

        // 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;

        Stage1ZirInst *old_value = old_case->value;
        Stage1AirInst *new_value = old_value->child;
        if (type_is_invalid(new_value->value->type))
            continue;

        Stage1AirInst *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) {
        Stage1AirInstSwitchBrCase *new_case = &cases[i];
        if (type_is_invalid(new_case->value->value->type))
            return ir_unreach_error(ira);
        new_case->block->ref_instruction = &switch_br_instruction->base;
    }

    Stage1AirBasicBlock *new_else_block = ir_get_new_bb(ira, switch_br_instruction->else_block, &switch_br_instruction->base);
    Stage1AirInstSwitchBr *switch_br = ir_build_switch_br_gen(ira, switch_br_instruction->base.scope,
            switch_br_instruction->base.source_node, target_value, new_else_block, case_count, cases);
    return ir_finish_anal(ira, &switch_br->base);
}

static Stage1AirInst *ir_analyze_instruction_switch_target(IrAnalyze *ira,
        Stage1ZirInstSwitchTarget *switch_target_instruction)
{
    Error err;
    Stage1AirInst *target_value_ptr = switch_target_instruction->target_value_ptr->child;
    if (type_is_invalid(target_value_ptr->value->type))
        return ira->codegen->invalid_inst_gen;

    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.scope,
                switch_target_instruction->base.source_node, ptr_type->data.pointer.child_type);
    }

    ZigType *target_type = target_value_ptr->value->type->data.pointer.child_type;
    ZigValue *pointee_val = nullptr;
    if (instr_is_comptime(target_value_ptr) && target_value_ptr->value->data.x_ptr.mut != ConstPtrMutRuntimeVar) {
        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_inst_gen;

        if (pointee_val->special == ConstValSpecialRuntime)
            pointee_val = nullptr;
    }
    if ((err = type_resolve(ira->codegen, target_type, ResolveStatusSizeKnown)))
        return ira->codegen->invalid_inst_gen;

    switch (target_type->id) {
        case ZigTypeIdInvalid:
            zig_unreachable();
        case ZigTypeIdMetaType:
        case ZigTypeIdVoid:
        case ZigTypeIdBool:
        case ZigTypeIdInt:
        case ZigTypeIdFloat:
        case ZigTypeIdComptimeFloat:
        case ZigTypeIdComptimeInt:
        case ZigTypeIdEnumLiteral:
        case ZigTypeIdPointer:
        case ZigTypeIdFn:
        case ZigTypeIdErrorSet: {
            if (pointee_val) {
                Stage1AirInst *result = ir_const(ira, switch_target_instruction->base.scope,
                        switch_target_instruction->base.source_node, nullptr);
                copy_const_val(ira->codegen, result->value, pointee_val);
                result->value->type = target_type;
                return result;
            }

            Stage1AirInst *result = ir_get_deref(ira, switch_target_instruction->base.scope,
                    switch_target_instruction->base.source_node, target_value_ptr, nullptr);
            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_inst_gen;
            }
            ZigType *tag_type = target_type->data.unionation.tag_type;
            assert(tag_type != nullptr);
            assert(tag_type->id == ZigTypeIdEnum);
            if (pointee_val) {
                Stage1AirInst *result = ir_const(ira, switch_target_instruction->base.scope, switch_target_instruction->base.source_node, tag_type);
                bigint_init_bigint(&result->value->data.x_enum_tag, &pointee_val->data.x_union.tag);
                return result;
            }

            if (can_fold_enum_type(tag_type)) {
                Stage1AirInst *result = ir_const(ira, switch_target_instruction->base.scope, switch_target_instruction->base.source_node, 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;
            }

            Stage1AirInst *union_value = ir_get_deref(ira, switch_target_instruction->base.scope,
                    switch_target_instruction->base.source_node, target_value_ptr, nullptr);
            union_value->value->type = target_type;

            return ir_build_union_tag(ira, switch_target_instruction->base.scope, switch_target_instruction->base.source_node, union_value, tag_type);
        }
        case ZigTypeIdEnum: {
            if ((err = type_resolve(ira->codegen, target_type, ResolveStatusZeroBitsKnown)))
                return ira->codegen->invalid_inst_gen;

            if (can_fold_enum_type(target_type)) {
                TypeEnumField *only_field = &target_type->data.enumeration.fields[0];
                Stage1AirInst *result = ir_const(ira, switch_target_instruction->base.scope, switch_target_instruction->base.source_node, target_type);
                bigint_init_bigint(&result->value->data.x_enum_tag, &only_field->value);
                return result;
            }

            if (pointee_val) {
                Stage1AirInst *result = ir_const(ira, switch_target_instruction->base.scope, switch_target_instruction->base.source_node, target_type);
                bigint_init_bigint(&result->value->data.x_enum_tag, &pointee_val->data.x_enum_tag);
                return result;
            }

            Stage1AirInst *enum_value = ir_get_deref(ira, switch_target_instruction->base.scope,
                    switch_target_instruction->base.source_node, target_value_ptr, nullptr);
            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 ZigTypeIdOpaque:
        case ZigTypeIdVector:
        case ZigTypeIdFnFrame:
        case ZigTypeIdAnyFrame:
            ir_add_error_node(ira, switch_target_instruction->base.source_node,
                buf_sprintf("invalid switch target type '%s'", buf_ptr(&target_type->name)));
            return ira->codegen->invalid_inst_gen;
    }
    zig_unreachable();
}

static Stage1AirInst *ir_analyze_instruction_switch_var(IrAnalyze *ira, Stage1ZirInstSwitchVar *instruction) {
    Stage1AirInst *target_value_ptr = instruction->target_value_ptr->child;
    if (type_is_invalid(target_value_ptr->value->type))
        return ira->codegen->invalid_inst_gen;

    ZigType *ref_type = target_value_ptr->value->type;
    assert(ref_type->id == ZigTypeIdPointer);
    ZigType *target_type = target_value_ptr->value->type->data.pointer.child_type;
    if (target_type->id == ZigTypeIdUnion) {
        ZigType *enum_type = target_type->data.unionation.tag_type;
        assert(enum_type != nullptr);
        assert(enum_type->id == ZigTypeIdEnum);
        assert(instruction->prongs_len > 0);

        Stage1AirInst *first_prong_value = instruction->prongs_ptr[0]->child;
        if (type_is_invalid(first_prong_value->value->type))
            return ira->codegen->invalid_inst_gen;

        Stage1AirInst *first_casted_prong_value = ir_implicit_cast(ira, first_prong_value, enum_type);
        if (type_is_invalid(first_casted_prong_value->value->type))
            return ira->codegen->invalid_inst_gen;

        ZigValue *first_prong_val = ir_resolve_const(ira, first_casted_prong_value, UndefBad);
        if (first_prong_val == nullptr)
            return ira->codegen->invalid_inst_gen;

        TypeUnionField *first_field = find_union_field_by_tag(target_type, &first_prong_val->data.x_enum_tag);

        ErrorMsg *invalid_payload_msg = nullptr;
        for (size_t prong_i = 1; prong_i < instruction->prongs_len; prong_i += 1) {
            Stage1AirInst *this_prong_inst = instruction->prongs_ptr[prong_i]->child;
            if (type_is_invalid(this_prong_inst->value->type))
                return ira->codegen->invalid_inst_gen;

            Stage1AirInst *this_casted_prong_value = ir_implicit_cast(ira, this_prong_inst, enum_type);
            if (type_is_invalid(this_casted_prong_value->value->type))
                return ira->codegen->invalid_inst_gen;

            ZigValue *this_prong = ir_resolve_const(ira, this_casted_prong_value, UndefBad);
            if (this_prong == nullptr)
                return ira->codegen->invalid_inst_gen;

            TypeUnionField *payload_field = find_union_field_by_tag(target_type, &this_prong->data.x_enum_tag);
            ZigType *payload_type = payload_field->type_entry;
            if (first_field->type_entry != payload_type) {
                if (invalid_payload_msg == nullptr) {
                    invalid_payload_msg = ir_add_error_node(ira, instruction->base.source_node,
                        buf_sprintf("capture group with incompatible types"));
                    add_error_note(ira->codegen, invalid_payload_msg, first_prong_value->source_node,
                            buf_sprintf("type '%s' here", buf_ptr(&first_field->type_entry->name)));
                }
                add_error_note(ira->codegen, invalid_payload_msg, this_prong_inst->source_node,
                        buf_sprintf("type '%s' here", buf_ptr(&payload_field->type_entry->name)));
            }
        }

        if (invalid_payload_msg != nullptr) {
            return ira->codegen->invalid_inst_gen;
        }

        if (instr_is_comptime(target_value_ptr)) {
            ZigValue *target_val_ptr = ir_resolve_const(ira, target_value_ptr, UndefBad);
            if (!target_value_ptr)
                return ira->codegen->invalid_inst_gen;

            ZigValue *pointee_val = const_ptr_pointee(ira, ira->codegen, target_val_ptr, instruction->base.source_node);
            if (pointee_val == nullptr)
                return ira->codegen->invalid_inst_gen;

            Stage1AirInst *result = ir_const(ira, instruction->base.scope, instruction->base.source_node,
                    get_pointer_to_type(ira->codegen, first_field->type_entry,
                    target_val_ptr->type->data.pointer.is_const));
            ZigValue *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;
        }

        ZigType *result_type = get_pointer_to_type(ira->codegen, first_field->type_entry,
                target_value_ptr->value->type->data.pointer.is_const);
        return ir_build_union_field_ptr(ira, instruction->base.scope, instruction->base.source_node, target_value_ptr, first_field,
                false, false, result_type);
    } else if (target_type->id == ZigTypeIdErrorSet) {
        // construct an error set from the prong values
        ZigType *err_set_type = new_type_table_entry(ZigTypeIdErrorSet);
        err_set_type->size_in_bits = ira->codegen->builtin_types.entry_global_error_set->size_in_bits;
        err_set_type->abi_align = ira->codegen->builtin_types.entry_global_error_set->abi_align;
        err_set_type->abi_size = ira->codegen->builtin_types.entry_global_error_set->abi_size;
        ZigList<ErrorTableEntry *> error_list = {};
        buf_resize(&err_set_type->name, 0);
        buf_appendf(&err_set_type->name, "error{");
        for (size_t i = 0; i < instruction->prongs_len; i += 1) {
            ErrorTableEntry *err = ir_resolve_error(ira, instruction->prongs_ptr[i]->child);
            if (err == nullptr)
                return ira->codegen->invalid_inst_gen;
            error_list.append(err);
            buf_appendf(&err_set_type->name, "%s,", buf_ptr(&err->name));
        }
        err_set_type->data.error_set.errors = error_list.items;
        err_set_type->data.error_set.err_count = error_list.length;
        buf_appendf(&err_set_type->name, "}");


        ZigType *new_target_value_ptr_type = get_pointer_to_type_extra(ira->codegen,
            err_set_type,
            ref_type->data.pointer.is_const, ref_type->data.pointer.is_volatile,
            ref_type->data.pointer.ptr_len,
            ref_type->data.pointer.explicit_alignment,
            ref_type->data.pointer.bit_offset_in_host, ref_type->data.pointer.host_int_bytes,
            ref_type->data.pointer.allow_zero);
        return ir_analyze_ptr_cast(ira, instruction->base.scope, instruction->base.source_node,
                target_value_ptr, instruction->target_value_ptr->source_node,
                new_target_value_ptr_type, instruction->base.source_node, false, false);
    } else if (instruction->prongs_len > 1) {
        return target_value_ptr;
    } else {
        ir_add_error_node(ira, instruction->base.source_node,
            buf_sprintf("switch on type '%s' provides no expression parameter", buf_ptr(&target_type->name)));
        return ira->codegen->invalid_inst_gen;
    }
}

static Stage1AirInst *ir_analyze_instruction_switch_else_var(IrAnalyze *ira,
        Stage1ZirInstSwitchElseVar *instruction)
{
    Stage1AirInst *target_value_ptr = instruction->target_value_ptr->child;
    if (type_is_invalid(target_value_ptr->value->type))
        return ira->codegen->invalid_inst_gen;

    ZigType *ref_type = target_value_ptr->value->type;
    assert(ref_type->id == ZigTypeIdPointer);
    ZigType *target_type = target_value_ptr->value->type->data.pointer.child_type;
    if (target_type->id == ZigTypeIdErrorSet) {
        //  make a new set that has the other cases removed
        if (!resolve_inferred_error_set(ira->codegen, target_type, instruction->base.source_node)) {
            return ira->codegen->invalid_inst_gen;
        }
        if (type_is_global_error_set(target_type)) {
            // the type of the else capture variable still has to be the global error set.
            // once the runtime hint system is more sophisticated, we could add some hint information here.
            return target_value_ptr;
        }
        // Make note of the errors handled by other cases
        ErrorTableEntry **errors = heap::c_allocator.allocate<ErrorTableEntry *>(ira->codegen->errors_by_index.length);
        // We may not have any case in the switch if this is a lone else
        const size_t switch_cases = instruction->switch_br ? instruction->switch_br->case_count : 0;
        for (size_t case_i = 0; case_i < switch_cases; case_i += 1) {
            Stage1ZirInstSwitchBrCase *br_case = &instruction->switch_br->cases[case_i];
            Stage1AirInst *case_expr = br_case->value->child;
            if (case_expr->value->type->id == ZigTypeIdErrorSet) {
                ErrorTableEntry *err = ir_resolve_error(ira, case_expr);
                if (err == nullptr)
                    return ira->codegen->invalid_inst_gen;
                errors[err->value] = err;
            } else if (case_expr->value->type->id == ZigTypeIdMetaType) {
                ZigType *err_set_type = ir_resolve_type(ira, case_expr);
                if (type_is_invalid(err_set_type))
                    return ira->codegen->invalid_inst_gen;
                populate_error_set_table(errors, err_set_type);
            } else {
                zig_unreachable();
            }
        }
        ZigList<ErrorTableEntry *> result_list = {};

        ZigType *err_set_type = new_type_table_entry(ZigTypeIdErrorSet);
        buf_resize(&err_set_type->name, 0);
        buf_appendf(&err_set_type->name, "error{");

        // Look at all the errors in the type switched on and add them to the result_list
        // if they are not handled by cases.
        for (uint32_t i = 0; i < target_type->data.error_set.err_count; i += 1) {
            ErrorTableEntry *error_entry = target_type->data.error_set.errors[i];
            ErrorTableEntry *existing_entry = errors[error_entry->value];
            if (existing_entry == nullptr) {
                result_list.append(error_entry);
                buf_appendf(&err_set_type->name, "%s,", buf_ptr(&error_entry->name));
            }
        }
        heap::c_allocator.deallocate(errors, ira->codegen->errors_by_index.length);

        err_set_type->data.error_set.err_count = result_list.length;
        err_set_type->data.error_set.errors = result_list.items;
        err_set_type->size_in_bits = ira->codegen->builtin_types.entry_global_error_set->size_in_bits;
        err_set_type->abi_align = ira->codegen->builtin_types.entry_global_error_set->abi_align;
        err_set_type->abi_size = ira->codegen->builtin_types.entry_global_error_set->abi_size;

        buf_appendf(&err_set_type->name, "}");

        ZigType *new_target_value_ptr_type = get_pointer_to_type_extra(ira->codegen,
            err_set_type,
            ref_type->data.pointer.is_const, ref_type->data.pointer.is_volatile,
            ref_type->data.pointer.ptr_len,
            ref_type->data.pointer.explicit_alignment,
            ref_type->data.pointer.bit_offset_in_host, ref_type->data.pointer.host_int_bytes,
            ref_type->data.pointer.allow_zero);
        return ir_analyze_ptr_cast(ira, instruction->base.scope, instruction->base.source_node,
                target_value_ptr, instruction->target_value_ptr->source_node,
                new_target_value_ptr_type, instruction->base.source_node, false, false);
    }

    return target_value_ptr;
}

static Stage1AirInst *ir_analyze_instruction_import(IrAnalyze *ira, Stage1ZirInstImport *import_instruction) {
    Error err;

    Stage1AirInst *name_value = import_instruction->name->child;
    Buf *import_target_str = ir_resolve_str(ira, name_value);
    if (!import_target_str)
        return ira->codegen->invalid_inst_gen;

    AstNode *source_node = import_instruction->base.source_node;
    ZigType *import = source_node->owner;

    ZigType *target_import;
    Buf *import_target_path;
    Buf full_path = BUF_INIT;
    if ((err = analyze_import(ira->codegen, import, import_target_str, &target_import,
        &import_target_path, &full_path)))
    {
        if (err == ErrorImportOutsidePkgPath) {
            ir_add_error_node(ira, source_node,
                    buf_sprintf("import of file outside package path: '%s'",
                        buf_ptr(import_target_path)));
            return ira->codegen->invalid_inst_gen;
        } else 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_inst_gen;
        } 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_inst_gen;
        }
    }

    return ir_const_type(ira, import_instruction->base.scope, import_instruction->base.source_node, target_import);
}

static Stage1AirInst *ir_analyze_instruction_ref(IrAnalyze *ira, Stage1ZirInstRef *ref_instruction) {
    Stage1AirInst *value = ref_instruction->value->child;
    if (type_is_invalid(value->value->type))
        return ira->codegen->invalid_inst_gen;

    bool is_const = false;
    bool is_volatile = false;

    ZigValue *child_value = value->value;
    if (child_value->special == ConstValSpecialStatic) {
        is_const = true;
    }

    return ir_get_ref(ira, ref_instruction->base.scope, ref_instruction->base.source_node, value, is_const, is_volatile);
}

static Stage1AirInst *ir_analyze_union_init(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        AstNode *field_source_node, ZigType *union_type, Buf *field_name, Stage1AirInst *field_result_loc,
        Stage1AirInst *result_loc)
{
    Error err;
    assert(union_type->id == ZigTypeIdUnion);

    if ((err = type_resolve(ira->codegen, union_type, ResolveStatusZeroBitsKnown)))
        return ira->codegen->invalid_inst_gen;

    TypeUnionField *type_field = find_union_type_field(union_type, field_name);
    if (type_field == nullptr) {
        ir_add_error_node(ira, field_source_node,
            buf_sprintf("no field named '%s' in union '%s'",
                buf_ptr(field_name), buf_ptr(&union_type->name)));
        return ira->codegen->invalid_inst_gen;
    }

    if (type_is_invalid(type_field->type_entry))
        return ira->codegen->invalid_inst_gen;

    if (result_loc->value->data.x_ptr.mut == ConstPtrMutInfer) {
        if (instr_is_comptime(field_result_loc) &&
            field_result_loc->value->data.x_ptr.mut != ConstPtrMutRuntimeVar)
        {
            // nothing
        } else {
            result_loc->value->special = ConstValSpecialRuntime;
        }
    }

    bool is_comptime = ir_should_inline(ira->zir, scope)
        || type_requires_comptime(ira->codegen, union_type) == ReqCompTimeYes;

    Stage1AirInst *result = ir_get_deref(ira, scope, source_node, result_loc, nullptr);
    if (is_comptime && !instr_is_comptime(result)) {
        ir_add_error(ira, field_result_loc,
            buf_sprintf("unable to evaluate constant expression"));
        return ira->codegen->invalid_inst_gen;
    }
    return result;
}

static Stage1AirInst *ir_analyze_container_init_fields(IrAnalyze *ira, Scope *scope, AstNode *source_node,
    ZigType *container_type, size_t instr_field_count, Stage1ZirInstContainerInitFieldsField *fields,
    Stage1AirInst *result_loc)
{
    Error err;
    if (container_type->id == ZigTypeIdUnion) {
        if (instr_field_count != 1) {
            ir_add_error_node(ira, source_node,
                buf_sprintf("union initialization expects exactly one field"));
            return ira->codegen->invalid_inst_gen;
        }
        Stage1ZirInstContainerInitFieldsField *field = &fields[0];
        Stage1AirInst *field_result_loc = field->result_loc->child;
        if (type_is_invalid(field_result_loc->value->type))
            return ira->codegen->invalid_inst_gen;

        return ir_analyze_union_init(ira, scope, source_node, field->source_node, container_type, field->name,
                field_result_loc, result_loc);
    }
    if (container_type->id != ZigTypeIdStruct || is_slice(container_type)) {
        ir_add_error_node(ira, source_node,
            buf_sprintf("type '%s' does not support struct initialization syntax",
                buf_ptr(&container_type->name)));
        return ira->codegen->invalid_inst_gen;
    }

    if (container_type->data.structure.resolve_status == ResolveStatusBeingInferred) {
        // We're now done inferring the type.
        container_type->data.structure.resolve_status = ResolveStatusUnstarted;
    }

    if ((err = type_resolve(ira->codegen, container_type, ResolveStatusSizeKnown)))
        return ira->codegen->invalid_inst_gen;

    size_t actual_field_count = container_type->data.structure.src_field_count;

    Stage1AirInst *first_non_const_instruction = nullptr;

    AstNode **field_assign_nodes = heap::c_allocator.allocate<AstNode *>(actual_field_count);
    ZigList<Stage1AirInst *> const_ptrs = {};

    bool is_comptime = ir_should_inline(ira->zir, scope)
        || type_requires_comptime(ira->codegen, container_type) == ReqCompTimeYes;


    // Here we iterate over the fields that have been initialized, and emit
    // compile errors for missing fields and duplicate fields.
    // It is only now that we find out whether the struct initialization can be a comptime
    // value, but we have already emitted runtime instructions for the fields that
    // were initialized with runtime values, and have omitted instructions that would have
    // initialized fields with comptime values.
    // So now we must clean up this situation. If it turns out the struct initialization can
    // be a comptime value, overwrite ConstPtrMutInfer with ConstPtrMutComptimeConst.
    // Otherwise, we must emit instructions to runtime-initialize the fields that have
    // comptime-known values.

    for (size_t i = 0; i < instr_field_count; i += 1) {
        Stage1ZirInstContainerInitFieldsField *field = &fields[i];

        Stage1AirInst *field_result_loc = field->result_loc->child;
        if (type_is_invalid(field_result_loc->value->type))
            return ira->codegen->invalid_inst_gen;

        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 field named '%s' in struct '%s'",
                    buf_ptr(field->name), buf_ptr(&container_type->name)));
            return ira->codegen->invalid_inst_gen;
        }

        if (type_is_invalid(type_field->type_entry))
            return ira->codegen->invalid_inst_gen;

        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_inst_gen;
        }
        field_assign_nodes[field_index] = field->source_node;

        if (instr_is_comptime(field_result_loc) &&
            field_result_loc->value->data.x_ptr.mut != ConstPtrMutRuntimeVar)
        {
            const_ptrs.append(field_result_loc);
        } else {
            first_non_const_instruction = field_result_loc;
        }
    }

    bool any_missing = false;
    for (size_t i = 0; i < actual_field_count; i += 1) {
        if (field_assign_nodes[i] != nullptr) continue;

        // look for a default field value
        TypeStructField *field = container_type->data.structure.fields[i];
        memoize_field_init_val(ira->codegen, container_type, field);
        if (field->init_val == nullptr) {
            ir_add_error_node(ira, source_node,
                buf_sprintf("missing field: '%s'", buf_ptr(field->name)));
            any_missing = true;
            continue;
        }
        if (type_is_invalid(field->init_val->type))
            return ira->codegen->invalid_inst_gen;

        Stage1AirInst *runtime_inst = ir_const(ira, scope, source_node, field->init_val->type);
        copy_const_val(ira->codegen, runtime_inst->value, field->init_val);

        Stage1AirInst *field_ptr = ir_analyze_struct_field_ptr(ira, scope, source_node, field, result_loc,
                container_type, true);
        ir_analyze_store_ptr(ira, scope, source_node, field_ptr, runtime_inst, false);
        if (instr_is_comptime(field_ptr) && field_ptr->value->data.x_ptr.mut != ConstPtrMutRuntimeVar) {
            const_ptrs.append(field_ptr);
        } else {
            first_non_const_instruction = result_loc;
        }
    }
    heap::c_allocator.deallocate(field_assign_nodes, actual_field_count);
    if (any_missing)
        return ira->codegen->invalid_inst_gen;

    if (result_loc->value->data.x_ptr.mut == ConstPtrMutInfer) {
        if (const_ptrs.length != actual_field_count) {
            result_loc->value->special = ConstValSpecialRuntime;
            for (size_t i = 0; i < const_ptrs.length; i += 1) {
                Stage1AirInst *field_result_loc = const_ptrs.at(i);
                Stage1AirInst *deref = ir_get_deref(ira, field_result_loc->scope,
                        field_result_loc->source_node, field_result_loc, nullptr);
                field_result_loc->value->special = ConstValSpecialRuntime;
                ir_analyze_store_ptr(ira, field_result_loc->scope, field_result_loc->source_node,
                        field_result_loc, deref, false);
            }
        }
    }

    const_ptrs.deinit();
    Stage1AirInst *result = ir_get_deref(ira, scope, source_node, result_loc, nullptr);

    if (is_comptime && !instr_is_comptime(result)) {
        ir_add_error_node(ira, first_non_const_instruction->source_node,
            buf_sprintf("unable to evaluate constant expression"));
        return ira->codegen->invalid_inst_gen;
    }

    return result;
}

static Stage1AirInst *ir_analyze_instruction_container_init_list(IrAnalyze *ira,
        Stage1ZirInstContainerInitList *instruction)
{
    src_assert(instruction->result_loc != nullptr, instruction->base.source_node);
    Stage1AirInst *result_loc = instruction->result_loc->child;
    if (type_is_invalid(result_loc->value->type))
        return result_loc;

    src_assert(result_loc->value->type->id == ZigTypeIdPointer, instruction->base.source_node);
    if (result_loc->value->type->data.pointer.is_const) {
        ir_add_error_node(ira, instruction->base.source_node, buf_sprintf("cannot assign to constant"));
        return ira->codegen->invalid_inst_gen;
    }

    ZigType *container_type = result_loc->value->type->data.pointer.child_type;
    size_t elem_count = instruction->item_count;

    if (is_slice(container_type)) {
        ir_add_error_node(ira, instruction->init_array_type_source_node,
            buf_sprintf("array literal requires address-of operator to coerce to slice type '%s'",
                buf_ptr(&container_type->name)));
        return ira->codegen->invalid_inst_gen;
    }

    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_inst_gen;
        }
        return ir_const_void(ira, instruction->base.scope, instruction->base.source_node);
    }

    if (container_type->id == ZigTypeIdStruct && elem_count == 0) {
        src_assert(instruction->result_loc != nullptr, instruction->base.source_node);
        Stage1AirInst *result_loc = instruction->result_loc->child;
        if (type_is_invalid(result_loc->value->type))
            return result_loc;
        return ir_analyze_container_init_fields(ira, instruction->base.scope, instruction->base.source_node, container_type, 0, nullptr, result_loc);
    }

    if (container_type->id == ZigTypeIdArray) {
        ZigType *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, nullptr);

            ir_add_error_node(ira, instruction->base.source_node,
                buf_sprintf("expected %s literal, found %s literal",
                    buf_ptr(&container_type->name), buf_ptr(&literal_type->name)));
            return ira->codegen->invalid_inst_gen;
        }
    } else if (container_type->id == ZigTypeIdStruct &&
         container_type->data.structure.resolve_status == ResolveStatusBeingInferred)
    {
        // We're now done inferring the type.
        container_type->data.structure.resolve_status = ResolveStatusUnstarted;
    } else if (container_type->id == ZigTypeIdVector || is_tuple(container_type)) {
        // OK
    } 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_inst_gen;
    }

    switch (type_has_one_possible_value(ira->codegen, container_type)) {
        case OnePossibleValueInvalid:
            return ira->codegen->invalid_inst_gen;
        case OnePossibleValueYes:
            return ir_const_move(ira, instruction->base.scope, instruction->base.source_node,
                     get_the_one_possible_value(ira->codegen, container_type));
        case OnePossibleValueNo:
            break;
    }

    bool is_comptime;
    switch (type_requires_comptime(ira->codegen, container_type)) {
        case ReqCompTimeInvalid:
            return ira->codegen->invalid_inst_gen;
        case ReqCompTimeNo:
            is_comptime = ir_should_inline(ira->zir, instruction->base.scope);
            break;
        case ReqCompTimeYes:
            is_comptime = true;
            break;
    }

    Stage1AirInst *first_non_const_instruction = nullptr;

    // The Result Location Mechanism has already emitted runtime instructions to
    // initialize runtime elements and has omitted instructions for the comptime
    // elements. However it is only now that we find out whether the array initialization
    // can be a comptime value. So we must clean up the situation. If it turns out
    // array initialization can be a comptime value, overwrite ConstPtrMutInfer with
    // ConstPtrMutComptimeConst. Otherwise, emit instructions to runtime-initialize the
    // elements that have comptime-known values.
    ZigList<Stage1AirInst *> const_ptrs = {};

    for (size_t i = 0; i < elem_count; i += 1) {
        Stage1AirInst *elem_result_loc = instruction->elem_result_loc_list[i]->child;
        if (type_is_invalid(elem_result_loc->value->type))
            return ira->codegen->invalid_inst_gen;

        assert(elem_result_loc->value->type->id == ZigTypeIdPointer);

        if (instr_is_comptime(elem_result_loc) &&
            elem_result_loc->value->data.x_ptr.mut != ConstPtrMutRuntimeVar)
        {
            const_ptrs.append(elem_result_loc);
        } else {
            first_non_const_instruction = elem_result_loc;
        }
    }

    if (result_loc->value->data.x_ptr.mut == ConstPtrMutInfer) {
        if (const_ptrs.length != elem_count) {
            result_loc->value->special = ConstValSpecialRuntime;
            for (size_t i = 0; i < const_ptrs.length; i += 1) {
                Stage1AirInst *elem_result_loc = const_ptrs.at(i);
                assert(elem_result_loc->value->special == ConstValSpecialStatic);
                if (elem_result_loc->value->type->data.pointer.inferred_struct_field != nullptr) {
                    // This field will be generated comptime; no need to do this.
                    continue;
                }
                Stage1AirInst *deref = ir_get_deref(ira, elem_result_loc->scope,
                        elem_result_loc->source_node, elem_result_loc, nullptr);
                elem_result_loc->value->special = ConstValSpecialRuntime;
                ir_analyze_store_ptr(ira, elem_result_loc->scope, elem_result_loc->source_node,
                        elem_result_loc, deref, false);
            }
        }
    }

    const_ptrs.deinit();

    Stage1AirInst *result = ir_get_deref(ira, instruction->base.scope, instruction->base.source_node,
            result_loc, nullptr);
    // If the result is a tuple, we are allowed to return a struct that uses ConstValSpecialRuntime fields at comptime.
    if (instr_is_comptime(result) || is_tuple(container_type))
        return result;

    if (is_comptime) {
        ir_add_error(ira, first_non_const_instruction,
            buf_sprintf("unable to evaluate constant expression"));
        return ira->codegen->invalid_inst_gen;
    }

    ZigType *result_elem_type = result_loc->value->type->data.pointer.child_type;
    if (is_slice(result_elem_type)) {
        ErrorMsg *msg = ir_add_error_node(ira, instruction->base.source_node,
            buf_sprintf("runtime-initialized array cannot be casted to slice type '%s'",
                buf_ptr(&result_elem_type->name)));
        add_error_note(ira->codegen, msg, first_non_const_instruction->source_node,
            buf_sprintf("this value is not comptime-known"));
        return ira->codegen->invalid_inst_gen;
    }
    return result;
}

static Stage1AirInst *ir_analyze_instruction_container_init_fields(IrAnalyze *ira,
        Stage1ZirInstContainerInitFields *instruction)
{
    src_assert(instruction->result_loc != nullptr, instruction->base.source_node);
    Stage1AirInst *result_loc = instruction->result_loc->child;
    if (type_is_invalid(result_loc->value->type))
        return result_loc;

    src_assert(result_loc->value->type->id == ZigTypeIdPointer, instruction->base.source_node);
    if (result_loc->value->type->data.pointer.is_const) {
        ir_add_error_node(ira, instruction->base.source_node, buf_sprintf("cannot assign to constant"));
        return ira->codegen->invalid_inst_gen;
    }

    ZigType *container_type = result_loc->value->type->data.pointer.child_type;

    return ir_analyze_container_init_fields(ira, instruction->base.scope, instruction->base.source_node, container_type,
        instruction->field_count, instruction->fields, result_loc);
}

static Stage1AirInst *ir_analyze_instruction_compile_err(IrAnalyze *ira, Stage1ZirInstCompileErr *instruction) {
    Stage1AirInst *msg_value = instruction->msg->child;
    Buf *msg_buf = ir_resolve_str(ira, msg_value);
    if (!msg_buf)
        return ira->codegen->invalid_inst_gen;

    ir_add_error_node(ira, instruction->base.source_node, msg_buf);

    return ira->codegen->invalid_inst_gen;
}

static Stage1AirInst *ir_analyze_instruction_compile_log(IrAnalyze *ira, Stage1ZirInstCompileLog *instruction) {
    Buf buf = BUF_INIT;
    fprintf(stderr, "| ");
    for (size_t i = 0; i < instruction->msg_count; i += 1) {
        Stage1AirInst *msg = instruction->msg_list[i]->child;
        if (type_is_invalid(msg->value->type))
            return ira->codegen->invalid_inst_gen;
        buf_resize(&buf, 0);
        if (msg->value->special == ConstValSpecialLazy) {
            // Resolve any lazy value that's passed, we need its value
            if (ir_resolve_lazy(ira->codegen, msg->source_node, msg->value))
                return ira->codegen->invalid_inst_gen;
        }
        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.scope, instruction->base.source_node);
}

static Stage1AirInst *ir_analyze_instruction_err_name(IrAnalyze *ira, Stage1ZirInstErrName *instruction) {
    Stage1AirInst *value = instruction->value->child;
    if (type_is_invalid(value->value->type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *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_inst_gen;

    if (instr_is_comptime(casted_value)) {
        ZigValue *val = ir_resolve_const(ira, casted_value, UndefBad);
        if (val == nullptr)
            return ira->codegen->invalid_inst_gen;
        ErrorTableEntry *err = casted_value->value->data.x_err_set;
        if (!err->cached_error_name_val) {
            err->cached_error_name_val = create_sentineled_str_lit(
                ira->codegen, &err->name,
                ira->codegen->intern.for_zero_byte());
        }
        Stage1AirInst *result = ir_const(ira, instruction->base.scope, instruction->base.source_node, nullptr);
        result->value = err->cached_error_name_val;
        return result;
    }

    ira->codegen->generate_error_name_table = true;

    ZigType *u8_ptr_type = get_pointer_to_type_extra2(ira->codegen, ira->codegen->builtin_types.entry_u8,
            true, false, PtrLenUnknown, 0, 0, 0, false,
            VECTOR_INDEX_NONE, nullptr, ira->codegen->intern.for_zero_byte());
    ZigType *str_type = get_slice_type(ira->codegen, u8_ptr_type);
    return ir_build_err_name_gen(ira, instruction->base.scope, instruction->base.source_node, value, str_type);
}

static Stage1AirInst *ir_analyze_instruction_enum_tag_name(IrAnalyze *ira, Stage1ZirInstTagName *instruction) {
    Error err;
    Stage1AirInst *target = instruction->target->child;
    if (type_is_invalid(target->value->type))
        return ira->codegen->invalid_inst_gen;

    ZigType *target_type = target->value->type;

    if (target_type->id == ZigTypeIdEnumLiteral) {
        Stage1AirInst *result = ir_const(ira, instruction->base.scope, instruction->base.source_node, nullptr);
        Buf *field_name = target->value->data.x_enum_literal;
        result->value = create_sentineled_str_lit(
            ira->codegen, field_name,
            ira->codegen->intern.for_zero_byte());
        return result;
    }

    if (target_type->id == ZigTypeIdUnion) {
        target = ir_analyze_union_tag(ira, instruction->base.scope, instruction->base.source_node, target);
        if (type_is_invalid(target->value->type))
            return ira->codegen->invalid_inst_gen;
        target_type = target->value->type;
    }

    if (target_type->id != ZigTypeIdEnum) {
        ir_add_error(ira, target,
            buf_sprintf("expected enum tag, found '%s'", buf_ptr(&target_type->name)));
        return ira->codegen->invalid_inst_gen;
    }

    if (can_fold_enum_type(target_type)) {
        TypeEnumField *only_field = &target_type->data.enumeration.fields[0];
        Stage1AirInst *result = ir_const(ira, instruction->base.scope, instruction->base.source_node, nullptr);
        result->value = create_sentineled_str_lit(
            ira->codegen, only_field->name,
            ira->codegen->intern.for_zero_byte());
        return result;
    }

    if (instr_is_comptime(target)) {
        if ((err = type_resolve(ira->codegen, target_type, ResolveStatusZeroBitsKnown)))
            return ira->codegen->invalid_inst_gen;
        TypeEnumField *field = find_enum_field_by_tag(target_type, &target->value->data.x_bigint);
        if (field == nullptr) {
            Buf *int_buf = buf_alloc();
            bigint_append_buf(int_buf, &target->value->data.x_bigint, 10);

            ir_add_error(ira, target,
                buf_sprintf("no tag by value %s", buf_ptr(int_buf)));
            return ira->codegen->invalid_inst_gen;
        }
        Stage1AirInst *result = ir_const(ira, instruction->base.scope, instruction->base.source_node, nullptr);
        result->value = create_sentineled_str_lit(
            ira->codegen, field->name,
            ira->codegen->intern.for_zero_byte());
        return result;
    }

    ZigType *u8_ptr_type = get_pointer_to_type_extra2(
            ira->codegen, ira->codegen->builtin_types.entry_u8,
            true, false, PtrLenUnknown, 0, 0, 0, false,
            VECTOR_INDEX_NONE, nullptr, ira->codegen->intern.for_zero_byte());
    ZigType *result_type = get_slice_type(ira->codegen, u8_ptr_type);
    return ir_build_tag_name_gen(ira, instruction->base.scope, instruction->base.source_node, target, result_type);
}

static Stage1AirInst *ir_analyze_instruction_field_parent_ptr(IrAnalyze *ira,
        Stage1ZirInstFieldParentPtr *instruction)
{
    Error err;
    Stage1AirInst *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_inst_gen;

    Stage1AirInst *field_name_value = instruction->field_name->child;
    Buf *field_name = ir_resolve_str(ira, field_name_value);
    if (!field_name)
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *field_ptr = instruction->field_ptr->child;
    if (type_is_invalid(field_ptr->value->type))
        return ira->codegen->invalid_inst_gen;

    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_inst_gen;
    }

    if ((err = type_resolve(ira->codegen, container_type, ResolveStatusSizeKnown)))
        return ira->codegen->invalid_inst_gen;

    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_inst_gen;
    }

    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_inst_gen;
    }

    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, false);
    Stage1AirInst *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_inst_gen;

    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, false);

    if (instr_is_comptime(casted_field_ptr)) {
        ZigValue *field_ptr_val = ir_resolve_const(ira, casted_field_ptr, UndefBad);
        if (!field_ptr_val)
            return ira->codegen->invalid_inst_gen;

        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_inst_gen;
        }

        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_node(ira, instruction->base.source_node,
                    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_inst_gen;
        }

        Stage1AirInst *result = ir_const(ira, instruction->base.scope, instruction->base.source_node, result_type);
        ZigValue *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;
    }

    return ir_build_field_parent_ptr_gen(ira, instruction->base.scope, instruction->base.source_node, casted_field_ptr, field, result_type);
}

static TypeStructField *validate_host_int_byte_offset(IrAnalyze *ira,
        Stage1AirInst *type_value,
        Stage1AirInst *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 = type_resolve(ira->codegen, container_type, ResolveStatusSizeKnown)))
        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(ira->codegen, 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 = field->offset;
    return field;
}

static Stage1AirInst *ir_analyze_instruction_offset_of(IrAnalyze *ira, Stage1ZirInstOffsetOf *instruction) {
    Stage1AirInst *type_value = instruction->type_value->child;
    if (type_is_invalid(type_value->value->type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *field_name_value = instruction->field_name->child;
    size_t host_int_byte_offset = 0;
    TypeStructField *field = nullptr;
    if (!(field = validate_host_int_byte_offset(ira, type_value, field_name_value, &host_int_byte_offset)))
        return ira->codegen->invalid_inst_gen;

    size_t byte_offset = host_int_byte_offset + (field->bit_offset_in_host / 8);
    return ir_const_unsigned(ira, instruction->base.scope, instruction->base.source_node, byte_offset);
}

static Stage1AirInst *ir_analyze_instruction_bit_offset_of(IrAnalyze *ira, Stage1ZirInstBitOffsetOf *instruction) {
    Stage1AirInst *type_value = instruction->type_value->child;
    if (type_is_invalid(type_value->value->type))
        return ira->codegen->invalid_inst_gen;
    Stage1AirInst *field_name_value = instruction->field_name->child;
    size_t host_int_byte_offset = 0;
    TypeStructField *field = nullptr;
    if (!(field = validate_host_int_byte_offset(ira, type_value, field_name_value, &host_int_byte_offset)))
        return ira->codegen->invalid_inst_gen;

    size_t bit_offset = host_int_byte_offset * 8 + field->bit_offset_in_host;
    return ir_const_unsigned(ira, instruction->base.scope, instruction->base.source_node, 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));
    field_name_buf = buf_create_from_str(field_name);
    TypeStructField *field = find_struct_type_field(type, field_name_buf);
    buf_deinit(field_name_buf);

    if (field == nullptr || field->src_index != index)
        zig_panic("reference to unknown field %s", field_name);
}

static ZigType *ir_type_info_get_type(IrAnalyze *ira, const char *type_name, ZigType *root) {
    Error err;
    ZigType *type_info_type = get_builtin_type(ira->codegen, "TypeInfo");
    assert(type_info_type->id == ZigTypeIdUnion);
    if ((err = type_resolve(ira->codegen, type_info_type, ResolveStatusSizeKnown))) {
        zig_unreachable();
    }

    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;

    assert(var->const_value->type->id == ZigTypeIdMetaType);

    return ir_resolve_const_type(ira->codegen, ira->new_irb.exec, nullptr, var->const_value);
}

static Error ir_make_type_info_decls(IrAnalyze *ira, AstNode *source_node, ZigValue *out_val,
        ScopeDecls *decls_scope, bool resolve_types)
{
    Error err;
    ZigType *type_info_declaration_type = ir_type_info_get_type(ira, "Declaration", nullptr);
    if ((err = type_resolve(ira->codegen, type_info_declaration_type, ResolveStatusSizeKnown)))
        return err;

    ensure_field_index(type_info_declaration_type, "name", 0);
    ensure_field_index(type_info_declaration_type, "is_pub", 1);
    ensure_field_index(type_info_declaration_type, "data", 2);

    if (!resolve_types) {
        ZigType *ptr_type = get_pointer_to_type_extra(ira->codegen, type_info_declaration_type,
            false, false, PtrLenUnknown, 0, 0, 0, false);

        out_val->special = ConstValSpecialLazy;
        out_val->type = get_slice_type(ira->codegen, ptr_type);

        LazyValueTypeInfoDecls *lazy_type_info_decls = heap::c_allocator.create<LazyValueTypeInfoDecls>();
        lazy_type_info_decls->ira = ira; ira_ref(ira);
        out_val->data.x_lazy = &lazy_type_info_decls->base;
        lazy_type_info_decls->base.id = LazyValueIdTypeInfoDecls;

        lazy_type_info_decls->source_node = source_node;
        lazy_type_info_decls->decls_scope = decls_scope;

        return ErrorNone;
    }

    ZigType *type_info_declaration_data_type = ir_type_info_get_type(ira, "Data", type_info_declaration_type);
    if ((err = type_resolve(ira->codegen, type_info_declaration_data_type, ResolveStatusSizeKnown)))
        return err;

    ZigType *type_info_fn_decl_type = ir_type_info_get_type(ira, "FnDecl", type_info_declaration_data_type);
    if ((err = type_resolve(ira->codegen, type_info_fn_decl_type, ResolveStatusSizeKnown)))
        return err;

    resolve_container_usingnamespace_decls(ira->codegen, decls_scope);

    // The unresolved declarations are collected in a separate queue to avoid
    // modifying decl_table while iterating over it
    ZigList<Tld*> resolve_decl_queue{};

    auto decl_it = decls_scope->decl_table.entry_iterator();
    decltype(decls_scope->decl_table)::Entry *curr_entry = nullptr;
    while ((curr_entry = decl_it.next()) != nullptr) {
        if (curr_entry->value->resolution == TldResolutionInvalid) {
            return ErrorSemanticAnalyzeFail;
        }

        if (curr_entry->value->resolution == TldResolutionResolving) {
            ir_error_dependency_loop(ira, source_node);
            return ErrorSemanticAnalyzeFail;
        }

        // If the declaration is unresolved, force it to be resolved again.
        if (curr_entry->value->resolution == TldResolutionUnresolved)
            resolve_decl_queue.append(curr_entry->value);
    }

    for (size_t i = 0; i < resolve_decl_queue.length; i++) {
        Tld *decl = resolve_decl_queue.at(i);
        resolve_top_level_decl(ira->codegen, decl, decl->source_node, false);
        if (decl->resolution == TldResolutionInvalid) {
            return ErrorSemanticAnalyzeFail;
        }
    }

    resolve_decl_queue.deinit();

    // Loop through our declarations once to figure out how many declarations we will generate info for.
    int declaration_count = 0;
    decl_it = decls_scope->decl_table.entry_iterator();
    while ((curr_entry = decl_it.next()) != nullptr) {
        // Skip comptime blocks and test functions.
        if (curr_entry->value->id == TldIdCompTime)
            continue;

        if (curr_entry->value->id == TldIdFn) {
            ZigFn *fn_entry = ((TldFn *)curr_entry->value)->fn_entry;
            if (fn_entry->is_test)
                continue;
        }

        declaration_count += 1;
    }

    ZigValue *declaration_array = ira->codegen->pass1_arena->create<ZigValue>();
    declaration_array->special = ConstValSpecialStatic;
    declaration_array->type = get_array_type(ira->codegen, type_info_declaration_type, declaration_count, nullptr);
    declaration_array->data.x_array.special = ConstArraySpecialNone;
    declaration_array->data.x_array.data.s_none.elements = ira->codegen->pass1_arena->allocate<ZigValue>(declaration_count);
    init_const_slice(ira->codegen, out_val, declaration_array, 0, declaration_count, false, nullptr);

    // Loop through the declarations and generate info.
    decl_it = decls_scope->decl_table.entry_iterator();
    curr_entry = nullptr;
    int declaration_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;
        }

        ZigValue *declaration_val = &declaration_array->data.x_array.data.s_none.elements[declaration_index];

        declaration_val->special = ConstValSpecialStatic;
        declaration_val->type = type_info_declaration_type;

        ZigValue **inner_fields = alloc_const_vals_ptrs(ira->codegen, 3);
        ZigValue *name = create_const_str_lit(ira->codegen, curr_entry->key)->data.x_ptr.data.ref.pointee;
        init_const_slice(ira->codegen, inner_fields[0], name, 0, buf_len(curr_entry->key), true, nullptr);
        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_declaration_data_type;
        inner_fields[2]->parent.id = ConstParentIdStruct;
        inner_fields[2]->parent.data.p_struct.struct_val = declaration_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;
                    assert(var != nullptr);

                    if ((err = type_resolve(ira->codegen, var->const_value->type, ResolveStatusSizeKnown)))
                        return ErrorSemanticAnalyzeFail;

                    if (var->const_value->type->id == ZigTypeIdMetaType) {
                        // We have a variable of type 'type', so it's actually a type declaration.
                        // 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);

                        ZigValue *payload = ira->codegen->pass1_arena->create<ZigValue>();
                        payload->special = ConstValSpecialStatic;
                        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.FnDecl
                    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);
                    assert(fn_entry->type_entry != nullptr);

                    AstNodeFnProto *fn_node = &fn_entry->proto_node->data.fn_proto;

                    ZigValue *fn_decl_val = ira->codegen->pass1_arena->create<ZigValue>();
                    fn_decl_val->special = ConstValSpecialStatic;
                    fn_decl_val->type = type_info_fn_decl_type;
                    fn_decl_val->parent.id = ConstParentIdUnion;
                    fn_decl_val->parent.data.p_union.union_val = inner_fields[2];

                    ZigValue **fn_decl_fields = alloc_const_vals_ptrs(ira->codegen, 9);
                    fn_decl_val->data.x_struct.fields = fn_decl_fields;

                    // fn_type: type
                    ensure_field_index(fn_decl_val->type, "fn_type", 0);
                    fn_decl_fields[0]->special = ConstValSpecialStatic;
                    fn_decl_fields[0]->type = ira->codegen->builtin_types.entry_type;
                    fn_decl_fields[0]->data.x_type = fn_entry->type_entry;
                    // is_noinline: bool
                    ensure_field_index(fn_decl_val->type, "is_noinline", 1);
                    fn_decl_fields[1]->special = ConstValSpecialStatic;
                    fn_decl_fields[1]->type = ira->codegen->builtin_types.entry_bool;
                    fn_decl_fields[1]->data.x_bool = fn_entry->is_noinline;
                    // is_var_args: bool
                    ensure_field_index(fn_decl_val->type, "is_var_args", 2);
                    bool is_varargs = fn_node->is_var_args;
                    fn_decl_fields[2]->special = ConstValSpecialStatic;
                    fn_decl_fields[2]->type = ira->codegen->builtin_types.entry_bool;
                    fn_decl_fields[2]->data.x_bool = is_varargs;
                    // is_extern: bool
                    ensure_field_index(fn_decl_val->type, "is_extern", 3);
                    fn_decl_fields[3]->special = ConstValSpecialStatic;
                    fn_decl_fields[3]->type = ira->codegen->builtin_types.entry_bool;
                    fn_decl_fields[3]->data.x_bool = fn_node->is_extern;
                    // is_export: bool
                    ensure_field_index(fn_decl_val->type, "is_export", 4);
                    fn_decl_fields[4]->special = ConstValSpecialStatic;
                    fn_decl_fields[4]->type = ira->codegen->builtin_types.entry_bool;
                    fn_decl_fields[4]->data.x_bool = fn_node->is_export;
                    // lib_name: ?[]const u8
                    ensure_field_index(fn_decl_val->type, "lib_name", 5);
                    fn_decl_fields[5]->special = ConstValSpecialStatic;
                    ZigType *u8_ptr = get_pointer_to_type_extra(
                        ira->codegen, ira->codegen->builtin_types.entry_u8,
                        true, false, PtrLenUnknown,
                        0, 0, 0, false);
                    fn_decl_fields[5]->type = get_optional_type(ira->codegen, get_slice_type(ira->codegen, u8_ptr));
                    if (fn_node->is_extern && fn_node->lib_name != nullptr && buf_len(fn_node->lib_name) > 0) {
                        ZigValue *slice_val = ira->codegen->pass1_arena->create<ZigValue>();
                        ZigValue *lib_name = create_const_str_lit(ira->codegen, fn_node->lib_name)->data.x_ptr.data.ref.pointee;
                        init_const_slice(ira->codegen, slice_val, lib_name, 0, buf_len(fn_node->lib_name), true, nullptr);
                        set_optional_payload(fn_decl_fields[5], slice_val);
                    } else {
                        set_optional_payload(fn_decl_fields[5], nullptr);
                    }
                    // return_type: type
                    ensure_field_index(fn_decl_val->type, "return_type", 6);
                    fn_decl_fields[6]->special = ConstValSpecialStatic;
                    fn_decl_fields[6]->type = ira->codegen->builtin_types.entry_type;
                    fn_decl_fields[6]->data.x_type = fn_entry->type_entry->data.fn.fn_type_id.return_type;
                    // arg_names: [][] const u8
                    ensure_field_index(fn_decl_val->type, "arg_names", 7);
                    size_t fn_arg_count = fn_entry->variable_list.length;
                    ZigValue *fn_arg_name_array = ira->codegen->pass1_arena->create<ZigValue>();
                    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, nullptr);
                    fn_arg_name_array->data.x_array.special = ConstArraySpecialNone;
                    fn_arg_name_array->data.x_array.data.s_none.elements = ira->codegen->pass1_arena->allocate<ZigValue>(fn_arg_count);

                    init_const_slice(ira->codegen, fn_decl_fields[7], fn_arg_name_array, 0, fn_arg_count, false, nullptr);

                    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);
                        ZigValue *fn_arg_name_val = &fn_arg_name_array->data.x_array.data.s_none.elements[fn_arg_index];
                        ZigValue *arg_name = create_const_str_lit(ira->codegen,
                                buf_create_from_str(arg_var->name))->data.x_ptr.data.ref.pointee;
                        init_const_slice(ira->codegen, fn_arg_name_val, arg_name, 0, strlen(arg_var->name), true, nullptr);
                        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_decl_val;
                    break;
                }
            default:
                zig_unreachable();
        }

        declaration_val->data.x_struct.fields = inner_fields;
        declaration_index += 1;
    }

    assert(declaration_index == declaration_count);
    return ErrorNone;
}

static BuiltinPtrSize ptr_len_to_size_enum_index(PtrLen ptr_len) {
    switch (ptr_len) {
        case PtrLenSingle:
            return BuiltinPtrSizeOne;
        case PtrLenUnknown:
            return BuiltinPtrSizeMany;
        case PtrLenC:
            return BuiltinPtrSizeC;
    }
    zig_unreachable();
}

static PtrLen size_enum_index_to_ptr_len(BuiltinPtrSize size_enum_index) {
    switch (size_enum_index) {
        case BuiltinPtrSizeOne:
            return PtrLenSingle;
        case BuiltinPtrSizeMany:
        case BuiltinPtrSizeSlice:
            return PtrLenUnknown;
        case BuiltinPtrSizeC:
            return PtrLenC;
    }
    zig_unreachable();
}

static ZigValue *create_ptr_like_type_info(IrAnalyze *ira, Scope *scope, AstNode *source_node, ZigType *ptr_type_entry) {
    ZigType *attrs_type;
    BuiltinPtrSize size_enum_index;
    if (is_slice(ptr_type_entry)) {
        TypeStructField *ptr_field = ptr_type_entry->data.structure.fields[slice_ptr_index];
        attrs_type = resolve_struct_field_type(ira->codegen, ptr_field);
        size_enum_index = BuiltinPtrSizeSlice;
    } 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();
    }

    ZigType *type_info_pointer_type = ir_type_info_get_type(ira, "Pointer", nullptr);
    assertNoError(type_resolve(ira->codegen, type_info_pointer_type, ResolveStatusSizeKnown));

    ZigValue *result = ira->codegen->pass1_arena->create<ZigValue>();
    result->special = ConstValSpecialStatic;
    result->type = type_info_pointer_type;

    ZigValue **fields = alloc_const_vals_ptrs(ira->codegen, 8);
    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(type_resolve(ira->codegen, type_info_pointer_size_type, ResolveStatusSizeKnown));
    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: comptime_int
    ensure_field_index(result->type, "alignment", 3);
    fields[3]->type = ira->codegen->builtin_types.entry_num_lit_int;
    if (attrs_type->data.pointer.explicit_alignment != 0) {
        fields[3]->special = ConstValSpecialStatic;
        bigint_init_unsigned(&fields[3]->data.x_bigint, attrs_type->data.pointer.explicit_alignment);
    } else {
        LazyValueAlignOf *lazy_align_of = heap::c_allocator.create<LazyValueAlignOf>();
        lazy_align_of->ira = ira; ira_ref(ira);
        fields[3]->special = ConstValSpecialLazy;
        fields[3]->data.x_lazy = &lazy_align_of->base;
        lazy_align_of->base.id = LazyValueIdAlignOf;
        lazy_align_of->target_type = ir_const_type(ira, scope, source_node, attrs_type->data.pointer.child_type);
    }
    // address_space: AddressSpace,
    ensure_field_index(result->type, "address_space", 4);
    fields[4]->special = ConstValSpecialStatic;
    fields[4]->type = get_builtin_type(ira->codegen, "AddressSpace");
    bigint_init_unsigned(&fields[4]->data.x_enum_tag, AddressSpaceGeneric);
    // child: type
    ensure_field_index(result->type, "child", 5);
    fields[5]->special = ConstValSpecialStatic;
    fields[5]->type = ira->codegen->builtin_types.entry_type;
    fields[5]->data.x_type = attrs_type->data.pointer.child_type;
    // is_allowzero: bool
    ensure_field_index(result->type, "is_allowzero", 6);
    fields[6]->special = ConstValSpecialStatic;
    fields[6]->type = ira->codegen->builtin_types.entry_bool;
    fields[6]->data.x_bool = attrs_type->data.pointer.allow_zero;
    // sentinel: anytype
    ensure_field_index(result->type, "sentinel", 7);
    fields[7]->special = ConstValSpecialStatic;
    if (attrs_type->data.pointer.sentinel != nullptr) {
        fields[7]->type = get_optional_type(ira->codegen, attrs_type->data.pointer.child_type);
        set_optional_payload(fields[7], attrs_type->data.pointer.sentinel);
    } else {
        fields[7]->type = ira->codegen->builtin_types.entry_null;
    }

    return result;
};

static void make_enum_field_val(IrAnalyze *ira, ZigValue *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;

    ZigValue **inner_fields = alloc_const_vals_ptrs(ira->codegen, 2);
    inner_fields[1]->special = ConstValSpecialStatic;
    inner_fields[1]->type = ira->codegen->builtin_types.entry_num_lit_int;

    ZigValue *name = create_const_str_lit(ira->codegen, enum_field->name)->data.x_ptr.data.ref.pointee;
    init_const_slice(ira->codegen, inner_fields[0], name, 0, buf_len(enum_field->name), true, nullptr);

    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, Scope *scope, AstNode *source_node, ZigType *type_entry,
        ZigValue **out)
{
    Error err;
    assert(type_entry != nullptr);
    assert(!type_is_invalid(type_entry));

    auto entry = ira->codegen->type_info_cache.maybe_get(type_entry);
    if (entry != nullptr) {
        *out = entry->value;
        return ErrorNone;
    }

    ZigValue *result = nullptr;
    switch (type_entry->id) {
        case ZigTypeIdInvalid:
            zig_unreachable();
        case ZigTypeIdMetaType:
        case ZigTypeIdVoid:
        case ZigTypeIdBool:
        case ZigTypeIdUnreachable:
        case ZigTypeIdComptimeFloat:
        case ZigTypeIdComptimeInt:
        case ZigTypeIdEnumLiteral:
        case ZigTypeIdUndefined:
        case ZigTypeIdNull:
            result = ira->codegen->intern.for_void();
            break;
        case ZigTypeIdInt:
            {
                result = ira->codegen->pass1_arena->create<ZigValue>();
                result->special = ConstValSpecialStatic;
                result->type = ir_type_info_get_type(ira, "Int", nullptr);

                ZigValue **fields = alloc_const_vals_ptrs(ira->codegen, 2);
                result->data.x_struct.fields = fields;

                // is_signed: Signedness
                ensure_field_index(result->type, "signedness", 0);
                fields[0]->special = ConstValSpecialStatic;
                fields[0]->type = get_builtin_type(ira->codegen, "Signedness");
                bigint_init_unsigned(&fields[0]->data.x_enum_tag, !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_num_lit_int;
                bigint_init_unsigned(&fields[1]->data.x_bigint, type_entry->data.integral.bit_count);

                break;
            }
        case ZigTypeIdFloat:
            {
                result = ira->codegen->pass1_arena->create<ZigValue>();
                result->special = ConstValSpecialStatic;
                result->type = ir_type_info_get_type(ira, "Float", nullptr);

                ZigValue **fields = alloc_const_vals_ptrs(ira->codegen, 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_num_lit_int;
                bigint_init_unsigned(&fields[0]->data.x_bigint, type_entry->data.floating.bit_count);

                break;
            }
        case ZigTypeIdPointer:
            {
                result = create_ptr_like_type_info(ira, scope, source_node, type_entry);
                if (result == nullptr)
                    return ErrorSemanticAnalyzeFail;
                break;
            }
        case ZigTypeIdArray:
            {
                result = ira->codegen->pass1_arena->create<ZigValue>();
                result->special = ConstValSpecialStatic;
                result->type = ir_type_info_get_type(ira, "Array", nullptr);

                ZigValue **fields = alloc_const_vals_ptrs(ira->codegen, 3);
                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_num_lit_int;
                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;
                // sentinel: anytype
                fields[2]->special = ConstValSpecialStatic;
                if (type_entry->data.array.child_type != nullptr) {
                    fields[2]->type = get_optional_type(ira->codegen, type_entry->data.array.child_type);
                    set_optional_payload(fields[2], type_entry->data.array.sentinel);
                } else {
                    fields[2]->type = ira->codegen->builtin_types.entry_null;
                }
                break;
            }
        case ZigTypeIdVector: {
            result = ira->codegen->pass1_arena->create<ZigValue>();
            result->special = ConstValSpecialStatic;
            result->type = ir_type_info_get_type(ira, "Vector", nullptr);

            ZigValue **fields = alloc_const_vals_ptrs(ira->codegen, 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_num_lit_int;
            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 = ira->codegen->pass1_arena->create<ZigValue>();
                result->special = ConstValSpecialStatic;
                result->type = ir_type_info_get_type(ira, "Optional", nullptr);

                ZigValue **fields = alloc_const_vals_ptrs(ira->codegen, 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 ZigTypeIdAnyFrame: {
            result = ira->codegen->pass1_arena->create<ZigValue>();
            result->special = ConstValSpecialStatic;
            result->type = ir_type_info_get_type(ira, "AnyFrame", nullptr);

            ZigValue **fields = alloc_const_vals_ptrs(ira->codegen, 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);
            fields[0]->data.x_optional = (type_entry->data.any_frame.result_type == nullptr) ? nullptr :
                create_const_type(ira->codegen, type_entry->data.any_frame.result_type);
            break;
        }
        case ZigTypeIdEnum:
            {
                if ((err = type_resolve(ira->codegen, type_entry, ResolveStatusSizeKnown)))
                    return err;

                result = ira->codegen->pass1_arena->create<ZigValue>();
                result->special = ConstValSpecialStatic;
                result->type = ir_type_info_get_type(ira, "Enum", nullptr);

                ZigValue **fields = alloc_const_vals_ptrs(ira->codegen, 5);
                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);
                if ((err = type_resolve(ira->codegen, type_info_enum_field_type, ResolveStatusSizeKnown))) {
                    zig_unreachable();
                }
                uint32_t enum_field_count = type_entry->data.enumeration.src_field_count;

                ZigValue *enum_field_array = ira->codegen->pass1_arena->create<ZigValue>();
                enum_field_array->special = ConstValSpecialStatic;
                enum_field_array->type = get_array_type(ira->codegen, type_info_enum_field_type, enum_field_count, nullptr);
                enum_field_array->data.x_array.special = ConstArraySpecialNone;
                enum_field_array->data.x_array.data.s_none.elements = ira->codegen->pass1_arena->allocate<ZigValue>(enum_field_count);

                init_const_slice(ira->codegen, fields[2], enum_field_array, 0, enum_field_count, false, nullptr);

                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];
                    ZigValue *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;
                }
                // decls: []TypeInfo.Declaration
                ensure_field_index(result->type, "decls", 3);
                if ((err = ir_make_type_info_decls(ira, source_node, fields[3],
                            type_entry->data.enumeration.decls_scope, false)))
                {
                    return err;
                }
                // is_exhaustive: bool
                ensure_field_index(result->type, "is_exhaustive", 4);
                fields[4]->special = ConstValSpecialStatic;
                fields[4]->type = ira->codegen->builtin_types.entry_bool;
                fields[4]->data.x_bool = !type_entry->data.enumeration.non_exhaustive;

                break;
            }
        case ZigTypeIdErrorSet:
            {
                result = ira->codegen->pass1_arena->create<ZigValue>();
                result->special = ConstValSpecialStatic;
                result->type = ir_type_info_get_type(ira, "ErrorSet", nullptr);

                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)) {
                    result->data.x_optional = nullptr;
                    break;
                }
                if ((err = type_resolve(ira->codegen, type_info_error_type, ResolveStatusSizeKnown))) {
                    zig_unreachable();
                }
                ZigValue *slice_val = ira->codegen->pass1_arena->create<ZigValue>();
                result->data.x_optional = slice_val;

                uint32_t error_count = type_entry->data.error_set.err_count;
                ZigValue *error_array = ira->codegen->pass1_arena->create<ZigValue>();
                error_array->special = ConstValSpecialStatic;
                error_array->type = get_array_type(ira->codegen, type_info_error_type, error_count, nullptr);
                error_array->data.x_array.special = ConstArraySpecialNone;
                error_array->data.x_array.data.s_none.elements = ira->codegen->pass1_arena->allocate<ZigValue>(error_count);

                init_const_slice(ira->codegen, slice_val, error_array, 0, error_count, false, nullptr);
                for (uint32_t error_index = 0; error_index < error_count; error_index++) {
                    ErrorTableEntry *error = type_entry->data.error_set.errors[error_index];
                    ZigValue *error_val = &error_array->data.x_array.data.s_none.elements[error_index];

                    error_val->special = ConstValSpecialStatic;
                    error_val->type = type_info_error_type;

                    ZigValue **inner_fields = alloc_const_vals_ptrs(ira->codegen, 1);

                    ZigValue *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)->data.x_ptr.data.ref.pointee;
                    init_const_slice(ira->codegen, inner_fields[0], name, 0, buf_len(&error->name), true, nullptr);

                    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 = ira->codegen->pass1_arena->create<ZigValue>();
                result->special = ConstValSpecialStatic;
                result->type = ir_type_info_get_type(ira, "ErrorUnion", nullptr);

                ZigValue **fields = alloc_const_vals_ptrs(ira->codegen, 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:
            {
                if ((err = type_resolve(ira->codegen, type_entry, ResolveStatusSizeKnown)))
                    return err;

                result = ira->codegen->pass1_arena->create<ZigValue>();
                result->special = ConstValSpecialStatic;
                result->type = ir_type_info_get_type(ira, "Union", nullptr);

                ZigValue **fields = alloc_const_vals_ptrs(ira->codegen, 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)
                {
                    ZigValue *tag_type = ira->codegen->pass1_arena->create<ZigValue>();
                    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);
                if ((err = type_resolve(ira->codegen, type_info_union_field_type, ResolveStatusSizeKnown)))
                    zig_unreachable();
                uint32_t union_field_count = type_entry->data.unionation.src_field_count;

                ZigValue *union_field_array = ira->codegen->pass1_arena->create<ZigValue>();
                union_field_array->special = ConstValSpecialStatic;
                union_field_array->type = get_array_type(ira->codegen, type_info_union_field_type, union_field_count, nullptr);
                union_field_array->data.x_array.special = ConstArraySpecialNone;
                union_field_array->data.x_array.data.s_none.elements = ira->codegen->pass1_arena->allocate<ZigValue>(union_field_count);

                init_const_slice(ira->codegen, fields[2], union_field_array, 0, union_field_count, false, 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];
                    ZigValue *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;

                    ZigValue **inner_fields = alloc_const_vals_ptrs(ira->codegen, 3);
                    // field_type: type
                    inner_fields[1]->special = ConstValSpecialStatic;
                    inner_fields[1]->type = ira->codegen->builtin_types.entry_type;
                    inner_fields[1]->data.x_type = union_field->type_entry;

                    // alignment: comptime_int
                    inner_fields[2]->special = ConstValSpecialStatic;
                    inner_fields[2]->type = ira->codegen->builtin_types.entry_num_lit_int;
                    bigint_init_unsigned(&inner_fields[2]->data.x_bigint, union_field->align);

                    ZigValue *name = create_const_str_lit(ira->codegen, union_field->name)->data.x_ptr.data.ref.pointee;
                    init_const_slice(ira->codegen, inner_fields[0], name, 0, buf_len(union_field->name), true, nullptr);

                    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;
                }
                // decls: []TypeInfo.Declaration
                ensure_field_index(result->type, "decls", 3);
                if ((err = ir_make_type_info_decls(ira, source_node, fields[3],
                                type_entry->data.unionation.decls_scope, false)))
                {
                    return err;
                }

                break;
            }
        case ZigTypeIdStruct:
            {
                if (type_entry->data.structure.special == StructSpecialSlice) {
                    result = create_ptr_like_type_info(ira, scope, source_node, type_entry);
                    if (result == nullptr)
                        return ErrorSemanticAnalyzeFail;
                    break;
                }

                if ((err = type_resolve(ira->codegen, type_entry, ResolveStatusSizeKnown)))
                    return err;

                result = ira->codegen->pass1_arena->create<ZigValue>();
                result->special = ConstValSpecialStatic;
                result->type = ir_type_info_get_type(ira, "Struct", nullptr);

                ZigValue **fields = alloc_const_vals_ptrs(ira->codegen, 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.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);
                if ((err = type_resolve(ira->codegen, type_info_struct_field_type, ResolveStatusSizeKnown))) {
                    zig_unreachable();
                }
                uint32_t struct_field_count = type_entry->data.structure.src_field_count;

                ZigValue *struct_field_array = ira->codegen->pass1_arena->create<ZigValue>();
                struct_field_array->special = ConstValSpecialStatic;
                struct_field_array->type = get_array_type(ira->codegen, type_info_struct_field_type, struct_field_count, nullptr);
                struct_field_array->data.x_array.special = ConstArraySpecialNone;
                struct_field_array->data.x_array.data.s_none.elements = ira->codegen->pass1_arena->allocate<ZigValue>(struct_field_count);

                init_const_slice(ira->codegen, fields[1], struct_field_array, 0, struct_field_count, false, nullptr);

                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];
                    ZigValue *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;

                    ZigValue **inner_fields = alloc_const_vals_ptrs(ira->codegen, 5);

                    inner_fields[1]->special = ConstValSpecialStatic;
                    inner_fields[1]->type = ira->codegen->builtin_types.entry_type;
                    inner_fields[1]->data.x_type = struct_field->type_entry;

                    // default_value: anytype
                    inner_fields[2]->special = ConstValSpecialStatic;
                    inner_fields[2]->type = get_optional_type2(ira->codegen, struct_field->type_entry);
                    if (inner_fields[2]->type == nullptr) return ErrorSemanticAnalyzeFail;
                    memoize_field_init_val(ira->codegen, type_entry, struct_field);
                    if(struct_field->init_val != nullptr && type_is_invalid(struct_field->init_val->type)){
                        return ErrorSemanticAnalyzeFail;
                    }
                    set_optional_payload(inner_fields[2], struct_field->init_val);

                    // is_comptime: bool
                    inner_fields[3]->special = ConstValSpecialStatic;
                    inner_fields[3]->type = ira->codegen->builtin_types.entry_bool;
                    inner_fields[3]->data.x_bool = struct_field->is_comptime;

                    // alignment: comptime_int
                    inner_fields[4]->special = ConstValSpecialStatic;
                    inner_fields[4]->type = ira->codegen->builtin_types.entry_num_lit_int;
                    bigint_init_unsigned(&inner_fields[4]->data.x_bigint, struct_field->align);

                    ZigValue *name = create_const_str_lit(ira->codegen, struct_field->name)->data.x_ptr.data.ref.pointee;
                    init_const_slice(ira->codegen, inner_fields[0], name, 0, buf_len(struct_field->name), true, nullptr);

                    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;
                }
                // decls: []TypeInfo.Declaration
                ensure_field_index(result->type, "decls", 2);
                if ((err = ir_make_type_info_decls(ira, source_node, fields[2],
                                type_entry->data.structure.decls_scope, false)))
                {
                    return err;
                }

                // is_tuple: bool
                ensure_field_index(result->type, "is_tuple", 3);
                fields[3]->special = ConstValSpecialStatic;
                fields[3]->type = ira->codegen->builtin_types.entry_bool;
                fields[3]->data.x_bool = is_tuple(type_entry);

                break;
            }
        case ZigTypeIdFn:
            {
                result = ira->codegen->pass1_arena->create<ZigValue>();
                result->special = ConstValSpecialStatic;
                result->type = ir_type_info_get_type(ira, "Fn", nullptr);

                ZigValue **fields = alloc_const_vals_ptrs(ira->codegen, 7);
                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 = get_builtin_type(ira->codegen, "CallingConvention");
                bigint_init_unsigned(&fields[0]->data.x_enum_tag, type_entry->data.fn.fn_type_id.cc);
                // alignment: comptime_int
                ensure_field_index(result->type, "alignment", 1);
                fields[1]->special = ConstValSpecialStatic;
                fields[1]->type = ira->codegen->builtin_types.entry_num_lit_int;
                bigint_init_unsigned(&fields[1]->data.x_bigint, get_ptr_align(ira->codegen, type_entry));
                // is_generic: bool
                ensure_field_index(result->type, "is_generic", 2);
                bool is_generic = type_entry->data.fn.is_generic;
                fields[2]->special = ConstValSpecialStatic;
                fields[2]->type = ira->codegen->builtin_types.entry_bool;
                fields[2]->data.x_bool = is_generic;
                // is_varargs: bool
                ensure_field_index(result->type, "is_var_args", 3);
                bool is_varargs = type_entry->data.fn.fn_type_id.is_var_args;
                fields[3]->special = ConstValSpecialStatic;
                fields[3]->type = ira->codegen->builtin_types.entry_bool;
                fields[3]->data.x_bool = is_varargs;
                // return_type: ?type
                ensure_field_index(result->type, "return_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.return_type == nullptr)
                    fields[4]->data.x_optional = nullptr;
                else {
                    ZigValue *return_type = ira->codegen->pass1_arena->create<ZigValue>();
                    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[4]->data.x_optional = return_type;
                }
                // args: []TypeInfo.FnArg
                ZigType *type_info_fn_arg_type = ir_type_info_get_type(ira, "FnArg", nullptr);
                if ((err = type_resolve(ira->codegen, type_info_fn_arg_type, ResolveStatusSizeKnown))) {
                    zig_unreachable();
                }
                size_t fn_arg_count = type_entry->data.fn.fn_type_id.param_count;

                ZigValue *fn_arg_array = ira->codegen->pass1_arena->create<ZigValue>();
                fn_arg_array->special = ConstValSpecialStatic;
                fn_arg_array->type = get_array_type(ira->codegen, type_info_fn_arg_type, fn_arg_count, nullptr);
                fn_arg_array->data.x_array.special = ConstArraySpecialNone;
                fn_arg_array->data.x_array.data.s_none.elements = ira->codegen->pass1_arena->allocate<ZigValue>(fn_arg_count);

                init_const_slice(ira->codegen, fields[5], fn_arg_array, 0, fn_arg_count, false, nullptr);

                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];
                    ZigValue *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);

                    ZigValue **inner_fields = alloc_const_vals_ptrs(ira->codegen, 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 {
                        ZigValue *arg_type = ira->codegen->pass1_arena->create<ZigValue>();
                        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, scope, source_node, fn_type, &result)))
                    return err;

                break;
            }
        case ZigTypeIdOpaque:
            {
                result = ira->codegen->pass1_arena->create<ZigValue>();
                result->special = ConstValSpecialStatic;
                result->type = ir_type_info_get_type(ira, "Opaque", nullptr);

                ZigValue **fields = alloc_const_vals_ptrs(ira->codegen, 1);
                result->data.x_struct.fields = fields;

                // decls: []TypeInfo.Declaration
                ensure_field_index(result->type, "decls", 0);
                if ((err = ir_make_type_info_decls(ira, source_node, fields[0],
                            type_entry->data.opaque.decls_scope, false)))
                {
                    return err;
                }

                break;
            }
        case ZigTypeIdFnFrame:
            {
                result = ira->codegen->pass1_arena->create<ZigValue>();
                result->special = ConstValSpecialStatic;
                result->type = ir_type_info_get_type(ira, "Frame", nullptr);
                ZigValue **fields = alloc_const_vals_ptrs(ira->codegen, 1);
                result->data.x_struct.fields = fields;
                ZigFn *fn = type_entry->data.frame.fn;
                // function: anytype
                ensure_field_index(result->type, "function", 0);
                fields[0] = create_const_fn(ira->codegen, fn);
                break;
            }
    }

    assert(result != nullptr);
    ira->codegen->type_info_cache.put(type_entry, result);
    *out = result;
    return ErrorNone;
}

static Stage1AirInst *ir_analyze_instruction_type_info(IrAnalyze *ira, Stage1ZirInstTypeInfo *instruction) {
    Error err;
    Stage1AirInst *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_inst_gen;

    ZigType *result_type = ir_type_info_get_type(ira, nullptr, nullptr);

    ZigValue *payload;
    if ((err = ir_make_type_info_value(ira, instruction->base.scope, instruction->base.source_node, type_entry, &payload)))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *result = ir_const(ira, instruction->base.scope, instruction->base.source_node, result_type);
    ZigValue *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) {
        payload->parent.id = ConstParentIdUnion;
        payload->parent.data.p_union.union_val = out_val;
    }

    return result;
}

static ZigValue *get_const_field(IrAnalyze *ira, AstNode *source_node, ZigValue *struct_value,
        const char *name, size_t field_index)
{
    Error err;
    ensure_field_index(struct_value->type, name, field_index);
    TypeStructField *field = struct_value->type->data.structure.fields[field_index];
    ZigValue *val = field->is_comptime ? field->init_val :
        struct_value->data.x_struct.fields[field_index];
    if ((err = ir_resolve_const_val(ira->codegen, ira->new_irb.exec, source_node, val, UndefBad)))
        return nullptr;
    return val;
}

static Error get_const_field_sentinel(IrAnalyze *ira, Scope *scope, AstNode *source_node, ZigValue *struct_value,
        const char *name, size_t field_index, ZigType *elem_type, ZigValue **result)
{
    ZigValue *field_val = get_const_field(ira, source_node, struct_value, name, field_index);
    if (field_val == nullptr)
        return ErrorSemanticAnalyzeFail;

    Stage1AirInst *field_inst = ir_const_move(ira, scope, source_node, field_val);
    Stage1AirInst *casted_field_inst = ir_implicit_cast(ira, field_inst,
            get_optional_type(ira->codegen, elem_type));
    if (type_is_invalid(casted_field_inst->value->type))
        return ErrorSemanticAnalyzeFail;

    if (optional_value_is_null(casted_field_inst->value)) {
        *result = nullptr;
    } else {
        assert(type_has_optional_repr(casted_field_inst->value->type));
        *result = casted_field_inst->value->data.x_optional;
    }

    return ErrorNone;
}

static Error get_const_field_bool(IrAnalyze *ira, AstNode *source_node, ZigValue *struct_value,
        const char *name, size_t field_index, bool *out)
{
    ZigValue *value = get_const_field(ira, source_node, struct_value, name, field_index);
    if (value == nullptr)
        return ErrorSemanticAnalyzeFail;
    assert(value->type == ira->codegen->builtin_types.entry_bool);
    *out = value->data.x_bool;
    return ErrorNone;
}

static BigInt *get_const_field_lit_int(IrAnalyze *ira, AstNode *source_node, ZigValue *struct_value, const char *name, size_t field_index)
{
    ZigValue *value = get_const_field(ira, source_node, struct_value, name, field_index);
    if (value == nullptr)
        return nullptr;
    assert(value->type == ira->codegen->builtin_types.entry_num_lit_int);
    return &value->data.x_bigint;
}

static ZigType *get_const_field_meta_type(IrAnalyze *ira, AstNode *source_node, ZigValue *struct_value, const char *name, size_t field_index)
{
    ZigValue *value = get_const_field(ira, source_node, struct_value, name, field_index);
    if (value == nullptr)
        return ira->codegen->invalid_inst_gen->value->type;
    assert(value->type == ira->codegen->builtin_types.entry_type);
    return value->data.x_type;
}

static ZigType *get_const_field_meta_type_optional(IrAnalyze *ira, AstNode *source_node,
    ZigValue *struct_value, const char *name, size_t field_index)
{
    ZigValue *value = get_const_field(ira, source_node, struct_value, name, field_index);
    if (value == nullptr)
        return ira->codegen->invalid_inst_gen->value->type;
    assert(value->type->id == ZigTypeIdOptional);
    assert(value->type->data.maybe.child_type == ira->codegen->builtin_types.entry_type);
    if (value->data.x_optional == nullptr)
        return nullptr;
    return value->data.x_optional->data.x_type;
}

static Error get_const_field_buf(IrAnalyze *ira, AstNode *source_node, ZigValue *struct_value,
    const char *name, size_t field_index, Buf *out)
{
    ZigValue *slice = get_const_field(ira, source_node, struct_value, name, field_index);
    ZigValue *ptr = slice->data.x_struct.fields[slice_ptr_index];
    ZigValue *len = slice->data.x_struct.fields[slice_len_index];
    assert(ptr->data.x_ptr.special == ConstPtrSpecialBaseArray);
    ZigValue *arr = ptr->data.x_ptr.data.base_array.array_val;
    assert(arr->special == ConstValSpecialStatic);

    const size_t start_value = ptr->data.x_ptr.data.base_array.elem_index;
    const size_t len_value = bigint_as_usize(&len->data.x_bigint);

    switch (arr->data.x_array.special) {
        case ConstArraySpecialUndef:
            return ErrorSemanticAnalyzeFail;
        case ConstArraySpecialNone: {
            assert(start_value <= arr->type->data.array.len);
            assert(start_value + len_value <= arr->type->data.array.len);
            buf_resize(out, 0);
            for (size_t j = 0; j < len_value; j++) {
                ZigValue *ch_val = &arr->data.x_array.data.s_none.elements[start_value + j];
                unsigned ch = bigint_as_u32(&ch_val->data.x_bigint);
                buf_append_char(out, ch);
            }
            break;
        }
        case ConstArraySpecialBuf:
            assert(start_value <= buf_len(arr->data.x_array.data.s_buf));
            assert(start_value + len_value <= buf_len(arr->data.x_array.data.s_buf));
            buf_init_from_mem(out, buf_ptr(arr->data.x_array.data.s_buf) + start_value, len_value);
            break;
    }
    return ErrorNone;
}

static ZigType *type_info_to_type(IrAnalyze *ira, Scope *scope, AstNode *source_node, ZigTypeId tagTypeId, ZigValue *payload) {
    Error err;
    switch (tagTypeId) {
        case ZigTypeIdInvalid:
            zig_unreachable();
        case ZigTypeIdMetaType:
            return ira->codegen->builtin_types.entry_type;
        case ZigTypeIdVoid:
            return ira->codegen->builtin_types.entry_void;
        case ZigTypeIdBool:
            return ira->codegen->builtin_types.entry_bool;
        case ZigTypeIdUnreachable:
            return ira->codegen->builtin_types.entry_unreachable;
        case ZigTypeIdComptimeFloat:
            return ira->codegen->builtin_types.entry_num_lit_float;
        case ZigTypeIdComptimeInt:
            return ira->codegen->builtin_types.entry_num_lit_int;
        case ZigTypeIdUndefined:
            return ira->codegen->builtin_types.entry_undef;
        case ZigTypeIdNull:
            return ira->codegen->builtin_types.entry_null;
        case ZigTypeIdEnumLiteral:
            return ira->codegen->builtin_types.entry_enum_literal;
        default:
            if ((err = ir_resolve_const_val(ira->codegen, ira->new_irb.exec, source_node, payload, UndefBad)))
                return ira->codegen->invalid_inst_gen->value->type;
    }
    switch (tagTypeId) {
        case ZigTypeIdInvalid:
        case ZigTypeIdMetaType:
        case ZigTypeIdVoid:
        case ZigTypeIdBool:
        case ZigTypeIdUnreachable:
        case ZigTypeIdComptimeFloat:
        case ZigTypeIdComptimeInt:
        case ZigTypeIdUndefined:
        case ZigTypeIdNull:
        case ZigTypeIdEnumLiteral:
            zig_unreachable();
        case ZigTypeIdInt: {
            assert(payload->special == ConstValSpecialStatic);
            assert(payload->type == ir_type_info_get_type(ira, "Int", nullptr));
            BigInt *bi = get_const_field_lit_int(ira, source_node, payload, "bits", 1);
            if (bi == nullptr)
                return ira->codegen->invalid_inst_gen->value->type;
            ZigValue *value = get_const_field(ira, source_node, payload, "signedness", 0);
            if (value == nullptr)
                return ira->codegen->invalid_inst_gen->value->type;
            assert(value->type == get_builtin_type(ira->codegen, "Signedness"));
            bool is_signed = !bigint_as_u32(&value->data.x_enum_tag);
            return get_int_type(ira->codegen, is_signed, bigint_as_u32(bi));
        }
        case ZigTypeIdFloat:
            {
                assert(payload->special == ConstValSpecialStatic);
                assert(payload->type == ir_type_info_get_type(ira, "Float", nullptr));
                BigInt *bi = get_const_field_lit_int(ira, source_node, payload, "bits", 0);
                if (bi == nullptr)
                    return ira->codegen->invalid_inst_gen->value->type;
                uint32_t bits = bigint_as_u32(bi);
                switch (bits) {
                    case  16: return ira->codegen->builtin_types.entry_f16;
                    case  32: return ira->codegen->builtin_types.entry_f32;
                    case  64: return ira->codegen->builtin_types.entry_f64;
                    case 128: return ira->codegen->builtin_types.entry_f128;
                }
                ir_add_error_node(ira, source_node, buf_sprintf("%d-bit float unsupported", bits));
                return ira->codegen->invalid_inst_gen->value->type;
            }
        case ZigTypeIdPointer:
            {
                ZigType *type_info_pointer_type = ir_type_info_get_type(ira, "Pointer", nullptr);
                assert(payload->special == ConstValSpecialStatic);
                assert(payload->type == type_info_pointer_type);
                ZigValue *size_value = get_const_field(ira, source_node, payload, "size", 0);
                if (size_value == nullptr)
                    return ira->codegen->invalid_inst_gen->value->type;

                assert(size_value->type == ir_type_info_get_type(ira, "Size", type_info_pointer_type));
                BuiltinPtrSize size_enum_index = (BuiltinPtrSize)bigint_as_u32(&size_value->data.x_enum_tag);
                PtrLen ptr_len = size_enum_index_to_ptr_len(size_enum_index);
                ZigType *elem_type = get_const_field_meta_type(ira, source_node, payload, "child", 5);
                if (type_is_invalid(elem_type))
                    return ira->codegen->invalid_inst_gen->value->type;
                ZigValue *sentinel;
                if ((err = get_const_field_sentinel(ira, scope, source_node, payload, "sentinel", 7,
                                elem_type, &sentinel)))
                {
                    return ira->codegen->invalid_inst_gen->value->type;
                }
                if (sentinel != nullptr && (size_enum_index == BuiltinPtrSizeOne || size_enum_index == BuiltinPtrSizeC)) {
                    ir_add_error_node(ira, source_node,
                        buf_sprintf("sentinels are only allowed on slices and unknown-length pointers"));
                    return ira->codegen->invalid_inst_gen->value->type;
                }

                BigInt *alignment = get_const_field_lit_int(ira, source_node, payload, "alignment", 3);
                if (alignment == nullptr)
                    return ira->codegen->invalid_inst_gen->value->type;

                ZigValue *as_value = get_const_field(ira, source_node, payload, "address_space", 4);
                if (as_value == nullptr)
                    return ira->codegen->invalid_inst_gen->value->type;
                assert(as_value->special == ConstValSpecialStatic);
                assert(as_value->type == get_builtin_type(ira->codegen, "AddressSpace"));
                AddressSpace as = (AddressSpace)bigint_as_u32(&as_value->data.x_enum_tag);
                if (as != AddressSpaceGeneric) {
                    ir_add_error_node(ira, source_node, buf_sprintf(
                        "address space '%s' not available in stage 1 compiler, must be .generic",
                        address_space_name(as)));
                    return ira->codegen->invalid_inst_gen->value->type;
                }

                bool is_const;
                if ((err = get_const_field_bool(ira, source_node, payload, "is_const", 1, &is_const)))
                    return ira->codegen->invalid_inst_gen->value->type;

                bool is_volatile;
                if ((err = get_const_field_bool(ira, source_node, payload, "is_volatile", 2,
                                &is_volatile)))
                {
                    return ira->codegen->invalid_inst_gen->value->type;
                }

                bool is_allowzero;
                if ((err = get_const_field_bool(ira, source_node, payload, "is_allowzero", 6,
                                &is_allowzero)))
                {
                    return ira->codegen->invalid_inst_gen->value->type;
                }

                ZigType *ptr_type = get_pointer_to_type_extra2(ira->codegen,
                    elem_type,
                    is_const,
                    is_volatile,
                    ptr_len,
                    bigint_as_u32(alignment),
                    0, // bit_offset_in_host
                    0, // host_int_bytes
                    is_allowzero,
                    VECTOR_INDEX_NONE, nullptr, sentinel);
                if (size_enum_index != BuiltinPtrSizeSlice)
                    return ptr_type;
                return get_slice_type(ira->codegen, ptr_type);
            }
        case ZigTypeIdArray: {
            assert(payload->special == ConstValSpecialStatic);
            assert(payload->type == ir_type_info_get_type(ira, "Array", nullptr));
            ZigType *elem_type = get_const_field_meta_type(ira, source_node, payload, "child", 1);
            if (type_is_invalid(elem_type))
                return ira->codegen->invalid_inst_gen->value->type;
            ZigValue *sentinel;
            if ((err = get_const_field_sentinel(ira, scope, source_node, payload, "sentinel", 2,
                            elem_type, &sentinel)))
            {
                return ira->codegen->invalid_inst_gen->value->type;
            }
            BigInt *bi = get_const_field_lit_int(ira, source_node, payload, "len", 0);
            if (bi == nullptr)
                return ira->codegen->invalid_inst_gen->value->type;
            return get_array_type(ira->codegen, elem_type, bigint_as_u64(bi), sentinel);
        }
        case ZigTypeIdOptional: {
            assert(payload->special == ConstValSpecialStatic);
            assert(payload->type == ir_type_info_get_type(ira, "Optional", nullptr));
            ZigType *child_type = get_const_field_meta_type(ira, source_node, payload, "child", 0);
            if (type_is_invalid(child_type))
                return ira->codegen->invalid_inst_gen->value->type;
            return get_optional_type(ira->codegen, child_type);
        }
        case ZigTypeIdErrorUnion: {
            assert(payload->special == ConstValSpecialStatic);
            assert(payload->type == ir_type_info_get_type(ira, "ErrorUnion", nullptr));
            ZigType *err_set_type = get_const_field_meta_type(ira, source_node, payload, "error_set", 0);
            if (type_is_invalid(err_set_type))
                return ira->codegen->invalid_inst_gen->value->type;

            ZigType *payload_type = get_const_field_meta_type(ira, source_node, payload, "payload", 1);
            if (type_is_invalid(payload_type))
                return ira->codegen->invalid_inst_gen->value->type;

            return get_error_union_type(ira->codegen, err_set_type, payload_type);
        }
        case ZigTypeIdOpaque: {
            assert(payload->special == ConstValSpecialStatic);
            assert(payload->type == ir_type_info_get_type(ira, "Opaque", nullptr));

            ZigValue *decls_value = get_const_field(ira, source_node, payload, "decls", 0);
            if (decls_value == nullptr)
                return ira->codegen->invalid_inst_gen->value->type;
            assert(decls_value->special == ConstValSpecialStatic);
            assert(is_slice(decls_value->type));
            ZigValue *decls_len_value = decls_value->data.x_struct.fields[slice_len_index];
            size_t decls_len = bigint_as_usize(&decls_len_value->data.x_bigint);
            if (decls_len != 0) {
                ir_add_error_node(ira, source_node, buf_create_from_str("TypeInfo.Struct.decls must be empty for @Type"));
                return ira->codegen->invalid_inst_gen->value->type;
            }

            Buf *bare_name = buf_alloc();
            Buf *full_name = get_anon_type_name(ira->codegen,
                ira->zir, "opaque", scope, source_node, bare_name, nullptr);
            return get_opaque_type(ira->codegen,
                scope, source_node, buf_ptr(full_name), bare_name);
        }
        case ZigTypeIdVector: {
            assert(payload->special == ConstValSpecialStatic);
            assert(payload->type == ir_type_info_get_type(ira, "Vector", nullptr));
            BigInt *len = get_const_field_lit_int(ira, source_node, payload, "len", 0);
            if (len == nullptr)
                return ira->codegen->invalid_inst_gen->value->type;

            ZigType *child_type = get_const_field_meta_type(ira, source_node, payload, "child", 1);
            if ((err = ir_validate_vector_elem_type(ira, source_node, child_type))) {
                return ira->codegen->invalid_inst_gen->value->type;
            }
            return get_vector_type(ira->codegen, bigint_as_u32(len), child_type);
        }
        case ZigTypeIdAnyFrame: {
            assert(payload->special == ConstValSpecialStatic);
            assert(payload->type == ir_type_info_get_type(ira, "AnyFrame", nullptr));
            ZigType *child_type = get_const_field_meta_type_optional(ira, source_node, payload, "child", 0);
            if (child_type != nullptr && type_is_invalid(child_type))
                return ira->codegen->invalid_inst_gen->value->type;

            return get_any_frame_type(ira->codegen, child_type);
        }
        case ZigTypeIdFnFrame: {
            assert(payload->special == ConstValSpecialStatic);
            assert(payload->type == ir_type_info_get_type(ira, "Frame", nullptr));
            ZigValue *function = get_const_field(ira, source_node, payload, "function", 0);
            if (function == nullptr)
                return ira->codegen->invalid_inst_gen->value->type;

            assert(function->type->id == ZigTypeIdFn);
            ZigFn *fn = function->data.x_ptr.data.fn.fn_entry;
            return get_fn_frame_type(ira->codegen, fn);
        }
        case ZigTypeIdErrorSet: {
            assert(payload->special == ConstValSpecialStatic);
            assert(payload->type->id == ZigTypeIdOptional);
            ZigValue *slice = payload->data.x_optional;
            if (slice == nullptr)
                return ira->codegen->builtin_types.entry_global_error_set;
            assert(slice->special == ConstValSpecialStatic);
            assert(is_slice(slice->type));
            ZigType *err_set_type = new_type_table_entry(ZigTypeIdErrorSet);
            Buf bare_name = BUF_INIT;
            buf_init_from_buf(&err_set_type->name,
                get_anon_type_name(ira->codegen, ira->zir, "error", scope, source_node, &bare_name, nullptr));
            err_set_type->size_in_bits = ira->codegen->builtin_types.entry_global_error_set->size_in_bits;
            err_set_type->abi_align = ira->codegen->builtin_types.entry_global_error_set->abi_align;
            err_set_type->abi_size = ira->codegen->builtin_types.entry_global_error_set->abi_size;
            ZigValue *ptr = slice->data.x_struct.fields[slice_ptr_index];
            assert(ptr->data.x_ptr.special == ConstPtrSpecialBaseArray);;
            assert(ptr->data.x_ptr.data.base_array.elem_index == 0);
            ZigValue *arr = ptr->data.x_ptr.data.base_array.array_val;
            assert(arr->special == ConstValSpecialStatic);
            assert(arr->data.x_array.special == ConstArraySpecialNone);
            ZigValue *len = slice->data.x_struct.fields[slice_len_index];
            size_t count = bigint_as_usize(&len->data.x_bigint);
            err_set_type->data.error_set.err_count = count;
            err_set_type->data.error_set.errors = heap::c_allocator.allocate<ErrorTableEntry *>(count);
            bool *already_set = heap::c_allocator.allocate<bool>(ira->codegen->errors_by_index.length + count);
            for (size_t i = 0; i < count; i++) {
                ZigValue *error = &arr->data.x_array.data.s_none.elements[i];
                assert(error->type == ir_type_info_get_type(ira, "Error", nullptr));
                ErrorTableEntry *err_entry = heap::c_allocator.create<ErrorTableEntry>();
                err_entry->decl_node = source_node;
                if ((err = get_const_field_buf(ira, source_node, error, "name", 0, &err_entry->name)))
                    return ira->codegen->invalid_inst_gen->value->type;
                auto existing_entry = ira->codegen->error_table.put_unique(&err_entry->name, err_entry);
                if (existing_entry) {
                    err_entry->value = existing_entry->value->value;
                } else {
                    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);
                }
                if (already_set[err_entry->value]) {
                    ir_add_error_node(ira, source_node, buf_sprintf("duplicate error: %s", buf_ptr(&err_entry->name)));
                    return ira->codegen->invalid_inst_gen->value->type;
                } else {
                    already_set[err_entry->value] = true;
                }
                err_set_type->data.error_set.errors[i] = err_entry;
            }
            return err_set_type;
        }
        case ZigTypeIdStruct: {
            assert(payload->special == ConstValSpecialStatic);
            assert(payload->type == ir_type_info_get_type(ira, "Struct", nullptr));

            ZigValue *layout_value = get_const_field(ira, source_node, payload, "layout", 0);
            if (layout_value == nullptr)
                return ira->codegen->invalid_inst_gen->value->type;
            assert(layout_value->special == ConstValSpecialStatic);
            assert(layout_value->type == ir_type_info_get_type(ira, "ContainerLayout", nullptr));
            ContainerLayout layout = (ContainerLayout)bigint_as_u32(&layout_value->data.x_enum_tag);

            ZigValue *fields_value = get_const_field(ira, source_node, payload, "fields", 1);
            if (fields_value == nullptr)
                return ira->codegen->invalid_inst_gen->value->type;
            assert(fields_value->special == ConstValSpecialStatic);
            assert(is_slice(fields_value->type));
            ZigValue *fields_ptr = fields_value->data.x_struct.fields[slice_ptr_index];
            ZigValue *fields_len_value = fields_value->data.x_struct.fields[slice_len_index];
            size_t fields_len = bigint_as_usize(&fields_len_value->data.x_bigint);

            ZigValue *decls_value = get_const_field(ira, source_node, payload, "decls", 2);
            if (decls_value == nullptr)
                return ira->codegen->invalid_inst_gen->value->type;
            assert(decls_value->special == ConstValSpecialStatic);
            assert(is_slice(decls_value->type));
            ZigValue *decls_len_value = decls_value->data.x_struct.fields[slice_len_index];
            size_t decls_len = bigint_as_usize(&decls_len_value->data.x_bigint);
            if (decls_len != 0) {
                ir_add_error_node(ira, source_node, buf_create_from_str("TypeInfo.Struct.decls must be empty for @Type"));
                return ira->codegen->invalid_inst_gen->value->type;
            }

            bool is_tuple;
            if ((err = get_const_field_bool(ira, source_node, payload, "is_tuple", 3, &is_tuple)))
                return ira->codegen->invalid_inst_gen->value->type;

            ZigType *entry = new_type_table_entry(ZigTypeIdStruct);
            buf_init_from_buf(&entry->name,
                get_anon_type_name(ira->codegen, ira->zir, "struct", scope, source_node, &entry->name, nullptr));
            entry->data.structure.decl_node = source_node;
            entry->data.structure.fields = alloc_type_struct_fields(fields_len);
            entry->data.structure.fields_by_name.init(fields_len);
            entry->data.structure.src_field_count = fields_len;
            entry->data.structure.layout = layout;
            entry->data.structure.special = is_tuple ? StructSpecialInferredTuple : StructSpecialNone;
            entry->data.structure.created_by_at_type = true;
            entry->data.structure.decls_scope = create_decls_scope(
                ira->codegen, source_node, scope, entry, get_scope_import(scope), &entry->name);

            assert(fields_ptr->data.x_ptr.special == ConstPtrSpecialBaseArray);
            assert(fields_ptr->data.x_ptr.data.base_array.elem_index == 0);
            ZigValue *fields_arr = fields_ptr->data.x_ptr.data.base_array.array_val;
            assert(fields_arr->special == ConstValSpecialStatic);
            assert(fields_arr->data.x_array.special == ConstArraySpecialNone);
            for (size_t i = 0; i < fields_len; i++) {
                ZigValue *field_value = &fields_arr->data.x_array.data.s_none.elements[i];
                assert(field_value->type == ir_type_info_get_type(ira, "StructField", nullptr));
                TypeStructField *field = entry->data.structure.fields[i];
                field->name = buf_alloc();
                if ((err = get_const_field_buf(ira, source_node, field_value, "name", 0, field->name)))
                    return ira->codegen->invalid_inst_gen->value->type;
                field->decl_node = source_node;
                ZigValue *type_value = get_const_field(ira, source_node, field_value, "field_type", 1);
                if (type_value == nullptr)
                    return ira->codegen->invalid_inst_gen->value->type;
                field->type_val = type_value;
                field->type_entry = type_value->data.x_type;
                if (entry->data.structure.fields_by_name.put_unique(field->name, field) != nullptr) {
                    ir_add_error_node(ira, source_node, buf_sprintf("duplicate struct field '%s'", buf_ptr(field->name)));
                    return ira->codegen->invalid_inst_gen->value->type;
                }
                ZigValue *default_value = get_const_field(ira, source_node, field_value, "default_value", 2);
                if (default_value == nullptr)
                    return ira->codegen->invalid_inst_gen->value->type;
                if (default_value->type->id == ZigTypeIdNull) {
                    field->init_val = nullptr;
                } else if (default_value->type->id == ZigTypeIdOptional && default_value->type->data.maybe.child_type == field->type_entry) {
                    field->init_val = default_value->data.x_optional;
                } else if (default_value->type == field->type_entry) {
                    field->init_val = default_value;
                } else {
                    ir_add_error_node(ira, source_node,
                        buf_sprintf("default_value of field '%s' is of type '%s', expected '%s' or '?%s'",
                            buf_ptr(field->name), buf_ptr(&default_value->type->name),
                            buf_ptr(&field->type_entry->name), buf_ptr(&field->type_entry->name)));
                    return ira->codegen->invalid_inst_gen->value->type;
                }
                if ((err = get_const_field_bool(ira, source_node, field_value, "is_comptime", 3, &field->is_comptime)))
                    return ira->codegen->invalid_inst_gen->value->type;
                BigInt *alignment = get_const_field_lit_int(ira, source_node, field_value, "alignment", 4);
                if (alignment == nullptr)
                    return ira->codegen->invalid_inst_gen->value->type;
                field->align = bigint_as_u32(alignment);
            }

            return entry;
        }
        case ZigTypeIdEnum: {
            assert(payload->special == ConstValSpecialStatic);
            assert(payload->type == ir_type_info_get_type(ira, "Enum", nullptr));

            ZigValue *layout_value = get_const_field(ira, source_node, payload, "layout", 0);
            if (layout_value == nullptr)
                return ira->codegen->invalid_inst_gen->value->type;

            assert(layout_value->special == ConstValSpecialStatic);
            assert(layout_value->type == ir_type_info_get_type(ira, "ContainerLayout", nullptr));
            ContainerLayout layout = (ContainerLayout)bigint_as_u32(&layout_value->data.x_enum_tag);

            ZigType *tag_type = get_const_field_meta_type(ira, source_node, payload, "tag_type", 1);
            if (type_is_invalid(tag_type))
                return ira->codegen->invalid_inst_gen->value->type;
            if (tag_type->id != ZigTypeIdInt) {
                ir_add_error_node(ira, source_node, buf_sprintf(
                    "TypeInfo.Enum.tag_type must be an integer type, not '%s'", buf_ptr(&tag_type->name)));
                return ira->codegen->invalid_inst_gen->value->type;
            }

            ZigValue *fields_value = get_const_field(ira, source_node, payload, "fields", 2);
            if (fields_value == nullptr)
                return ira->codegen->invalid_inst_gen->value->type;

            assert(fields_value->special == ConstValSpecialStatic);
            assert(is_slice(fields_value->type));
            ZigValue *fields_ptr = fields_value->data.x_struct.fields[slice_ptr_index];
            ZigValue *fields_len_value = fields_value->data.x_struct.fields[slice_len_index];
            size_t fields_len = bigint_as_usize(&fields_len_value->data.x_bigint);

            ZigValue *decls_value = get_const_field(ira, source_node, payload, "decls", 3);
            if (decls_value == nullptr)
                return ira->codegen->invalid_inst_gen->value->type;

            assert(decls_value->special == ConstValSpecialStatic);
            assert(is_slice(decls_value->type));
            ZigValue *decls_len_value = decls_value->data.x_struct.fields[slice_len_index];
            size_t decls_len = bigint_as_usize(&decls_len_value->data.x_bigint);
            if (decls_len != 0) {
                ir_add_error_node(ira, source_node, buf_create_from_str("TypeInfo.Enum.decls must be empty for @Type"));
                return ira->codegen->invalid_inst_gen->value->type;
            }

            Error err;
            bool is_exhaustive;
            if ((err = get_const_field_bool(ira, source_node, payload, "is_exhaustive", 4, &is_exhaustive)))
                return ira->codegen->invalid_inst_gen->value->type;

            ZigType *entry = new_type_table_entry(ZigTypeIdEnum);
            buf_init_from_buf(&entry->name,
                get_anon_type_name(ira->codegen, ira->zir, "enum", scope, source_node, &entry->name, nullptr));
            entry->data.enumeration.decl_node = source_node;
            entry->data.enumeration.tag_int_type = tag_type;
            entry->data.enumeration.decls_scope = create_decls_scope(
                ira->codegen, source_node, scope, entry, get_scope_import(scope), &entry->name);
            entry->data.enumeration.fields = heap::c_allocator.allocate<TypeEnumField>(fields_len);
            entry->data.enumeration.fields_by_name.init(fields_len);
            entry->data.enumeration.src_field_count = fields_len;
            entry->data.enumeration.layout = layout;
            entry->data.enumeration.non_exhaustive = !is_exhaustive;

            assert(fields_ptr->data.x_ptr.special == ConstPtrSpecialBaseArray);
            assert(fields_ptr->data.x_ptr.data.base_array.elem_index == 0);
            ZigValue *fields_arr = fields_ptr->data.x_ptr.data.base_array.array_val;
            assert(fields_arr->special == ConstValSpecialStatic);
            assert(fields_arr->data.x_array.special == ConstArraySpecialNone);
            for (size_t i = 0; i < fields_len; i++) {
                ZigValue *field_value = &fields_arr->data.x_array.data.s_none.elements[i];
                assert(field_value->type == ir_type_info_get_type(ira, "EnumField", nullptr));
                TypeEnumField *field = &entry->data.enumeration.fields[i];
                field->name = buf_alloc();
                if ((err = get_const_field_buf(ira, source_node, field_value, "name", 0, field->name)))
                    return ira->codegen->invalid_inst_gen->value->type;
                field->decl_index = i;
                field->decl_node = source_node;
                if (entry->data.enumeration.fields_by_name.put_unique(field->name, field) != nullptr) {
                    ir_add_error_node(ira, source_node, buf_sprintf("duplicate enum field '%s'", buf_ptr(field->name)));
                    return ira->codegen->invalid_inst_gen->value->type;
                }
                BigInt *field_int_value = get_const_field_lit_int(ira, source_node, field_value, "value", 1);
                if (field_int_value == nullptr)
                    return ira->codegen->invalid_inst_gen->value->type;
                field->value = *field_int_value;
            }
            return entry;
        }
        case ZigTypeIdUnion: {
            assert(payload->special == ConstValSpecialStatic);
            assert(payload->type == ir_type_info_get_type(ira, "Union", nullptr));

            ZigValue *layout_value = get_const_field(ira, source_node, payload, "layout", 0);
            if (layout_value == nullptr)
                return ira->codegen->invalid_inst_gen->value->type;
            assert(layout_value->special == ConstValSpecialStatic);
            assert(layout_value->type == ir_type_info_get_type(ira, "ContainerLayout", nullptr));
            ContainerLayout layout = (ContainerLayout)bigint_as_u32(&layout_value->data.x_enum_tag);

            ZigType *tag_type = get_const_field_meta_type_optional(ira, source_node, payload, "tag_type", 1);
            if (tag_type != nullptr && type_is_invalid(tag_type)) {
                return ira->codegen->invalid_inst_gen->value->type;
            }
            if (tag_type != nullptr && tag_type->id != ZigTypeIdEnum) {
                ir_add_error_node(ira, source_node, buf_sprintf(
                    "expected enum type, found '%s'", type_id_name(tag_type->id)));
                return ira->codegen->invalid_inst_gen->value->type;
            }

            ZigValue *fields_value = get_const_field(ira, source_node, payload, "fields", 2);
            if (fields_value == nullptr)
                return ira->codegen->invalid_inst_gen->value->type;

            assert(fields_value->special == ConstValSpecialStatic);
            assert(is_slice(fields_value->type));
            ZigValue *fields_ptr = fields_value->data.x_struct.fields[slice_ptr_index];
            ZigValue *fields_len_value = fields_value->data.x_struct.fields[slice_len_index];
            size_t fields_len = bigint_as_usize(&fields_len_value->data.x_bigint);

            ZigValue *decls_value = get_const_field(ira, source_node, payload, "decls", 3);
            if (decls_value == nullptr)
                return ira->codegen->invalid_inst_gen->value->type;

            assert(decls_value->special == ConstValSpecialStatic);
            assert(is_slice(decls_value->type));
            ZigValue *decls_len_value = decls_value->data.x_struct.fields[slice_len_index];
            size_t decls_len = bigint_as_usize(&decls_len_value->data.x_bigint);
            if (decls_len != 0) {
                ir_add_error_node(ira, source_node, buf_create_from_str("TypeInfo.Union.decls must be empty for @Type"));
                return ira->codegen->invalid_inst_gen->value->type;
            }

            ZigType *entry = new_type_table_entry(ZigTypeIdUnion);
            buf_init_from_buf(&entry->name,
                get_anon_type_name(ira->codegen, ira->zir, "union", scope, source_node, &entry->name, nullptr));
            entry->data.unionation.decl_node = source_node;
            entry->data.unionation.fields = heap::c_allocator.allocate<TypeUnionField>(fields_len);
            entry->data.unionation.fields_by_name.init(fields_len);
            entry->data.unionation.decls_scope = create_decls_scope(
                ira->codegen, source_node, scope, entry, get_scope_import(scope), &entry->name);
            entry->data.unionation.tag_type = tag_type;
            entry->data.unionation.src_field_count = fields_len;
            entry->data.unionation.layout = layout;

            assert(fields_ptr->data.x_ptr.special == ConstPtrSpecialBaseArray);
            assert(fields_ptr->data.x_ptr.data.base_array.elem_index == 0);
            ZigValue *fields_arr = fields_ptr->data.x_ptr.data.base_array.array_val;
            assert(fields_arr->special == ConstValSpecialStatic);
            assert(fields_arr->data.x_array.special == ConstArraySpecialNone);
            for (size_t i = 0; i < fields_len; i++) {
                ZigValue *field_value = &fields_arr->data.x_array.data.s_none.elements[i];
                assert(field_value->type == ir_type_info_get_type(ira, "UnionField", nullptr));
                TypeUnionField *field = &entry->data.unionation.fields[i];
                field->name = buf_alloc();
                if ((err = get_const_field_buf(ira, source_node, field_value, "name", 0, field->name)))
                    return ira->codegen->invalid_inst_gen->value->type;
                if (entry->data.unionation.fields_by_name.put_unique(field->name, field) != nullptr) {
                    ir_add_error_node(ira, source_node, buf_sprintf("duplicate union field '%s'", buf_ptr(field->name)));
                    return ira->codegen->invalid_inst_gen->value->type;
                }
                field->decl_node = source_node;
                ZigValue *type_value = get_const_field(ira, source_node, field_value, "field_type", 1);
                if (type_value == nullptr)
                    return ira->codegen->invalid_inst_gen->value->type;
                field->type_val = type_value;
                field->type_entry = type_value->data.x_type;
                BigInt *alignment = get_const_field_lit_int(ira, source_node, field_value, "alignment", 2);
                if (alignment == nullptr)
                    return ira->codegen->invalid_inst_gen->value->type;
                field->align = bigint_as_u32(alignment);
            }
            return entry;
        }
        case ZigTypeIdFn:
        case ZigTypeIdBoundFn: {
            assert(payload->special == ConstValSpecialStatic);
            assert(payload->type == ir_type_info_get_type(ira, "Fn", nullptr));

            ZigValue *cc_value = get_const_field(ira, source_node, payload, "calling_convention", 0);
            if (cc_value == nullptr)
                return ira->codegen->invalid_inst_gen->value->type;
            assert(cc_value->special == ConstValSpecialStatic);
            assert(cc_value->type == get_builtin_type(ira->codegen, "CallingConvention"));
            CallingConvention cc = (CallingConvention)bigint_as_u32(&cc_value->data.x_enum_tag);

            BigInt *alignment = get_const_field_lit_int(ira, source_node, payload, "alignment", 1);
            if (alignment == nullptr)
                return ira->codegen->invalid_inst_gen->value->type;

            Error err;
            bool is_generic;
            if ((err = get_const_field_bool(ira, source_node, payload, "is_generic", 2, &is_generic)))
                return ira->codegen->invalid_inst_gen->value->type;
            if (is_generic) {
                ir_add_error_node(ira, source_node, buf_sprintf("TypeInfo.Fn.is_generic must be false for @Type"));
                return ira->codegen->invalid_inst_gen->value->type;
            }

            bool is_var_args;
            if ((err = get_const_field_bool(ira, source_node, payload, "is_var_args", 3, &is_var_args)))
                return ira->codegen->invalid_inst_gen->value->type;
            if (is_var_args && cc != CallingConventionC) {
                ir_add_error_node(ira, source_node, buf_sprintf("varargs functions must have C calling convention"));
                return ira->codegen->invalid_inst_gen->value->type;
            }

            ZigType *return_type = get_const_field_meta_type_optional(ira, source_node, payload, "return_type", 4);
            if (return_type == nullptr) {
                ir_add_error_node(ira, source_node, buf_sprintf("TypeInfo.Fn.return_type must be non-null for @Type"));
                return ira->codegen->invalid_inst_gen->value->type;
            }

            ZigValue *args_value = get_const_field(ira, source_node, payload, "args", 5);
            if (args_value == nullptr)
                return ira->codegen->invalid_inst_gen->value->type;
            assert(args_value->special == ConstValSpecialStatic);
            assert(is_slice(args_value->type));
            ZigValue *args_ptr = args_value->data.x_struct.fields[slice_ptr_index];
            ZigValue *args_len_value = args_value->data.x_struct.fields[slice_len_index];
            size_t args_len = bigint_as_usize(&args_len_value->data.x_bigint);

            FnTypeId fn_type_id = {};
            fn_type_id.return_type = return_type;
            fn_type_id.param_info = heap::c_allocator.allocate<FnTypeParamInfo>(args_len);
            fn_type_id.param_count = args_len;
            fn_type_id.next_param_index = args_len;
            fn_type_id.is_var_args = is_var_args;
            fn_type_id.cc = cc;
            fn_type_id.alignment = bigint_as_u32(alignment);

            assert(args_ptr->data.x_ptr.special == ConstPtrSpecialBaseArray);
            assert(args_ptr->data.x_ptr.data.base_array.elem_index == 0);
            ZigValue *args_arr = args_ptr->data.x_ptr.data.base_array.array_val;
            assert(args_arr->special == ConstValSpecialStatic);
            assert(args_arr->data.x_array.special == ConstArraySpecialNone);
            for (size_t i = 0; i < args_len; i++) {
                ZigValue *arg_value = &args_arr->data.x_array.data.s_none.elements[i];
                assert(arg_value->type == ir_type_info_get_type(ira, "FnArg", nullptr));
                FnTypeParamInfo *info = &fn_type_id.param_info[i];
                Error err;
                bool is_generic;
                if ((err = get_const_field_bool(ira, source_node, arg_value, "is_generic", 0, &is_generic)))
                    return ira->codegen->invalid_inst_gen->value->type;
                if (is_generic) {
                    ir_add_error_node(ira, source_node, buf_sprintf("TypeInfo.FnArg.is_generic must be false for @Type"));
                    return ira->codegen->invalid_inst_gen->value->type;
                }
                if ((err = get_const_field_bool(ira, source_node, arg_value, "is_noalias", 1, &info->is_noalias)))
                    return ira->codegen->invalid_inst_gen->value->type;
                ZigType *type = get_const_field_meta_type_optional(
                    ira, source_node, arg_value, "arg_type", 2);
                if (type == nullptr) {
                    ir_add_error_node(ira, source_node, buf_sprintf("TypeInfo.FnArg.arg_type must be non-null for @Type"));
                    return ira->codegen->invalid_inst_gen->value->type;
                }
                info->type = type;
            }

            ZigType *entry = get_fn_type(ira->codegen, &fn_type_id);

            switch (tagTypeId) {
                case ZigTypeIdFn:
                    return entry;
                case ZigTypeIdBoundFn: {
                    ZigType *bound_fn_entry = new_type_table_entry(ZigTypeIdBoundFn);
                    bound_fn_entry->name = *buf_sprintf("(bound %s)", buf_ptr(&entry->name));
                    bound_fn_entry->data.bound_fn.fn_type = entry;
                    return bound_fn_entry;
                }
                default:
                    zig_unreachable();
            }
        }
    }
    zig_unreachable();
}

static Stage1AirInst *ir_analyze_instruction_type(IrAnalyze *ira, Stage1ZirInstType *instruction) {
    Stage1AirInst *uncasted_type_info = instruction->type_info->child;
    if (type_is_invalid(uncasted_type_info->value->type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *type_info = ir_implicit_cast(ira, uncasted_type_info, ir_type_info_get_type(ira, nullptr, nullptr));
    if (type_is_invalid(type_info->value->type))
        return ira->codegen->invalid_inst_gen;

    ZigValue *type_info_val = ir_resolve_const(ira, type_info, UndefBad);
    if (type_info_val == nullptr)
        return ira->codegen->invalid_inst_gen;
    ZigTypeId type_id_tag = type_id_at_index(bigint_as_usize(&type_info_val->data.x_union.tag));
    ZigType *type = type_info_to_type(ira, uncasted_type_info->scope,
            uncasted_type_info->source_node, type_id_tag, type_info_val->data.x_union.payload);
    if (type_is_invalid(type))
        return ira->codegen->invalid_inst_gen;
    return ir_const_type(ira, instruction->base.scope, instruction->base.source_node, type);
}

static Stage1AirInst *ir_analyze_instruction_set_eval_branch_quota(IrAnalyze *ira,
        Stage1ZirInstSetEvalBranchQuota *instruction)
{
    uint64_t new_quota;
    if (!ir_resolve_unsigned(ira, instruction->new_quota->child, ira->codegen->builtin_types.entry_u32, &new_quota))
        return ira->codegen->invalid_inst_gen;

    if (new_quota > *ira->backward_branch_quota) {
        *ira->backward_branch_quota = new_quota;
    }

    return ir_const_void(ira, instruction->base.scope, instruction->base.source_node);
}

static Stage1AirInst *ir_analyze_instruction_type_name(IrAnalyze *ira, Stage1ZirInstTypeName *instruction) {
    Stage1AirInst *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_inst_gen;

    if (!type_entry->cached_const_name_val) {
        type_entry->cached_const_name_val = create_const_str_lit(ira->codegen, type_bare_name(type_entry));
    }
    Stage1AirInst *result = ir_const(ira, instruction->base.scope, instruction->base.source_node, nullptr);
    copy_const_val(ira->codegen, result->value, type_entry->cached_const_name_val);
    return result;
}

static Stage1AirInst *ir_analyze_instruction_c_import(IrAnalyze *ira, Stage1ZirInstCImport *instruction) {
    Error err;
    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;
    ZigValue *cimport_result;
    ZigValue *result_ptr;
    create_result_ptr(ira->codegen, void_type, &cimport_result, &result_ptr);
    if ((err = ir_eval_const_value(ira->codegen, &cimport_scope->base, block_node, result_ptr,
        ira->backward_branch_count, ira->backward_branch_quota, nullptr,
        &cimport_scope->buf, block_node, nullptr, nullptr, nullptr, UndefBad)))
    {
        return ira->codegen->invalid_inst_gen;
    }
    if (type_is_invalid(cimport_result->type))
        return ira->codegen->invalid_inst_gen;

    ZigPackage *cur_scope_pkg = scope_package(instruction->base.scope);
    RootStruct *root_struct = node->owner->data.structure.root_struct;
    TokenLoc tok_loc = root_struct->token_locs[node->main_token];
    Buf *namespace_name = buf_sprintf("%s.cimport:%" PRIu32 ":%" PRIu32,
            buf_ptr(&cur_scope_pkg->pkg_path), tok_loc.line + 1, tok_loc.column + 1);

    ZigPackage *cimport_pkg = new_anonymous_package();
    cimport_pkg->package_table.put(buf_create_from_str("builtin"), ira->codegen->compile_var_package);
    cimport_pkg->package_table.put(buf_create_from_str("std"), ira->codegen->std_package);
    buf_init_from_buf(&cimport_pkg->pkg_path, namespace_name);

    const char *out_zig_path_ptr;
    size_t out_zig_path_len;
    Stage2ErrorMsg *errors_ptr;
    size_t errors_len;
    if ((err = stage2_cimport(&ira->codegen->stage1,
            buf_ptr(&cimport_scope->buf), buf_len(&cimport_scope->buf),
            &out_zig_path_ptr, &out_zig_path_len,
            &errors_ptr, &errors_len)))
    {
        if (err != ErrorCCompileErrors) {
            ir_add_error_node(ira, node, buf_sprintf("C import failed: %s", err_str(err)));
            return ira->codegen->invalid_inst_gen;
        }

        ErrorMsg *parent_err_msg = ir_add_error_node(ira, node, buf_sprintf("C import failed"));
        if (!ira->codegen->stage1.link_libc) {
            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_len; i += 1) {
            Stage2ErrorMsg *clang_err = &errors_ptr[i];
            // Clang can emit "too many errors, stopping now", in which case
            // `source` and `filename_ptr` are null
            if (clang_err->source && clang_err->filename_ptr) {
                ErrorMsg *err_msg = err_msg_create_with_offset(
                    clang_err->filename_ptr ?
                        buf_create_from_mem(clang_err->filename_ptr, clang_err->filename_len) :
                        buf_alloc(),
                    clang_err->offset, clang_err->source,
                    buf_create_from_mem(clang_err->msg_ptr, clang_err->msg_len));
                err_msg_add_note(parent_err_msg, err_msg);
            }
        }

        return ira->codegen->invalid_inst_gen;
    }
    Buf *out_zig_path = buf_create_from_mem(out_zig_path_ptr, out_zig_path_len);

    Buf *import_code = buf_alloc();
    if ((err = file_fetch(ira->codegen, out_zig_path, import_code))) {
        ir_add_error_node(ira, node,
                buf_sprintf("unable to open '%s': %s", buf_ptr(out_zig_path), err_str(err)));
        return ira->codegen->invalid_inst_gen;
    }
    ZigType *child_import = add_source_file(ira->codegen, cimport_pkg, out_zig_path,
            import_code, SourceKindCImport);
    return ir_const_type(ira, instruction->base.scope, instruction->base.source_node, child_import);
}

static Stage1AirInst *ir_analyze_instruction_c_include(IrAnalyze *ira, Stage1ZirInstCInclude *instruction) {
    Stage1AirInst *name_value = instruction->name->child;
    if (type_is_invalid(name_value->value->type))
        return ira->codegen->invalid_inst_gen;

    Buf *include_name = ir_resolve_str(ira, name_value);
    if (!include_name)
        return ira->codegen->invalid_inst_gen;

    Buf *c_import_buf = ira->new_irb.exec->c_import_buf;
    // 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.scope, instruction->base.source_node);
}

static Stage1AirInst *ir_analyze_instruction_c_define(IrAnalyze *ira, Stage1ZirInstCDefine *instruction) {
    Stage1AirInst *name = instruction->name->child;
    if (type_is_invalid(name->value->type))
        return ira->codegen->invalid_inst_gen;

    Buf *define_name = ir_resolve_str(ira, name);
    if (!define_name)
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *value = instruction->value->child;
    if (type_is_invalid(value->value->type))
        return ira->codegen->invalid_inst_gen;

    Buf *define_value = nullptr;
    // The second parameter is either a string or void (equivalent to "")
    if (value->value->type->id != ZigTypeIdVoid) {
        define_value = ir_resolve_str(ira, value);
        if (!define_value)
            return ira->codegen->invalid_inst_gen;
    }

    Buf *c_import_buf = ira->new_irb.exec->c_import_buf;
    // We check for this error in pass1
    assert(c_import_buf);

    buf_appendf(c_import_buf, "#define %s %s\n", buf_ptr(define_name),
        define_value ? buf_ptr(define_value) : "");

    return ir_const_void(ira, instruction->base.scope, instruction->base.source_node);
}

static Stage1AirInst *ir_analyze_instruction_c_undef(IrAnalyze *ira, Stage1ZirInstCUndef *instruction) {
    Stage1AirInst *name = instruction->name->child;
    if (type_is_invalid(name->value->type))
        return ira->codegen->invalid_inst_gen;

    Buf *undef_name = ir_resolve_str(ira, name);
    if (!undef_name)
        return ira->codegen->invalid_inst_gen;

    Buf *c_import_buf = ira->new_irb.exec->c_import_buf;
    // 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.scope, instruction->base.source_node);
}

static Stage1AirInst *ir_analyze_instruction_embed_file(IrAnalyze *ira, Stage1ZirInstEmbedFile *instruction) {
    Stage1AirInst *name = instruction->name->child;
    if (type_is_invalid(name->value->type))
        return ira->codegen->invalid_inst_gen;

    Buf *rel_file_path = ir_resolve_str(ira, name);
    if (!rel_file_path)
        return ira->codegen->invalid_inst_gen;

    ZigType *import = get_scope_import(instruction->base.scope);
    // figure out absolute path to resource
    Buf source_dir_path = BUF_INIT;
    os_path_dirname(import->data.structure.root_struct->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_node(ira, instruction->name->source_node,
                    buf_sprintf("unable to find '%s'", buf_ptr(file_path)));
            return ira->codegen->invalid_inst_gen;
        } else {
            ir_add_error_node(ira, instruction->name->source_node,
                    buf_sprintf("unable to open '%s': %s", buf_ptr(file_path), err_str(err)));
            return ira->codegen->invalid_inst_gen;
        }
    }

    Stage1AirInst *result = ir_const(ira, instruction->base.scope, instruction->base.source_node, nullptr);
    init_const_str_lit(ira->codegen, result->value, file_contents, true);
    return result;
}

static Stage1AirInst *ir_analyze_instruction_cmpxchg(IrAnalyze *ira, Stage1ZirInstCmpxchg *instruction) {
    ZigType *operand_type = ir_resolve_atomic_operand_type(ira, instruction->type_value->child);
    if (type_is_invalid(operand_type))
        return ira->codegen->invalid_inst_gen;

    if (operand_type->id == ZigTypeIdFloat) {
        ir_add_error(ira, instruction->type_value->child,
            buf_sprintf("expected bool, integer, enum or pointer type, found '%s'", buf_ptr(&operand_type->name)));
        return ira->codegen->invalid_inst_gen;
    }

    Stage1AirInst *ptr = instruction->ptr->child;
    if (type_is_invalid(ptr->value->type))
        return ira->codegen->invalid_inst_gen;

    // TODO let this be volatile
    ZigType *ptr_type = get_pointer_to_type(ira->codegen, operand_type, false);
    Stage1AirInst *casted_ptr = ir_implicit_cast2(ira, instruction->ptr->scope,
            instruction->ptr->source_node, ptr, ptr_type);
    if (type_is_invalid(casted_ptr->value->type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *cmp_value = instruction->cmp_value->child;
    if (type_is_invalid(cmp_value->value->type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *new_value = instruction->new_value->child;
    if (type_is_invalid(new_value->value->type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *success_order_value = instruction->success_order_value->child;
    if (type_is_invalid(success_order_value->value->type))
        return ira->codegen->invalid_inst_gen;

    AtomicOrder success_order;
    if (!ir_resolve_atomic_order(ira, success_order_value, &success_order))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *failure_order_value = instruction->failure_order_value->child;
    if (type_is_invalid(failure_order_value->value->type))
        return ira->codegen->invalid_inst_gen;

    AtomicOrder failure_order;
    if (!ir_resolve_atomic_order(ira, failure_order_value, &failure_order))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *casted_cmp_value = ir_implicit_cast2(ira, instruction->cmp_value->scope,
            instruction->cmp_value->source_node, cmp_value, operand_type);
    if (type_is_invalid(casted_cmp_value->value->type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *casted_new_value = ir_implicit_cast2(ira, instruction->new_value->scope,
            instruction->new_value->source_node, new_value, operand_type);
    if (type_is_invalid(casted_new_value->value->type))
        return ira->codegen->invalid_inst_gen;

    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_inst_gen;
    }
    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_inst_gen;
    }
    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_inst_gen;
    }
    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_inst_gen;
    }

    ZigType *result_type = get_optional_type(ira->codegen, operand_type);

    // special case zero bit types
    switch (type_has_one_possible_value(ira->codegen, operand_type)) {
        case OnePossibleValueInvalid:
            return ira->codegen->invalid_inst_gen;
        case OnePossibleValueYes: {
            Stage1AirInst *result = ir_const(ira, instruction->base.scope, instruction->base.source_node, result_type);
            set_optional_value_to_null(result->value);
            return result;
        }
        case OnePossibleValueNo:
            break;
    }

    if (instr_is_comptime(casted_ptr) && casted_ptr->value->data.x_ptr.mut != ConstPtrMutRuntimeVar &&
        instr_is_comptime(casted_cmp_value) && instr_is_comptime(casted_new_value)) {
        ZigValue *ptr_val = ir_resolve_const(ira, casted_ptr, UndefBad);
        if (ptr_val == nullptr)
            return ira->codegen->invalid_inst_gen;

        ZigValue *stored_val = const_ptr_pointee(ira, ira->codegen, ptr_val, instruction->base.source_node);
        if (stored_val == nullptr)
            return ira->codegen->invalid_inst_gen;

        ZigValue *expected_val = ir_resolve_const(ira, casted_cmp_value, UndefBad);
        if (expected_val == nullptr)
            return ira->codegen->invalid_inst_gen;

        ZigValue *new_val = ir_resolve_const(ira, casted_new_value, UndefBad);
        if (new_val == nullptr)
            return ira->codegen->invalid_inst_gen;

        bool eql = const_values_equal(ira->codegen, stored_val, expected_val);
        Stage1AirInst *result = ir_const(ira, instruction->base.scope, instruction->base.source_node, result_type);
        if (eql) {
            copy_const_val(ira->codegen, stored_val, new_val);
            set_optional_value_to_null(result->value);
        } else {
            set_optional_payload(result->value, stored_val);
        }
        return result;
    }

    Stage1AirInst *result_loc;
    if (handle_is_ptr(ira->codegen, result_type)) {
        result_loc = ir_resolve_result(ira, &instruction->base, instruction->result_loc,
                result_type, nullptr, true, true);
        if (type_is_invalid(result_loc->value->type) || result_loc->value->type->id == ZigTypeIdUnreachable) {
            return result_loc;
        }
    } else {
        result_loc = nullptr;
    }

    return ir_build_cmpxchg_gen(ira, instruction->base.scope, instruction->base.source_node, result_type,
            casted_ptr, casted_cmp_value, casted_new_value,
            success_order, failure_order, instruction->is_weak, result_loc);
}

static ErrorMsg *ir_eval_reduce(IrAnalyze *ira, Scope *scope, AstNode *source_node, ReduceOp op, ZigValue *value, ZigValue *out_value) {
    assert(value->type->id == ZigTypeIdVector);
    ZigType *scalar_type = value->type->data.vector.elem_type;
    const size_t len = value->type->data.vector.len;
    assert(len > 0);

    out_value->type = scalar_type;
    out_value->special = ConstValSpecialStatic;

    if (scalar_type->id == ZigTypeIdBool) {
        ZigValue *first_elem_val = &value->data.x_array.data.s_none.elements[0];

        bool result = first_elem_val->data.x_bool;
        for (size_t i = 1; i < len; i++) {
            ZigValue *elem_val = &value->data.x_array.data.s_none.elements[i];

            switch (op) {
                case ReduceOp_and:
                    result = result && elem_val->data.x_bool;
                    if (!result) break; // Short circuit
                    break;
                case ReduceOp_or:
                    result = result || elem_val->data.x_bool;
                    if (result) break; // Short circuit
                    break;
                case ReduceOp_xor:
                    result = result != elem_val->data.x_bool;
                    break;
                default:
                    zig_unreachable();
            }
        }

        out_value->data.x_bool = result;
        return nullptr;
    }

    // Evaluate and/or/xor.
    if (op == ReduceOp_and || op == ReduceOp_or || op == ReduceOp_xor) {
        ZigValue *first_elem_val = &value->data.x_array.data.s_none.elements[0];

        copy_const_val(ira->codegen, out_value, first_elem_val);

        for (size_t i = 1; i < len; i++) {
            ZigValue *elem_val = &value->data.x_array.data.s_none.elements[i];

            IrBinOp bin_op;
            switch (op) {
                case ReduceOp_and: bin_op = IrBinOpBinAnd; break;
                case ReduceOp_or: bin_op = IrBinOpBinOr; break;
                case ReduceOp_xor: bin_op = IrBinOpBinXor; break;
                default: zig_unreachable();
            }

            ErrorMsg *msg = ir_eval_math_op_scalar(ira, scope, source_node, scalar_type,
                    out_value, bin_op, elem_val, out_value);
            if (msg != nullptr)
                return msg;
        }

        return nullptr;
    }

    // Evaluate add/sub.
    // Perform the reduction sequentially, starting from the neutral value.
    if (op == ReduceOp_add || op == ReduceOp_mul) {
        if (scalar_type->id == ZigTypeIdInt) {
            if (op == ReduceOp_add) {
                bigint_init_unsigned(&out_value->data.x_bigint, 0);
            } else {
                bigint_init_unsigned(&out_value->data.x_bigint, 1);
            }
        } else {
            if (op == ReduceOp_add) {
                float_init_f64(out_value, -0.0);
            } else {
                float_init_f64(out_value, 1.0);
            }
        }

        for (size_t i = 0; i < len; i++) {
            ZigValue *elem_val = &value->data.x_array.data.s_none.elements[i];

            IrBinOp bin_op;
            switch (op) {
                case ReduceOp_add: bin_op = IrBinOpAdd; break;
                case ReduceOp_mul: bin_op = IrBinOpMult; break;
                default: zig_unreachable();
            }

            ErrorMsg *msg = ir_eval_math_op_scalar(ira, scope, source_node, scalar_type,
                    out_value, bin_op, elem_val, out_value);
            if (msg != nullptr)
                return msg;
        }

        return nullptr;
    }

    // Evaluate min/max.
    ZigValue *candidate_elem_val = &value->data.x_array.data.s_none.elements[0];

    ZigValue *dummy_cmp_value = ira->codegen->pass1_arena->create<ZigValue>();
    for (size_t i = 1; i < len; i++) {
        ZigValue *elem_val = &value->data.x_array.data.s_none.elements[i];

        IrBinOp bin_op;
        switch (op) {
            case ReduceOp_min: bin_op = IrBinOpCmpLessThan; break;
            case ReduceOp_max: bin_op = IrBinOpCmpGreaterThan; break;
            default: zig_unreachable();
        }

        ErrorMsg *msg = ir_eval_bin_op_cmp_scalar(ira, scope, source_node,
                elem_val, bin_op, candidate_elem_val, dummy_cmp_value);
        if (msg != nullptr)
            return msg;

        if (dummy_cmp_value->data.x_bool)
            candidate_elem_val = elem_val;
    }

    ira->codegen->pass1_arena->destroy(dummy_cmp_value);
    copy_const_val(ira->codegen, out_value, candidate_elem_val);

    return nullptr;
}

static Stage1AirInst *ir_analyze_instruction_reduce(IrAnalyze *ira, Stage1ZirInstReduce *instruction) {
    Stage1AirInst *op_inst = instruction->op->child;
    if (type_is_invalid(op_inst->value->type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *value_inst = instruction->value->child;
    if (type_is_invalid(value_inst->value->type))
        return ira->codegen->invalid_inst_gen;

    ZigType *value_type = value_inst->value->type;
    if (value_type->id != ZigTypeIdVector) {
        ir_add_error(ira, value_inst,
                buf_sprintf("expected vector type, found '%s'",
                    buf_ptr(&value_type->name)));
        return ira->codegen->invalid_inst_gen;
    }

    ReduceOp op;
    if (!ir_resolve_reduce_op(ira, op_inst, &op))
        return ira->codegen->invalid_inst_gen;

    ZigType *elem_type = value_type->data.vector.elem_type;
    switch (elem_type->id) {
        case ZigTypeIdInt:
            break;
        case ZigTypeIdBool:
            if (op > ReduceOp_xor) {
                ir_add_error(ira, op_inst,
                        buf_sprintf("invalid operation for '%s' type",
                            buf_ptr(&elem_type->name)));
                return ira->codegen->invalid_inst_gen;
            } break;
        case ZigTypeIdFloat:
            if (op < ReduceOp_min) {
                ir_add_error(ira, op_inst,
                        buf_sprintf("invalid operation for '%s' type",
                            buf_ptr(&elem_type->name)));
                return ira->codegen->invalid_inst_gen;
            } break;
        default:
            // Vectors cannot have child types other than those listed above
            zig_unreachable();
    }

    // special case zero bit types
    switch (type_has_one_possible_value(ira->codegen, elem_type)) {
        case OnePossibleValueInvalid:
            return ira->codegen->invalid_inst_gen;
        case OnePossibleValueYes:
            return ir_const_move(ira, instruction->base.scope, instruction->base.source_node,
                    get_the_one_possible_value(ira->codegen, elem_type));
        case OnePossibleValueNo:
            break;
    }

    if (instr_is_comptime(value_inst)) {
        Stage1AirInst *result = ir_const(ira, instruction->base.scope, instruction->base.source_node, elem_type);
        if (ir_eval_reduce(ira, instruction->base.scope, instruction->base.source_node, op, value_inst->value, result->value))
            return ira->codegen->invalid_inst_gen;
        return result;
    }

    return ir_build_reduce_gen(ira, instruction->base.scope, instruction->base.source_node, op, value_inst, elem_type);
}

static Stage1AirInst *ir_analyze_instruction_fence(IrAnalyze *ira, Stage1ZirInstFence *instruction) {
    Stage1AirInst *order_inst = instruction->order->child;
    if (type_is_invalid(order_inst->value->type))
        return ira->codegen->invalid_inst_gen;

    AtomicOrder order;
    if (!ir_resolve_atomic_order(ira, order_inst, &order))
        return ira->codegen->invalid_inst_gen;

    if (order < AtomicOrderAcquire) {
        ir_add_error(ira, order_inst,
                buf_sprintf("atomic ordering must be Acquire or stricter"));
        return ira->codegen->invalid_inst_gen;
    }

    return ir_build_fence_gen(ira, instruction->base.scope, instruction->base.source_node, order);
}

static Stage1AirInst *ir_analyze_instruction_truncate(IrAnalyze *ira, Stage1ZirInstTruncate *instruction) {
    Stage1AirInst *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_inst_gen;

    Stage1AirInst *operand = instruction->target->child;
    if (type_is_invalid(operand->value->type))
        return ira->codegen->invalid_inst_gen;

    return ir_analyze_truncate(ira, instruction->base.scope, instruction->base.source_node,
            dest_type, instruction->dest_type->source_node,
            operand, instruction->target->source_node);
}

static Stage1AirInst *ir_analyze_int_cast(IrAnalyze *ira, Scope *scope, AstNode *source_node,
    ZigType *dest_type, AstNode *dest_type_src_node,
    Stage1AirInst *target, AstNode *target_src_node)
{
    ZigType *scalar_dest_type = (dest_type->id == ZigTypeIdVector) ?
        dest_type->data.vector.elem_type : dest_type;

    if (scalar_dest_type->id != ZigTypeIdInt && scalar_dest_type->id != ZigTypeIdComptimeInt) {
        ir_add_error_node(ira, dest_type_src_node,
                buf_sprintf("expected integer type, found '%s'", buf_ptr(&scalar_dest_type->name)));
        return ira->codegen->invalid_inst_gen;
    }

    ZigType *scalar_target_type = (target->value->type->id == ZigTypeIdVector) ?
        target->value->type->data.vector.elem_type : target->value->type;

    if (scalar_target_type->id != ZigTypeIdInt && scalar_target_type->id != ZigTypeIdComptimeInt) {
        ir_add_error_node(ira, target_src_node, buf_sprintf("expected integer type, found '%s'",
                    buf_ptr(&scalar_target_type->name)));
        return ira->codegen->invalid_inst_gen;
    }

    if (scalar_dest_type->id == ZigTypeIdComptimeInt) {
        ZigValue *val = ir_resolve_const(ira, target, UndefBad);
        if (val == nullptr)
            return ira->codegen->invalid_inst_gen;

        return ir_implicit_cast2(ira, scope, target_src_node, target, dest_type);
    }

    return ir_analyze_widen_or_shorten(ira, scope, source_node, target, dest_type);
}

static Stage1AirInst *ir_analyze_instruction_int_cast(IrAnalyze *ira, Stage1ZirInstIntCast *instruction) {
    ZigType *dest_type = ir_resolve_type(ira, instruction->dest_type->child);
    if (type_is_invalid(dest_type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *target = instruction->target->child;
    if (type_is_invalid(target->value->type))
        return ira->codegen->invalid_inst_gen;

    return ir_analyze_int_cast(ira, instruction->base.scope, instruction->base.source_node,
            dest_type, instruction->dest_type->source_node,
            target, instruction->target->source_node);
}

static Stage1AirInst *ir_analyze_instruction_float_cast(IrAnalyze *ira, Stage1ZirInstFloatCast *instruction) {
    ZigType *dest_type = ir_resolve_type(ira, instruction->dest_type->child);
    if (type_is_invalid(dest_type))
        return ira->codegen->invalid_inst_gen;

    if (dest_type->id != ZigTypeIdFloat && dest_type->id != ZigTypeIdComptimeFloat) {
        ir_add_error_node(ira, instruction->dest_type->source_node,
                buf_sprintf("expected float type, found '%s'", buf_ptr(&dest_type->name)));
        return ira->codegen->invalid_inst_gen;
    }

    Stage1AirInst *target = instruction->target->child;
    if (type_is_invalid(target->value->type))
        return ira->codegen->invalid_inst_gen;

    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.scope, instruction->base.source_node, target, dest_type, op);
        } else {
            return ira->codegen->invalid_inst_gen;
        }
    }

    if (target->value->type->id != ZigTypeIdFloat) {
        ir_add_error_node(ira, instruction->target->source_node, buf_sprintf("expected float type, found '%s'",
                    buf_ptr(&target->value->type->name)));
        return ira->codegen->invalid_inst_gen;
    }

    if (instr_is_comptime(target) || dest_type->id == ZigTypeIdComptimeFloat) {
        ZigValue *val = ir_resolve_const(ira, target, UndefBad);
        if (val == nullptr)
            return ira->codegen->invalid_inst_gen;

        // XXX: This will trigger an assertion failure if dest_type is comptime_float
        return ir_analyze_widen_or_shorten(ira, instruction->target->scope,
                instruction->target->source_node, target, dest_type);
    }

    return ir_analyze_widen_or_shorten(ira, instruction->base.scope, instruction->base.source_node, target, dest_type);
}

static Stage1AirInst *ir_analyze_instruction_err_set_cast(IrAnalyze *ira, Stage1ZirInstErrSetCast *instruction) {
    ZigType *dest_type = ir_resolve_type(ira, instruction->dest_type->child);
    if (type_is_invalid(dest_type))
        return ira->codegen->invalid_inst_gen;

    if (dest_type->id != ZigTypeIdErrorSet) {
        ir_add_error_node(ira, instruction->dest_type->source_node,
                buf_sprintf("expected error set type, found '%s'", buf_ptr(&dest_type->name)));
        return ira->codegen->invalid_inst_gen;
    }

    Stage1AirInst *target = instruction->target->child;
    if (type_is_invalid(target->value->type))
        return ira->codegen->invalid_inst_gen;

    if (target->value->type->id != ZigTypeIdErrorSet) {
        ir_add_error_node(ira, instruction->target->source_node,
                buf_sprintf("expected error set type, found '%s'", buf_ptr(&target->value->type->name)));
        return ira->codegen->invalid_inst_gen;
    }

    return ir_analyze_err_set_cast(ira, instruction->base.scope, instruction->base.source_node, target, dest_type);
}

static Error resolve_ptr_align(IrAnalyze *ira, ZigType *ty, uint32_t *result_align) {
    Error err;

    ZigType *ptr_type;
    if (is_slice(ty)) {
        TypeStructField *ptr_field = ty->data.structure.fields[slice_ptr_index];
        ptr_type = resolve_struct_field_type(ira->codegen, ptr_field);
    } else {
        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;
    } else if (is_slice(ptr_type)) {
        TypeStructField *ptr_field = ptr_type->data.structure.fields[slice_ptr_index];
        ZigType *slice_ptr_type = resolve_struct_field_type(ira->codegen, ptr_field);
        if ((err = type_resolve(ira->codegen, slice_ptr_type->data.pointer.child_type, ResolveStatusAlignmentKnown)))
            return err;
    }

    *result_align = get_ptr_align(ira->codegen, ty);
    return ErrorNone;
}

static Stage1AirInst *ir_analyze_instruction_int_to_float(IrAnalyze *ira, Stage1ZirInstIntToFloat *instruction) {
    ZigType *dest_type = ir_resolve_type(ira, instruction->dest_type->child);
    if (type_is_invalid(dest_type))
        return ira->codegen->invalid_inst_gen;

    if (dest_type->id != ZigTypeIdFloat && dest_type->id != ZigTypeIdComptimeFloat) {
        ir_add_error_node(ira, instruction->dest_type->source_node,
                buf_sprintf("expected float type, found '%s'", buf_ptr(&dest_type->name)));
        return ira->codegen->invalid_inst_gen;
    }

    Stage1AirInst *target = instruction->target->child;
    if (type_is_invalid(target->value->type))
        return ira->codegen->invalid_inst_gen;

    if (target->value->type->id != ZigTypeIdInt && target->value->type->id != ZigTypeIdComptimeInt) {
        ir_add_error_node(ira, instruction->target->source_node,
                buf_sprintf("expected int type, found '%s'", buf_ptr(&target->value->type->name)));
        return ira->codegen->invalid_inst_gen;
    }

    return ir_resolve_cast(ira, instruction->base.scope, instruction->base.source_node, target, dest_type, CastOpIntToFloat);
}

static Stage1AirInst *ir_analyze_instruction_float_to_int(IrAnalyze *ira, Stage1ZirInstFloatToInt *instruction) {
    ZigType *dest_type = ir_resolve_type(ira, instruction->dest_type->child);
    if (type_is_invalid(dest_type))
        return ira->codegen->invalid_inst_gen;

    if (dest_type->id != ZigTypeIdInt && dest_type->id != ZigTypeIdComptimeInt) {
        ir_add_error_node(ira, instruction->dest_type->source_node,
                buf_sprintf("expected integer type, found '%s'", buf_ptr(&dest_type->name)));
        return ira->codegen->invalid_inst_gen;
    }

    Stage1AirInst *target = instruction->target->child;
    if (type_is_invalid(target->value->type))
        return ira->codegen->invalid_inst_gen;

    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_node(ira, target->source_node, buf_sprintf("expected float type, found '%s'",
                    buf_ptr(&target->value->type->name)));
        return ira->codegen->invalid_inst_gen;
    }

    return ir_resolve_cast(ira, instruction->base.scope, instruction->base.source_node, target, dest_type, CastOpFloatToInt);
}

static Stage1AirInst *ir_analyze_instruction_err_to_int(IrAnalyze *ira, Stage1ZirInstErrToInt *instruction) {
    Stage1AirInst *target = instruction->target->child;
    if (type_is_invalid(target->value->type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *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_inst_gen;
    }

    return ir_analyze_err_to_int(ira, instruction->base.scope, instruction->base.source_node, casted_target, ira->codegen->err_tag_type);
}

static Stage1AirInst *ir_analyze_instruction_int_to_err(IrAnalyze *ira, Stage1ZirInstIntToErr *instruction) {
    Stage1AirInst *target = instruction->target->child;
    if (type_is_invalid(target->value->type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *casted_target = ir_implicit_cast(ira, target, ira->codegen->err_tag_type);
    if (type_is_invalid(casted_target->value->type))
        return ira->codegen->invalid_inst_gen;

    return ir_analyze_int_to_err(ira, instruction->base.scope, instruction->base.source_node, casted_target, ira->codegen->builtin_types.entry_global_error_set);
}

static Stage1AirInst *ir_analyze_instruction_bool_to_int(IrAnalyze *ira, Stage1ZirInstBoolToInt *instruction) {
    Stage1AirInst *target = instruction->target->child;
    if (type_is_invalid(target->value->type))
        return ira->codegen->invalid_inst_gen;

    if (target->value->type->id != ZigTypeIdBool) {
        ir_add_error_node(ira, instruction->target->source_node,
                buf_sprintf("expected bool, found '%s'", buf_ptr(&target->value->type->name)));
        return ira->codegen->invalid_inst_gen;
    }

    if (instr_is_comptime(target)) {
        bool is_true;
        if (!ir_resolve_bool(ira, target, &is_true))
            return ira->codegen->invalid_inst_gen;

        return ir_const_unsigned(ira, instruction->base.scope, instruction->base.source_node, is_true ? 1 : 0);
    }

    ZigType *u1_type = get_int_type(ira->codegen, false, 1);
    return ir_resolve_cast(ira, instruction->base.scope, instruction->base.source_node, target, u1_type, CastOpBoolToInt);
}

static Stage1AirInst *ir_analyze_instruction_vector_type(IrAnalyze *ira, Stage1ZirInstVectorType *instruction) {
    uint64_t len;
    if (!ir_resolve_unsigned(ira, instruction->len->child, ira->codegen->builtin_types.entry_u32, &len))
        return ira->codegen->invalid_inst_gen;

    ZigType *elem_type = ir_resolve_vector_elem_type(ira, instruction->elem_type->child);
    if (type_is_invalid(elem_type))
        return ira->codegen->invalid_inst_gen;

    ZigType *vector_type = get_vector_type(ira->codegen, len, elem_type);

    return ir_const_type(ira, instruction->base.scope, instruction->base.source_node, vector_type);
}

static Stage1AirInst *ir_analyze_shuffle_vector(IrAnalyze *ira, Scope *scope, AstNode *source_node,
    ZigType *scalar_type, Stage1AirInst *a, Stage1AirInst *b, Stage1AirInst *mask)
{
    Error err;
    src_assert(source_node && scalar_type && a && b && mask, source_node);

    if ((err = ir_validate_vector_elem_type(ira, source_node, scalar_type)))
        return ira->codegen->invalid_inst_gen;

    uint32_t len_mask;
    if (mask->value->type->id == ZigTypeIdVector) {
        len_mask = mask->value->type->data.vector.len;
    } else if (mask->value->type->id == ZigTypeIdArray) {
        len_mask = mask->value->type->data.array.len;
    } else {
        ir_add_error(ira, mask,
            buf_sprintf("expected vector or array, found '%s'",
                buf_ptr(&mask->value->type->name)));
        return ira->codegen->invalid_inst_gen;
    }
    mask = ir_implicit_cast(ira, mask, get_vector_type(ira->codegen, len_mask,
                ira->codegen->builtin_types.entry_i32));
    if (type_is_invalid(mask->value->type))
        return ira->codegen->invalid_inst_gen;

    uint32_t len_a;
    if (a->value->type->id == ZigTypeIdVector) {
        len_a = a->value->type->data.vector.len;
    } else if (a->value->type->id == ZigTypeIdArray) {
        len_a = a->value->type->data.array.len;
    } else if (a->value->type->id == ZigTypeIdUndefined) {
        len_a = UINT32_MAX;
    } else {
        ir_add_error(ira, a,
            buf_sprintf("expected vector or array with element type '%s', found '%s'",
                buf_ptr(&scalar_type->name),
                buf_ptr(&a->value->type->name)));
        return ira->codegen->invalid_inst_gen;
    }

    uint32_t len_b;
    if (b->value->type->id == ZigTypeIdVector) {
        len_b = b->value->type->data.vector.len;
    } else if (b->value->type->id == ZigTypeIdArray) {
        len_b = b->value->type->data.array.len;
    } else if (b->value->type->id == ZigTypeIdUndefined) {
        len_b = UINT32_MAX;
    } else {
        ir_add_error(ira, b,
            buf_sprintf("expected vector or array with element type '%s', found '%s'",
                buf_ptr(&scalar_type->name),
                buf_ptr(&b->value->type->name)));
        return ira->codegen->invalid_inst_gen;
    }

    if (len_a == UINT32_MAX && len_b == UINT32_MAX) {
        return ir_const_undef(ira, a->scope, a->source_node, get_vector_type(ira->codegen, len_mask, scalar_type));
    }

    if (len_a == UINT32_MAX) {
        len_a = len_b;
        a = ir_const_undef(ira, a->scope, a->source_node, get_vector_type(ira->codegen, len_a, scalar_type));
    } else {
        a = ir_implicit_cast(ira, a, get_vector_type(ira->codegen, len_a, scalar_type));
        if (type_is_invalid(a->value->type))
            return ira->codegen->invalid_inst_gen;
    }

    if (len_b == UINT32_MAX) {
        len_b = len_a;
        b = ir_const_undef(ira, b->scope, b->source_node, get_vector_type(ira->codegen, len_b, scalar_type));
    } else {
        b = ir_implicit_cast(ira, b, get_vector_type(ira->codegen, len_b, scalar_type));
        if (type_is_invalid(b->value->type))
            return ira->codegen->invalid_inst_gen;
    }

    ZigValue *mask_val = ir_resolve_const(ira, mask, UndefOk);
    if (mask_val == nullptr)
        return ira->codegen->invalid_inst_gen;

    expand_undef_array(ira->codegen, mask_val);

    for (uint32_t i = 0; i < len_mask; i += 1) {
        ZigValue *mask_elem_val = &mask_val->data.x_array.data.s_none.elements[i];
        if (mask_elem_val->special == ConstValSpecialUndef)
            continue;
        int32_t v_i32 = bigint_as_signed(&mask_elem_val->data.x_bigint);
        uint32_t v;
        Stage1AirInst *chosen_operand;
        if (v_i32 >= 0) {
            v = (uint32_t)v_i32;
            chosen_operand = a;
        } else {
            v = (uint32_t)~v_i32;
            chosen_operand = b;
        }
        if (v >= chosen_operand->value->type->data.vector.len) {
            ErrorMsg *msg = ir_add_error(ira, mask,
                buf_sprintf("mask index '%u' has out-of-bounds selection", i));
            add_error_note(ira->codegen, msg, chosen_operand->source_node,
                buf_sprintf("selected index '%u' out of bounds of %s", v,
                    buf_ptr(&chosen_operand->value->type->name)));
            if (chosen_operand == a && v < len_a + len_b) {
                add_error_note(ira->codegen, msg, b->source_node,
                    buf_create_from_str("selections from the second vector are specified with negative numbers"));
            }
            return ira->codegen->invalid_inst_gen;
        }
    }

    ZigType *result_type = get_vector_type(ira->codegen, len_mask, scalar_type);
    if (instr_is_comptime(a) && instr_is_comptime(b)) {
        ZigValue *a_val = ir_resolve_const(ira, a, UndefOk);
        if (a_val == nullptr)
            return ira->codegen->invalid_inst_gen;

        ZigValue *b_val = ir_resolve_const(ira, b, UndefOk);
        if (b_val == nullptr)
            return ira->codegen->invalid_inst_gen;

        expand_undef_array(ira->codegen, a_val);
        expand_undef_array(ira->codegen, b_val);

        Stage1AirInst *result = ir_const(ira, scope, source_node, result_type);
        result->value->data.x_array.data.s_none.elements = ira->codegen->pass1_arena->allocate<ZigValue>(len_mask);
        for (uint32_t i = 0; i < mask_val->type->data.vector.len; i += 1) {
            ZigValue *mask_elem_val = &mask_val->data.x_array.data.s_none.elements[i];
            ZigValue *result_elem_val = &result->value->data.x_array.data.s_none.elements[i];
            if (mask_elem_val->special == ConstValSpecialUndef) {
                result_elem_val->special = ConstValSpecialUndef;
                continue;
            }
            int32_t v = bigint_as_signed(&mask_elem_val->data.x_bigint);
            // We've already checked for and emitted compile errors for index out of bounds here.
            ZigValue *src_elem_val = (v >= 0) ?
                &a->value->data.x_array.data.s_none.elements[v] :
                &b->value->data.x_array.data.s_none.elements[~v];
            copy_const_val(ira->codegen, result_elem_val, src_elem_val);

            src_assert(result_elem_val->special == ConstValSpecialStatic, source_node);
        }
        result->value->special = ConstValSpecialStatic;
        return result;
    }

    // All static analysis passed, and not comptime.
    // For runtime codegen, vectors a and b must be the same length. Here we
    // recursively @shuffle the smaller vector to append undefined elements
    // to it up to the length of the longer vector. This recursion terminates
    // in 1 call because these calls to ir_analyze_shuffle_vector guarantee
    // len_a == len_b.
    if (len_a != len_b) {
        uint32_t len_min = min(len_a, len_b);
        uint32_t len_max = max(len_a, len_b);

        Stage1AirInst *expand_mask = ir_const(ira, mask->scope, mask->source_node,
            get_vector_type(ira->codegen, len_max, ira->codegen->builtin_types.entry_i32));
        expand_mask->value->data.x_array.data.s_none.elements = ira->codegen->pass1_arena->allocate<ZigValue>(len_max);
        uint32_t i = 0;
        for (; i < len_min; i += 1)
            bigint_init_unsigned(&expand_mask->value->data.x_array.data.s_none.elements[i].data.x_bigint, i);
        for (; i < len_max; i += 1)
            bigint_init_signed(&expand_mask->value->data.x_array.data.s_none.elements[i].data.x_bigint, -1);

        Stage1AirInst *undef = ir_const_undef(ira, scope, source_node,
            get_vector_type(ira->codegen, len_min, scalar_type));

        if (len_b < len_a) {
            b = ir_analyze_shuffle_vector(ira, scope, source_node, scalar_type, b, undef, expand_mask);
        } else {
            a = ir_analyze_shuffle_vector(ira, scope, source_node, scalar_type, a, undef, expand_mask);
        }
    }

    return ir_build_shuffle_vector_gen(ira, scope, source_node,
            result_type, a, b, mask);
}

static Stage1AirInst *ir_analyze_instruction_shuffle_vector(IrAnalyze *ira, Stage1ZirInstShuffleVector *instruction) {
    ZigType *scalar_type = ir_resolve_vector_elem_type(ira, instruction->scalar_type->child);
    if (type_is_invalid(scalar_type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *a = instruction->a->child;
    if (type_is_invalid(a->value->type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *b = instruction->b->child;
    if (type_is_invalid(b->value->type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *mask = instruction->mask->child;
    if (type_is_invalid(mask->value->type))
        return ira->codegen->invalid_inst_gen;

    return ir_analyze_shuffle_vector(ira, instruction->base.scope, instruction->base.source_node, scalar_type, a, b, mask);
}

static Stage1AirInst *ir_analyze_instruction_select(IrAnalyze *ira, Stage1ZirInstSelect *instruction) {
    Error err;

    ZigType *scalar_type = ir_resolve_vector_elem_type(ira, instruction->scalar_type->child);
    if (type_is_invalid(scalar_type))
        return ira->codegen->invalid_inst_gen;

    if ((err = ir_validate_vector_elem_type(ira, instruction->base.source_node, scalar_type)))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *pred = instruction->pred->child;
    if (type_is_invalid(pred->value->type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *a = instruction->a->child;
    if (type_is_invalid(a->value->type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *b = instruction->b->child;
    if (type_is_invalid(b->value->type))
        return ira->codegen->invalid_inst_gen;

    if (pred->value->type->id != ZigTypeIdVector) {
        ir_add_error(ira, pred,
                buf_sprintf("expected vector type, found '%s'",
                    buf_ptr(&pred->value->type->name)));
        return ira->codegen->invalid_inst_gen;
    }

    uint32_t pred_len = pred->value->type->data.vector.len;
    pred = ir_implicit_cast(ira, pred, get_vector_type(ira->codegen, pred_len,
                ira->codegen->builtin_types.entry_bool));
    if (type_is_invalid(pred->value->type))
        return ira->codegen->invalid_inst_gen;

    if (a->value->type->id != ZigTypeIdVector) {
        ir_add_error(ira, a,
                buf_sprintf("expected vector type, found '%s'",
                    buf_ptr(&a->value->type->name)));
        return ira->codegen->invalid_inst_gen;
    }

    if (b->value->type->id != ZigTypeIdVector) {
        ir_add_error(ira, b,
                buf_sprintf("expected vector type, found '%s'",
                    buf_ptr(&b->value->type->name)));
        return ira->codegen->invalid_inst_gen;
    }

    ZigType *result_type = get_vector_type(ira->codegen, pred_len, scalar_type);

    a = ir_implicit_cast(ira, a, result_type);
    if (type_is_invalid(a->value->type))
        return ira->codegen->invalid_inst_gen;

    b = ir_implicit_cast(ira, b, result_type);
    if (type_is_invalid(a->value->type))
        return ira->codegen->invalid_inst_gen;

    if (instr_is_comptime(pred) && instr_is_comptime(a) && instr_is_comptime(b)) {
        ZigValue *pred_val = ir_resolve_const(ira, pred, UndefBad);
        if (pred_val == nullptr)
            return ira->codegen->invalid_inst_gen;

        ZigValue *a_val = ir_resolve_const(ira, a, UndefBad);
        if (a_val == nullptr)
            return ira->codegen->invalid_inst_gen;

        ZigValue *b_val = ir_resolve_const(ira, b, UndefBad);
        if (b_val == nullptr)
            return ira->codegen->invalid_inst_gen;

        expand_undef_array(ira->codegen, a_val);
        expand_undef_array(ira->codegen, b_val);

        Stage1AirInst *result = ir_const(ira, instruction->base.scope, instruction->base.source_node, result_type);
        result->value->data.x_array.data.s_none.elements = ira->codegen->pass1_arena->allocate<ZigValue>(pred_len);

        for (uint64_t i = 0; i < pred_len; i += 1) {
            ZigValue *dst_elem_val = &result->value->data.x_array.data.s_none.elements[i];
            ZigValue *pred_elem_val = &pred_val->data.x_array.data.s_none.elements[i];
            ZigValue *a_elem_val = &a_val->data.x_array.data.s_none.elements[i];
            ZigValue *b_elem_val = &b_val->data.x_array.data.s_none.elements[i];
            ZigValue *result_elem_val = pred_elem_val->data.x_bool ? a_elem_val : b_elem_val;
            copy_const_val(ira->codegen, dst_elem_val, result_elem_val);
        }

        result->value->special = ConstValSpecialStatic;
        return result;
    }

    return ir_build_select_gen(ira, instruction->base.scope, instruction->base.source_node, result_type, pred, a, b);
}

static Stage1AirInst *ir_analyze_instruction_splat(IrAnalyze *ira, Stage1ZirInstSplat *instruction) {
    Error err;

    Stage1AirInst *len = instruction->len->child;
    if (type_is_invalid(len->value->type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *scalar = instruction->scalar->child;
    if (type_is_invalid(scalar->value->type))
        return ira->codegen->invalid_inst_gen;

    uint64_t len_u64;
    if (!ir_resolve_unsigned(ira, len, ira->codegen->builtin_types.entry_u32, &len_u64))
        return ira->codegen->invalid_inst_gen;
    uint32_t len_int = len_u64;

    if ((err = ir_validate_vector_elem_type(ira, scalar->source_node, scalar->value->type)))
        return ira->codegen->invalid_inst_gen;

    ZigType *return_type = get_vector_type(ira->codegen, len_int, scalar->value->type);

    if (instr_is_comptime(scalar)) {
        ZigValue *scalar_val = ir_resolve_const(ira, scalar, UndefOk);
        if (scalar_val == nullptr)
            return ira->codegen->invalid_inst_gen;
        if (scalar_val->special == ConstValSpecialUndef)
            return ir_const_undef(ira, instruction->base.scope, instruction->base.source_node, return_type);

        Stage1AirInst *result = ir_const(ira, instruction->base.scope, instruction->base.source_node, return_type);
        result->value->data.x_array.data.s_none.elements = ira->codegen->pass1_arena->allocate<ZigValue>(len_int);
        for (uint32_t i = 0; i < len_int; i += 1) {
            copy_const_val(ira->codegen, &result->value->data.x_array.data.s_none.elements[i], scalar_val);
        }
        return result;
    }

    return ir_build_splat_gen(ira, instruction->base.scope, instruction->base.source_node, return_type, scalar);
}

static Stage1AirInst *ir_analyze_instruction_bool_not(IrAnalyze *ira, Stage1ZirInstBoolNot *instruction) {
    Stage1AirInst *value = instruction->value->child;
    if (type_is_invalid(value->value->type))
        return ira->codegen->invalid_inst_gen;

    ZigType *bool_type = ira->codegen->builtin_types.entry_bool;

    Stage1AirInst *casted_value = ir_implicit_cast(ira, value, bool_type);
    if (type_is_invalid(casted_value->value->type))
        return ira->codegen->invalid_inst_gen;

    if (instr_is_comptime(casted_value)) {
        ZigValue *value = ir_resolve_const(ira, casted_value, UndefBad);
        if (value == nullptr)
            return ira->codegen->invalid_inst_gen;

        return ir_const_bool(ira, instruction->base.scope, instruction->base.source_node, !value->data.x_bool);
    }

    return ir_build_bool_not_gen(ira, instruction->base.scope, instruction->base.source_node, casted_value);
}

static Stage1AirInst *ir_analyze_instruction_memset(IrAnalyze *ira, Stage1ZirInstMemset *instruction) {
    Error err;

    Stage1AirInst *dest_ptr = instruction->dest_ptr->child;
    if (type_is_invalid(dest_ptr->value->type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *byte_value = instruction->byte->child;
    if (type_is_invalid(byte_value->value->type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *count_value = instruction->count->child;
    if (type_is_invalid(count_value->value->type))
        return ira->codegen->invalid_inst_gen;

    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_inst_gen;
    } 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, false);

    Stage1AirInst *casted_dest_ptr = ir_implicit_cast(ira, dest_ptr, u8_ptr);
    if (type_is_invalid(casted_dest_ptr->value->type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *casted_byte = ir_implicit_cast(ira, byte_value, u8);
    if (type_is_invalid(casted_byte->value->type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *casted_count = ir_implicit_cast(ira, count_value, usize);
    if (type_is_invalid(casted_count->value->type))
        return ira->codegen->invalid_inst_gen;

    // TODO test this at comptime with u8 and non-u8 types
    if (instr_is_comptime(casted_dest_ptr) &&
        instr_is_comptime(casted_byte) &&
        instr_is_comptime(casted_count))
    {
        ZigValue *dest_ptr_val = ir_resolve_const(ira, casted_dest_ptr, UndefBad);
        if (dest_ptr_val == nullptr)
            return ira->codegen->invalid_inst_gen;

        ZigValue *byte_val = ir_resolve_const(ira, casted_byte, UndefOk);
        if (byte_val == nullptr)
            return ira->codegen->invalid_inst_gen;

        ZigValue *count_val = ir_resolve_const(ira, casted_count, UndefBad);
        if (count_val == nullptr)
            return ira->codegen->invalid_inst_gen;

        if (casted_dest_ptr->value->data.x_ptr.special != ConstPtrSpecialHardCodedAddr &&
            casted_dest_ptr->value->data.x_ptr.mut != ConstPtrMutRuntimeVar)
        {
            ZigValue *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 ConstPtrSpecialSubArray:
                case ConstPtrSpecialBaseArray:
                    {
                        ZigValue *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_usize(&count_val->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_inst_gen;
            }

            for (size_t i = start; i < end; i += 1) {
                copy_const_val(ira->codegen, &dest_elements[i], byte_val);
            }

            return ir_const_void(ira, instruction->base.scope, instruction->base.source_node);
        }
    }

    return ir_build_memset_gen(ira, instruction->base.scope, instruction->base.source_node, casted_dest_ptr, casted_byte, casted_count);
}

static Stage1AirInst *ir_analyze_instruction_memcpy(IrAnalyze *ira, Stage1ZirInstMemcpy *instruction) {
    Error err;

    Stage1AirInst *dest_ptr = instruction->dest_ptr->child;
    if (type_is_invalid(dest_ptr->value->type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *src_ptr = instruction->src_ptr->child;
    if (type_is_invalid(src_ptr->value->type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *count_value = instruction->count->child;
    if (type_is_invalid(count_value->value->type))
        return ira->codegen->invalid_inst_gen;

    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_inst_gen;
    } 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_inst_gen;
    } 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, false);
    ZigType *u8_ptr_const = get_pointer_to_type_extra(ira->codegen, u8, true, src_is_volatile,
            PtrLenUnknown, src_align, 0, 0, false);

    Stage1AirInst *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_inst_gen;

    Stage1AirInst *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_inst_gen;

    Stage1AirInst *casted_count = ir_implicit_cast(ira, count_value, usize);
    if (type_is_invalid(casted_count->value->type))
        return ira->codegen->invalid_inst_gen;

    // TODO test this at comptime with u8 and non-u8 types
    // TODO test with dest ptr being a global runtime variable
    if (instr_is_comptime(casted_dest_ptr) &&
        instr_is_comptime(casted_src_ptr) &&
        instr_is_comptime(casted_count))
    {
        ZigValue *dest_ptr_val = ir_resolve_const(ira, casted_dest_ptr, UndefBad);
        if (dest_ptr_val == nullptr)
            return ira->codegen->invalid_inst_gen;

        ZigValue *src_ptr_val = ir_resolve_const(ira, casted_src_ptr, UndefBad);
        if (src_ptr_val == nullptr)
            return ira->codegen->invalid_inst_gen;

        ZigValue *count_val = ir_resolve_const(ira, casted_count, UndefBad);
        if (count_val == nullptr)
            return ira->codegen->invalid_inst_gen;

        if (dest_ptr_val->data.x_ptr.special != ConstPtrSpecialHardCodedAddr) {
            size_t count = bigint_as_usize(&count_val->data.x_bigint);

            ZigValue *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 ConstPtrSpecialSubArray:
                case ConstPtrSpecialBaseArray:
                    {
                        ZigValue *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_node(ira, instruction->base.source_node, buf_sprintf("out of bounds pointer access"));
                return ira->codegen->invalid_inst_gen;
            }

            ZigValue *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 ConstPtrSpecialSubArray:
                case ConstPtrSpecialBaseArray:
                    {
                        ZigValue *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_node(ira, instruction->base.source_node, buf_sprintf("out of bounds pointer access"));
                return ira->codegen->invalid_inst_gen;
            }

            // TODO check for noalias violations - this should be generalized to work for any function

            for (size_t i = 0; i < count; i += 1) {
                copy_const_val(ira->codegen, &dest_elements[dest_start + i], &src_elements[src_start + i]);
            }

            return ir_const_void(ira, instruction->base.scope, instruction->base.source_node);
        }
    }

    return ir_build_memcpy_gen(ira, instruction->base.scope, instruction->base.source_node, casted_dest_ptr, casted_src_ptr, casted_count);
}

static ZigType *get_result_loc_type(IrAnalyze *ira, ResultLoc *result_loc) {
    if (result_loc == nullptr) return nullptr;

    if (result_loc->id == ResultLocIdCast) {
        return ir_resolve_type(ira, result_loc->source_instruction->child);
    }

    return nullptr;
}

static Stage1AirInst *ir_analyze_instruction_slice(IrAnalyze *ira, Stage1ZirInstSlice *instruction) {
    Error err;

    Stage1AirInst *ptr_ptr = instruction->ptr->child;
    if (type_is_invalid(ptr_ptr->value->type))
        return ira->codegen->invalid_inst_gen;

    ZigType *ptr_ptr_type = ptr_ptr->value->type;
    assert(ptr_ptr_type->id == ZigTypeIdPointer);
    ZigType *array_type = ptr_ptr_type->data.pointer.child_type;

    Stage1AirInst *start = instruction->start->child;
    if (type_is_invalid(start->value->type))
        return ira->codegen->invalid_inst_gen;

    ZigType *usize = ira->codegen->builtin_types.entry_usize;
    Stage1AirInst *casted_start = ir_implicit_cast(ira, start, usize);
    if (type_is_invalid(casted_start->value->type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *end;
    if (instruction->end) {
        end = instruction->end->child;
        if (type_is_invalid(end->value->type))
            return ira->codegen->invalid_inst_gen;
        end = ir_implicit_cast(ira, end, usize);
        if (type_is_invalid(end->value->type))
            return ira->codegen->invalid_inst_gen;
    } else {
        end = nullptr;
    }

    ZigValue *slice_sentinel_val = nullptr;
    ZigType *non_sentinel_slice_ptr_type;
    ZigType *elem_type;

    bool generate_non_null_assert = false;

    if (array_type->id == ZigTypeIdArray) {
        elem_type = array_type->data.array.child_type;
        non_sentinel_slice_ptr_type = get_pointer_to_type_extra(ira->codegen, elem_type,
            ptr_ptr_type->data.pointer.is_const,
            ptr_ptr_type->data.pointer.is_volatile,
            PtrLenUnknown,
            ptr_ptr_type->data.pointer.explicit_alignment, 0, 0, false);
    } 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) {
                elem_type = main_type->data.pointer.child_type;
                non_sentinel_slice_ptr_type = get_pointer_to_type_extra(ira->codegen,
                        elem_type,
                        array_type->data.pointer.is_const, array_type->data.pointer.is_volatile,
                        PtrLenUnknown,
                        array_type->data.pointer.explicit_alignment, 0, 0, false);
            } else {
                ir_add_error_node(ira, instruction->base.source_node, buf_sprintf("slice of single-item pointer"));
                return ira->codegen->invalid_inst_gen;
            }
        } else {
            elem_type = array_type->data.pointer.child_type;
            if (array_type->data.pointer.ptr_len == PtrLenC) {
                array_type = adjust_ptr_len(ira->codegen, array_type, PtrLenUnknown);

                // C pointers are allowzero by default.
                // However, we want to be able to slice them without generating an allowzero slice (see issue #4401).
                // To achieve this, we generate a runtime safety check and make the slice type non-allowzero.
                if (array_type->data.pointer.allow_zero) {
                    array_type = adjust_ptr_allow_zero(ira->codegen, array_type, false);
                    generate_non_null_assert = true;
                }
            }
            ZigType *maybe_sentineled_slice_ptr_type = array_type;
            non_sentinel_slice_ptr_type = adjust_ptr_sentinel(ira->codegen, maybe_sentineled_slice_ptr_type, nullptr);
            if (!end) {
                ir_add_error_node(ira, instruction->base.source_node, buf_sprintf("slice of pointer must include end value"));
                return ira->codegen->invalid_inst_gen;
            }
        }
    } else if (is_slice(array_type)) {
        ZigType *maybe_sentineled_slice_ptr_type = array_type->data.structure.fields[slice_ptr_index]->type_entry;
        slice_sentinel_val = maybe_sentineled_slice_ptr_type->data.pointer.sentinel;
        non_sentinel_slice_ptr_type = adjust_ptr_sentinel(ira->codegen, maybe_sentineled_slice_ptr_type, nullptr);
        elem_type = non_sentinel_slice_ptr_type->data.pointer.child_type;
    } else {
        ir_add_error_node(ira, instruction->base.source_node,
            buf_sprintf("slice of non-array type '%s'", buf_ptr(&array_type->name)));
        return ira->codegen->invalid_inst_gen;
    }

    ZigValue *sentinel_val = nullptr;
    if (instruction->sentinel) {
        Stage1AirInst *uncasted_sentinel = instruction->sentinel->child;
        if (type_is_invalid(uncasted_sentinel->value->type))
            return ira->codegen->invalid_inst_gen;
        Stage1AirInst *sentinel = ir_implicit_cast(ira, uncasted_sentinel, elem_type);
        if (type_is_invalid(sentinel->value->type))
            return ira->codegen->invalid_inst_gen;
        sentinel_val = ir_resolve_const(ira, sentinel, UndefBad);
        if (sentinel_val == nullptr)
            return ira->codegen->invalid_inst_gen;
    }

    ZigType *child_array_type = (array_type->id == ZigTypeIdPointer &&
            array_type->data.pointer.ptr_len == PtrLenSingle) ? array_type->data.pointer.child_type : array_type;

    ZigType *return_type;

    // If start index and end index are both comptime known, then the result type is a pointer to array
    // not a slice. However, if the start or end index is a lazy value, and the result location is a slice,
    // then the pointer-to-array would be casted to a slice anyway. So, we preserve the laziness of these
    // values by making the return type a slice.
    ZigType *res_loc_type = get_result_loc_type(ira, instruction->result_loc);
    bool result_loc_is_slice = (res_loc_type != nullptr && is_slice(res_loc_type));
    bool end_is_known = !result_loc_is_slice &&
        ((end != nullptr && value_is_comptime(end->value)) ||
         (end == nullptr && child_array_type->id == ZigTypeIdArray));

    ZigValue *array_sentinel = sentinel_val;
    if (end_is_known) {
        uint64_t end_scalar;
        if (end != nullptr) {
            ZigValue *end_val = ir_resolve_const(ira, end, UndefBad);
            if (!end_val)
                return ira->codegen->invalid_inst_gen;
            end_scalar = bigint_as_u64(&end_val->data.x_bigint);
        } else {
            end_scalar = child_array_type->data.array.len;
        }
        array_sentinel = (child_array_type->id == ZigTypeIdArray && end_scalar == child_array_type->data.array.len)
            ? child_array_type->data.array.sentinel : sentinel_val;

        if (value_is_comptime(casted_start->value)) {
            ZigValue *start_val = ir_resolve_const(ira, casted_start, UndefBad);
            if (!start_val)
                return ira->codegen->invalid_inst_gen;

            uint64_t start_scalar = bigint_as_u64(&start_val->data.x_bigint);

            if (start_scalar > end_scalar) {
                ir_add_error_node(ira, instruction->base.source_node, buf_sprintf("out of bounds slice"));
                return ira->codegen->invalid_inst_gen;
            }

            uint32_t base_ptr_align = non_sentinel_slice_ptr_type->data.pointer.explicit_alignment;
            uint32_t ptr_byte_alignment = 0;
            if (end_scalar > start_scalar) {
                if ((err = compute_elem_align(ira, elem_type, base_ptr_align, start_scalar, &ptr_byte_alignment)))
                    return ira->codegen->invalid_inst_gen;
            }

            ZigType *return_array_type = get_array_type(ira->codegen, elem_type, end_scalar - start_scalar,
                    array_sentinel);
            return_type = get_pointer_to_type_extra(ira->codegen, return_array_type,
                    non_sentinel_slice_ptr_type->data.pointer.is_const,
                    non_sentinel_slice_ptr_type->data.pointer.is_volatile,
                    PtrLenSingle, ptr_byte_alignment, 0, 0, false);
            goto done_with_return_type;
        }
    } else if (array_sentinel == nullptr && end == nullptr) {
        array_sentinel = slice_sentinel_val;
    }
    if (array_sentinel != nullptr) {
        // TODO deal with non-abi-alignment here
        ZigType *slice_ptr_type = adjust_ptr_sentinel(ira->codegen, non_sentinel_slice_ptr_type, array_sentinel);
        return_type = get_slice_type(ira->codegen, slice_ptr_type);
    } else {
        // TODO deal with non-abi-alignment here
        return_type = get_slice_type(ira->codegen, non_sentinel_slice_ptr_type);
    }
done_with_return_type:

    if (instr_is_comptime(ptr_ptr) &&
        value_is_comptime(casted_start->value) &&
        (!end || value_is_comptime(end->value)))
    {
        ZigValue *array_val;
        ZigValue *parent_ptr;
        size_t abs_offset;
        size_t rel_end;
        bool ptr_is_undef = false;
        if (child_array_type->id == ZigTypeIdArray) {
            if (array_type->id == ZigTypeIdPointer) {
                parent_ptr = const_ptr_pointee(ira, ira->codegen, ptr_ptr->value, instruction->base.source_node);
                if (parent_ptr == nullptr)
                    return ira->codegen->invalid_inst_gen;

                if (parent_ptr->special == ConstValSpecialUndef) {
                    array_val = nullptr;
                    abs_offset = 0;
                    rel_end = SIZE_MAX;
                    ptr_is_undef = true;
                } else if (parent_ptr->data.x_ptr.special == ConstPtrSpecialHardCodedAddr) {
                    array_val = nullptr;
                    abs_offset = 0;
                    rel_end = SIZE_MAX;
                } else {
                    array_val = const_ptr_pointee(ira, ira->codegen, parent_ptr, instruction->base.source_node);
                    if (array_val == nullptr)
                        return ira->codegen->invalid_inst_gen;

                    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_inst_gen;
                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_inst_gen;

            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 ConstPtrSpecialSubArray:
                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)) {
            ZigValue *slice_ptr = const_ptr_pointee(ira, ira->codegen, ptr_ptr->value, instruction->base.source_node);
            if (slice_ptr == nullptr)
                return ira->codegen->invalid_inst_gen;

            if (slice_ptr->special == ConstValSpecialUndef) {
                ir_add_error_node(ira, instruction->base.source_node, buf_sprintf("slice of undefined"));
                return ira->codegen->invalid_inst_gen;
            }

            parent_ptr = slice_ptr->data.x_struct.fields[slice_ptr_index];
            if (parent_ptr->special == ConstValSpecialUndef) {
                ir_add_error_node(ira, instruction->base.source_node, buf_sprintf("slice of undefined"));
                return ira->codegen->invalid_inst_gen;
            }

            ZigValue *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 ConstPtrSpecialSubArray:
                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_usize(&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_usize(&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();
        }

        ZigValue *start_val = ir_resolve_const(ira, casted_start, UndefBad);
        if (!start_val)
            return ira->codegen->invalid_inst_gen;

        uint64_t start_scalar = bigint_as_u64(&start_val->data.x_bigint);
        if (!ptr_is_undef && start_scalar > rel_end) {
            ir_add_error_node(ira, instruction->base.source_node, buf_sprintf("out of bounds slice"));
            return ira->codegen->invalid_inst_gen;
        }

        uint64_t end_scalar = rel_end;
        if (end) {
            ZigValue *end_val = ir_resolve_const(ira, end, UndefBad);
            if (!end_val)
                return ira->codegen->invalid_inst_gen;
            end_scalar = bigint_as_u64(&end_val->data.x_bigint);
        }
        if (!ptr_is_undef) {
            if (end_scalar > rel_end) {
                ir_add_error_node(ira, instruction->base.source_node, buf_sprintf("out of bounds slice"));
                return ira->codegen->invalid_inst_gen;
            }
            if (start_scalar > end_scalar) {
                ir_add_error_node(ira, instruction->base.source_node, buf_sprintf("slice start is greater than end"));
                return ira->codegen->invalid_inst_gen;
            }
        }
        if (ptr_is_undef && start_scalar != end_scalar) {
            ir_add_error_node(ira, instruction->base.source_node, buf_sprintf("non-zero length slice of undefined pointer"));
            return ira->codegen->invalid_inst_gen;
        }

        // check sentinel when target is comptime-known
        {
            if (!sentinel_val)
                goto exit_check_sentinel;

            switch (ptr_ptr->value->data.x_ptr.mut) {
                case ConstPtrMutComptimeConst:
                case ConstPtrMutComptimeVar:
                    break;
                case ConstPtrMutRuntimeVar:
                case ConstPtrMutInfer:
                    goto exit_check_sentinel;
            }

            // prepare check parameters
            ZigValue *target = const_ptr_pointee(ira, ira->codegen, ptr_ptr->value, instruction->base.source_node);
            if (target == nullptr)
                return ira->codegen->invalid_inst_gen;

            uint64_t target_len = 0;
            ZigValue *target_sentinel = nullptr;
            ZigValue *target_elements = nullptr;

            for (;;) {
                if (target->type->id == ZigTypeIdArray) {
                    // handle `[N]T`
                    target_len = target->type->data.array.len;
                    target_sentinel = target->type->data.array.sentinel;
                    target_elements = target->data.x_array.data.s_none.elements;
                    break;
                } else if (target->type->id == ZigTypeIdPointer && target->type->data.pointer.child_type->id == ZigTypeIdArray) {
                    // handle `*[N]T`
                    target = const_ptr_pointee(ira, ira->codegen, target, instruction->base.source_node);
                    if (target == nullptr)
                        return ira->codegen->invalid_inst_gen;
                    assert(target->type->id == ZigTypeIdArray);
                    continue;
                } else if (target->type->id == ZigTypeIdPointer) {
                    // handle `[*]T`
                    // handle `[*c]T`
                    switch (target->data.x_ptr.special) {
                        case ConstPtrSpecialInvalid:
                        case ConstPtrSpecialDiscard:
                            zig_unreachable();
                        case ConstPtrSpecialRef:
                            target = target->data.x_ptr.data.ref.pointee;
                            assert(target->type->id == ZigTypeIdArray);
                            continue;
                        case ConstPtrSpecialBaseArray:
                        case ConstPtrSpecialSubArray:
                            target = target->data.x_ptr.data.base_array.array_val;
                            assert(target->type->id == ZigTypeIdArray);
                            continue;
                        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:
                            // skip check
                            goto exit_check_sentinel;
                        case ConstPtrSpecialFunction:
                            zig_panic("TODO slice of ptr cast from function");
                        case ConstPtrSpecialNull:
                            zig_panic("TODO slice of null ptr");
                    }
                    break;
                } else if (is_slice(target->type)) {
                    // handle `[]T`
                    target = target->data.x_struct.fields[slice_ptr_index];
                    assert(target->type->id == ZigTypeIdPointer);
                    continue;
                }

                zig_unreachable();
            }

            // perform check
            if (target_sentinel == nullptr) {
                if (end_scalar >= target_len) {
                    ir_add_error_node(ira, instruction->base.source_node, buf_sprintf("slice-sentinel is out of bounds"));
                    return ira->codegen->invalid_inst_gen;
                }
                if (!const_values_equal(ira->codegen, sentinel_val, &target_elements[end_scalar])) {
                    ir_add_error_node(ira, instruction->base.source_node, buf_sprintf("slice-sentinel does not match memory at target index"));
                    return ira->codegen->invalid_inst_gen;
                }
            } else {
                assert(end_scalar <= target_len);
                if (end_scalar == target_len) {
                    if (!const_values_equal(ira->codegen, sentinel_val, target_sentinel)) {
                        ir_add_error_node(ira, instruction->base.source_node, buf_sprintf("slice-sentinel does not match target-sentinel"));
                        return ira->codegen->invalid_inst_gen;
                    }
                } else {
                    if (!const_values_equal(ira->codegen, sentinel_val, &target_elements[end_scalar])) {
                        ir_add_error_node(ira, instruction->base.source_node, buf_sprintf("slice-sentinel does not match memory at target index"));
                        return ira->codegen->invalid_inst_gen;
                    }
                }
            }
        }
        exit_check_sentinel:

        Stage1AirInst *result = ir_const(ira, instruction->base.scope, instruction->base.source_node, return_type);

        ZigValue *ptr_val;
        if (return_type->id == ZigTypeIdPointer) {
            // pointer to array
            ptr_val = result->value;
        } else {
            // slice
            result->value->data.x_struct.fields = alloc_const_vals_ptrs(ira->codegen, 2);

            ptr_val = result->value->data.x_struct.fields[slice_ptr_index];

            ZigValue *len_val = result->value->data.x_struct.fields[slice_len_index];
            init_const_usize(ira->codegen, len_val, end_scalar - start_scalar);
        }

        bool return_type_is_const = non_sentinel_slice_ptr_type->data.pointer.is_const;
        if (array_val) {
            size_t index = abs_offset + start_scalar;
            init_const_ptr_array(ira->codegen, ptr_val, array_val, index, return_type_is_const, PtrLenUnknown);
            if (return_type->id == ZigTypeIdPointer) {
                ptr_val->data.x_ptr.special = ConstPtrSpecialSubArray;
            }
            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,
                return_type_is_const);
            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,
                        return_type_is_const);
                break;
            case ConstPtrSpecialSubArray:
            case ConstPtrSpecialBaseArray:
                zig_unreachable();
            case ConstPtrSpecialBaseStruct:
                zig_panic("TODO: ir_analyze_instruction_slice ConstPtrSpecialBaseStruct");
            case ConstPtrSpecialBaseErrorUnionCode:
                zig_panic("TODO: ir_analyze_instruction_slice ConstPtrSpecialBaseErrorUnionCode");
            case ConstPtrSpecialBaseErrorUnionPayload:
                zig_panic("TODO: ir_analyze_instruction_slice ConstPtrSpecialBaseErrorUnionPayload");
            case ConstPtrSpecialBaseOptionalPayload:
                zig_panic("TODO: ir_analyze_instruction_slice ConstPtrSpecialBaseOptionalPayload");
            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,
                    return_type_is_const);
                break;
            case ConstPtrSpecialFunction:
                zig_panic("TODO: ir_analyze_instruction_slice ConstPtrSpecialFunction");
            case ConstPtrSpecialNull:
                zig_panic("TODO: ir_analyze_instruction_slice ConstPtrSpecialNull");
        }

        // In the case of pointer-to-array, we must restore this because above it overwrites ptr_val->type
        result->value->type = return_type;
        return result;
    }

    if (generate_non_null_assert) {
        Stage1AirInst *ptr_val = ir_get_deref(ira, instruction->base.scope,
                instruction->base.source_node, ptr_ptr, nullptr);

        if (type_is_invalid(ptr_val->value->type))
            return ira->codegen->invalid_inst_gen;

        ir_build_assert_non_null(ira, instruction->base.scope, instruction->base.source_node, ptr_val);
    }

    Stage1AirInst *result_loc = nullptr;

    if (return_type->id != ZigTypeIdPointer) {
        result_loc = ir_resolve_result(ira, &instruction->base, instruction->result_loc,
                return_type, nullptr, true, true);
        if (result_loc != nullptr) {
            if (type_is_invalid(result_loc->value->type) || result_loc->value->type->id == ZigTypeIdUnreachable) {
                return result_loc;
            }

            src_assert(result_loc->value->type->id == ZigTypeIdPointer, instruction->base.source_node);
            if (result_loc->value->type->data.pointer.is_const) {
                ir_add_error_node(ira, instruction->base.source_node, buf_sprintf("cannot assign to constant"));
                return ira->codegen->invalid_inst_gen;
            }

            Stage1AirInst *dummy_value = ir_const(ira, instruction->base.scope, instruction->base.source_node, return_type);
            dummy_value->value->special = ConstValSpecialRuntime;
            Stage1AirInst *dummy_result = ir_implicit_cast2(ira,
                    instruction->base.scope, instruction->base.source_node,
                    dummy_value, result_loc->value->type->data.pointer.child_type);
            if (type_is_invalid(dummy_result->value->type))
                return ira->codegen->invalid_inst_gen;
        }
    }

    return ir_build_slice_gen(ira, instruction->base.scope, instruction->base.source_node, return_type, ptr_ptr,
            casted_start, end, instruction->safety_check_on, result_loc, sentinel_val);
}

static Stage1AirInst *ir_analyze_instruction_has_field(IrAnalyze *ira, Stage1ZirInstHasField *instruction) {
    Error err;
    ZigType *container_type = ir_resolve_type(ira, instruction->container_type->child);
    if (type_is_invalid(container_type))
        return ira->codegen->invalid_inst_gen;

    if ((err = type_resolve(ira->codegen, container_type, ResolveStatusZeroBitsKnown)))
        return ira->codegen->invalid_inst_gen;

    Buf *field_name = ir_resolve_str(ira, instruction->field_name->child);
    if (field_name == nullptr)
        return ira->codegen->invalid_inst_gen;

    bool result;
    if (container_type->id == ZigTypeIdStruct) {
        result = find_struct_type_field(container_type, field_name) != nullptr;
    } else if (container_type->id == ZigTypeIdEnum) {
        result = find_enum_type_field(container_type, field_name) != nullptr;
    } else if (container_type->id == ZigTypeIdUnion) {
        result = find_union_type_field(container_type, field_name) != nullptr;
    } else {
        ir_add_error_node(ira, instruction->container_type->source_node,
            buf_sprintf("type '%s' does not support @hasField", buf_ptr(&container_type->name)));
        return ira->codegen->invalid_inst_gen;
    }
    return ir_const_bool(ira, instruction->base.scope, instruction->base.source_node, result);
}

static Stage1AirInst *ir_analyze_instruction_wasm_memory_size(IrAnalyze *ira, Stage1ZirInstWasmMemorySize *instruction) {
    // TODO generate compile error for target_arch different than 32bit
    if (!target_is_wasm(ira->codegen->zig_target)) {
        ir_add_error_node(ira, instruction->base.source_node,
            buf_sprintf("@wasmMemorySize is a wasm32 feature only"));
        return ira->codegen->invalid_inst_gen;
    }

    Stage1AirInst *index = instruction->index->child;
    if (type_is_invalid(index->value->type))
        return ira->codegen->invalid_inst_gen;

    ZigType *u32 = ira->codegen->builtin_types.entry_u32;

    Stage1AirInst *casted_index = ir_implicit_cast(ira, index, u32);
    if (type_is_invalid(casted_index->value->type))
        return ira->codegen->invalid_inst_gen;

    return ir_build_wasm_memory_size_gen(ira, instruction->base.scope, instruction->base.source_node, casted_index);
}

static Stage1AirInst *ir_analyze_instruction_wasm_memory_grow(IrAnalyze *ira, Stage1ZirInstWasmMemoryGrow *instruction) {
    // TODO generate compile error for target_arch different than 32bit
    if (!target_is_wasm(ira->codegen->zig_target)) {
        ir_add_error_node(ira, instruction->base.source_node,
            buf_sprintf("@wasmMemoryGrow is a wasm32 feature only"));
        return ira->codegen->invalid_inst_gen;
    }

    Stage1AirInst *index = instruction->index->child;
    if (type_is_invalid(index->value->type))
        return ira->codegen->invalid_inst_gen;

    ZigType *u32 = ira->codegen->builtin_types.entry_u32;

    Stage1AirInst *casted_index = ir_implicit_cast(ira, index, u32);
    if (type_is_invalid(casted_index->value->type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *delta = instruction->delta->child;
    if (type_is_invalid(delta->value->type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *casted_delta = ir_implicit_cast(ira, delta, u32);
    if (type_is_invalid(casted_delta->value->type))
        return ira->codegen->invalid_inst_gen;

    return ir_build_wasm_memory_grow_gen(ira, instruction->base.scope, instruction->base.source_node, casted_index, casted_delta);
}

static Stage1AirInst *ir_analyze_instruction_breakpoint(IrAnalyze *ira, Stage1ZirInstBreakpoint *instruction) {
    return ir_build_breakpoint_gen(ira, instruction->base.scope, instruction->base.source_node);
}

static Stage1AirInst *ir_analyze_instruction_return_address(IrAnalyze *ira, Stage1ZirInstReturnAddress *instruction) {
    return ir_build_return_address_gen(ira, instruction->base.scope, instruction->base.source_node);
}

static Stage1AirInst *ir_analyze_instruction_frame_address(IrAnalyze *ira, Stage1ZirInstFrameAddress *instruction) {
    return ir_build_frame_address_gen(ira, instruction->base.scope, instruction->base.source_node);
}

static Stage1AirInst *ir_analyze_instruction_frame_handle(IrAnalyze *ira, Stage1ZirInstFrameHandle *instruction) {
    ZigFn *fn = ira->fn;
    src_assert(fn != nullptr, instruction->base.source_node);

    if (fn->inferred_async_node == nullptr) {
        fn->inferred_async_node = instruction->base.source_node;
    }

    ZigType *frame_type = get_fn_frame_type(ira->codegen, fn);
    ZigType *ptr_frame_type = get_pointer_to_type(ira->codegen, frame_type, false);

    return ir_build_handle_gen(ira, instruction->base.scope, instruction->base.source_node, ptr_frame_type);
}

static Stage1AirInst *ir_analyze_instruction_frame_type(IrAnalyze *ira, Stage1ZirInstFrameType *instruction) {
    ZigFn *fn = ir_resolve_fn(ira, instruction->fn->child);
    if (fn == nullptr)
        return ira->codegen->invalid_inst_gen;

    if (fn->type_entry->data.fn.is_generic) {
        ir_add_error_node(ira, instruction->base.source_node,
                buf_sprintf("@Frame() of generic function"));
        return ira->codegen->invalid_inst_gen;
    }

    ZigType *ty = get_fn_frame_type(ira->codegen, fn);
    return ir_const_type(ira, instruction->base.scope, instruction->base.source_node, ty);
}

static Stage1AirInst *ir_analyze_instruction_frame_size(IrAnalyze *ira, Stage1ZirInstFrameSize *instruction) {
    Stage1AirInst *fn = instruction->fn->child;
    if (type_is_invalid(fn->value->type))
        return ira->codegen->invalid_inst_gen;

    if (fn->value->type->id != ZigTypeIdFn) {
        ir_add_error(ira, fn,
                buf_sprintf("expected function, found '%s'", buf_ptr(&fn->value->type->name)));
        return ira->codegen->invalid_inst_gen;
    }

    ira->codegen->need_frame_size_prefix_data = true;

    return ir_build_frame_size_gen(ira, instruction->base.scope, instruction->base.source_node, fn);
}

static Stage1AirInst *ir_analyze_instruction_align_of(IrAnalyze *ira, Stage1ZirInstAlignOf *instruction) {
    // Here we create a lazy value in order to avoid resolving the alignment of the type
    // immediately. This avoids false positive dependency loops such as:
    // const Node = struct {
    //     field: []align(@alignOf(Node)) Node,
    // };
    Stage1AirInst *result = ir_const(ira, instruction->base.scope, instruction->base.source_node, ira->codegen->builtin_types.entry_num_lit_int);
    result->value->special = ConstValSpecialLazy;

    LazyValueAlignOf *lazy_align_of = heap::c_allocator.create<LazyValueAlignOf>();
    lazy_align_of->ira = ira; ira_ref(ira);
    result->value->data.x_lazy = &lazy_align_of->base;
    lazy_align_of->base.id = LazyValueIdAlignOf;

    lazy_align_of->target_type = instruction->type_value->child;
    if (ir_resolve_type_lazy(ira, lazy_align_of->target_type) == nullptr)
        return ira->codegen->invalid_inst_gen;

    return result;
}

static Stage1AirInst *ir_analyze_instruction_overflow_op(IrAnalyze *ira, Stage1ZirInstOverflowOp *instruction) {
    Error err;

    Stage1AirInst *type_value = instruction->type_value->child;
    if (type_is_invalid(type_value->value->type))
        return ira->codegen->invalid_inst_gen;

    ZigType *dest_type = ir_resolve_type(ira, type_value);
    if (type_is_invalid(dest_type))
        return ira->codegen->invalid_inst_gen;

    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_inst_gen;
    }

    Stage1AirInst *op1 = instruction->op1->child;
    if (type_is_invalid(op1->value->type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *casted_op1 = ir_implicit_cast(ira, op1, dest_type);
    if (type_is_invalid(casted_op1->value->type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *op2 = instruction->op2->child;
    if (type_is_invalid(op2->value->type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *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_inst_gen;

    Stage1AirInst *result_ptr = instruction->result_ptr->child;
    if (type_is_invalid(result_ptr->value->type))
        return ira->codegen->invalid_inst_gen;

    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_inst_gen;
        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, false);
    } else {
        expected_ptr_type = get_pointer_to_type(ira->codegen, dest_type, false);
    }

    Stage1AirInst *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_inst_gen;

    // Don't write anything to the result pointer.
    if (dest_type->data.integral.bit_count == 0)
        return ir_const_bool(ira, instruction->base.scope, instruction->base.source_node, false);

    if (instr_is_comptime(casted_op1) &&
        instr_is_comptime(casted_op2) &&
        instr_is_comptime(casted_result_ptr))
    {
        ZigValue *op1_val = ir_resolve_const(ira, casted_op1, UndefBad);
        if (op1_val == nullptr)
            return ira->codegen->invalid_inst_gen;

        ZigValue *op2_val = ir_resolve_const(ira, casted_op2, UndefBad);
        if (op2_val == nullptr)
            return ira->codegen->invalid_inst_gen;

        ZigValue *result_val = ir_resolve_const(ira, casted_result_ptr, UndefBad);
        if (result_val == nullptr)
            return ira->codegen->invalid_inst_gen;

        BigInt *op1_bigint = &op1_val->data.x_bigint;
        BigInt *op2_bigint = &op2_val->data.x_bigint;
        ZigValue *pointee_val = const_ptr_pointee(ira, ira->codegen, result_val,
                casted_result_ptr->source_node);
        if (pointee_val == nullptr)
            return ira->codegen->invalid_inst_gen;
        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.scope, instruction->base.source_node, result_bool);
    }

    return ir_build_overflow_op_gen(ira, instruction->base.scope, instruction->base.source_node, instruction->op,
            casted_op1, casted_op2, casted_result_ptr, dest_type);
}

static void ir_eval_mul_add(IrAnalyze *ira, ZigType *float_type,
    ZigValue *op1, ZigValue *op2, ZigValue *op3, ZigValue *out_val) {
    if (float_type->id == ZigTypeIdComptimeFloat) {
        f128M_mulAdd(&out_val->data.x_bigfloat.value, &op1->data.x_bigfloat.value, &op2->data.x_bigfloat.value,
            &op3->data.x_bigfloat.value);
    } else if (float_type->id == ZigTypeIdFloat) {
        switch (float_type->data.floating.bit_count) {
            case 16:
                out_val->data.x_f16 = f16_mulAdd(op1->data.x_f16, op2->data.x_f16, op3->data.x_f16);
                break;
            case 32:
                out_val->data.x_f32 = fmaf(op1->data.x_f32, op2->data.x_f32, op3->data.x_f32);
                break;
            case 64:
                out_val->data.x_f64 = fma(op1->data.x_f64, op2->data.x_f64, op3->data.x_f64);
                break;
            case 128:
                f128M_mulAdd(&op1->data.x_f128, &op2->data.x_f128, &op3->data.x_f128, &out_val->data.x_f128);
                break;
            default:
                zig_unreachable();
        }
    } else {
        zig_unreachable();
    }
}

static Stage1AirInst *ir_analyze_instruction_mul_add(IrAnalyze *ira, Stage1ZirInstMulAdd *instruction) {
    Stage1AirInst *type_value = instruction->type_value->child;
    if (type_is_invalid(type_value->value->type))
        return ira->codegen->invalid_inst_gen;

    ZigType *expr_type = ir_resolve_type(ira, type_value);
    if (type_is_invalid(expr_type))
        return ira->codegen->invalid_inst_gen;

    // Only allow float types, and vectors of floats.
    ZigType *float_type = (expr_type->id == ZigTypeIdVector) ? expr_type->data.vector.elem_type : expr_type;
    if (float_type->id != ZigTypeIdFloat) {
        ir_add_error(ira, type_value,
            buf_sprintf("expected float or vector of float type, found '%s'", buf_ptr(&float_type->name)));
        return ira->codegen->invalid_inst_gen;
    }

    Stage1AirInst *op1 = instruction->op1->child;
    if (type_is_invalid(op1->value->type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *casted_op1 = ir_implicit_cast(ira, op1, expr_type);
    if (type_is_invalid(casted_op1->value->type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *op2 = instruction->op2->child;
    if (type_is_invalid(op2->value->type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *casted_op2 = ir_implicit_cast(ira, op2, expr_type);
    if (type_is_invalid(casted_op2->value->type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *op3 = instruction->op3->child;
    if (type_is_invalid(op3->value->type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *casted_op3 = ir_implicit_cast(ira, op3, expr_type);
    if (type_is_invalid(casted_op3->value->type))
        return ira->codegen->invalid_inst_gen;

    if (instr_is_comptime(casted_op1) &&
        instr_is_comptime(casted_op2) &&
        instr_is_comptime(casted_op3)) {
        ZigValue *op1_const = ir_resolve_const(ira, casted_op1, UndefBad);
        if (!op1_const)
            return ira->codegen->invalid_inst_gen;
        ZigValue *op2_const = ir_resolve_const(ira, casted_op2, UndefBad);
        if (!op2_const)
            return ira->codegen->invalid_inst_gen;
        ZigValue *op3_const = ir_resolve_const(ira, casted_op3, UndefBad);
        if (!op3_const)
            return ira->codegen->invalid_inst_gen;

        Stage1AirInst *result = ir_const(ira, instruction->base.scope, instruction->base.source_node, expr_type);
        ZigValue *out_val = result->value;

        if (expr_type->id == ZigTypeIdVector) {
            expand_undef_array(ira->codegen, op1_const);
            expand_undef_array(ira->codegen, op2_const);
            expand_undef_array(ira->codegen, op3_const);
            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) {
                ZigValue *float_operand_op1 = &op1_const->data.x_array.data.s_none.elements[i];
                ZigValue *float_operand_op2 = &op2_const->data.x_array.data.s_none.elements[i];
                ZigValue *float_operand_op3 = &op3_const->data.x_array.data.s_none.elements[i];
                ZigValue *float_out_val = &out_val->data.x_array.data.s_none.elements[i];
                assert(float_operand_op1->type == float_type);
                assert(float_operand_op2->type == float_type);
                assert(float_operand_op3->type == float_type);
                assert(float_out_val->type == float_type);
                ir_eval_mul_add(ira, float_type,
                        op1_const, op2_const, op3_const, float_out_val);
                float_out_val->type = float_type;
            }
            out_val->type = expr_type;
            out_val->special = ConstValSpecialStatic;
        } else {
            ir_eval_mul_add(ira, float_type, op1_const, op2_const, op3_const, out_val);
        }
        return result;
    }

    return ir_build_mul_add_gen(ira, instruction->base.scope, instruction->base.source_node, casted_op1, casted_op2, casted_op3, expr_type);
}

static Stage1AirInst *ir_analyze_instruction_test_err(IrAnalyze *ira, Stage1ZirInstTestErr *instruction) {
    Stage1AirInst *base_ptr = instruction->base_ptr->child;
    if (type_is_invalid(base_ptr->value->type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *value;
    if (instruction->base_ptr_is_payload) {
        value = base_ptr;
    } else {
        value = ir_get_deref(ira, instruction->base.scope, instruction->base.source_node,
                base_ptr, nullptr);
    }

    ZigType *type_entry = value->value->type;
    if (type_is_invalid(type_entry))
        return ira->codegen->invalid_inst_gen;
    if (type_entry->id == ZigTypeIdErrorUnion) {
        if (instr_is_comptime(value)) {
            ZigValue *err_union_val = ir_resolve_const(ira, value, UndefBad);
            if (!err_union_val)
                return ira->codegen->invalid_inst_gen;

            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.scope, instruction->base.source_node, (err != nullptr));
            }
        }

        if (instruction->resolve_err_set) {
            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_inst_gen;
            }
            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.incomplete);
                return ir_const_bool(ira, instruction->base.scope, instruction->base.source_node, false);
            }
        }

        return ir_build_test_err_gen(ira, instruction->base.scope, instruction->base.source_node, value);
    } else if (type_entry->id == ZigTypeIdErrorSet) {
        return ir_const_bool(ira, instruction->base.scope, instruction->base.source_node, true);
    } else {
        return ir_const_bool(ira, instruction->base.scope, instruction->base.source_node, false);
    }
}

static Stage1AirInst *ir_analyze_unwrap_err_code(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        Stage1AirInst *base_ptr, bool initializing)
{
    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_inst_gen;

    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_inst_gen;
    }

    ZigType *err_set_type = type_entry->data.error_union.err_set_type;
    ZigType *result_type = get_pointer_to_type_extra(ira->codegen, err_set_type,
            ptr_type->data.pointer.is_const, ptr_type->data.pointer.is_volatile, PtrLenSingle,
            ptr_type->data.pointer.explicit_alignment, 0, 0, false);

    if (instr_is_comptime(base_ptr)) {
        ZigValue *ptr_val = ir_resolve_const(ira, base_ptr, UndefBad);
        if (!ptr_val)
            return ira->codegen->invalid_inst_gen;
        if (ptr_val->data.x_ptr.mut != ConstPtrMutRuntimeVar &&
            ptr_val->data.x_ptr.special != ConstPtrSpecialHardCodedAddr)
        {
            ZigValue *err_union_val = const_ptr_pointee(ira, ira->codegen, ptr_val, source_node);
            if (err_union_val == nullptr)
                return ira->codegen->invalid_inst_gen;

            if (initializing && err_union_val->special == ConstValSpecialUndef) {
                ZigValue *vals = ira->codegen->pass1_arena->allocate<ZigValue>(2);
                ZigValue *err_set_val = &vals[0];
                ZigValue *payload_val = &vals[1];

                err_set_val->special = ConstValSpecialUndef;
                err_set_val->type = err_set_type;
                err_set_val->parent.id = ConstParentIdErrUnionCode;
                err_set_val->parent.data.p_err_union_code.err_union_val = err_union_val;

                payload_val->special = ConstValSpecialUndef;
                payload_val->type = type_entry->data.error_union.payload_type;
                payload_val->parent.id = ConstParentIdErrUnionPayload;
                payload_val->parent.data.p_err_union_payload.err_union_val = err_union_val;

                err_union_val->special = ConstValSpecialStatic;
                err_union_val->data.x_err_union.error_set = err_set_val;
                err_union_val->data.x_err_union.payload = payload_val;
            }
            src_assert(err_union_val->special != ConstValSpecialRuntime, source_node);

            Stage1AirInst *result;
            if (ptr_val->data.x_ptr.mut == ConstPtrMutInfer) {
                result = ir_build_unwrap_err_code_gen(ira, scope,
                        source_node, base_ptr, result_type);
                result->value->special = ConstValSpecialStatic;
            } else {
                result = ir_const(ira, scope, source_node, result_type);
            }
            ZigValue *const_val = result->value;
            const_val->data.x_ptr.special = ConstPtrSpecialBaseErrorUnionCode;
            const_val->data.x_ptr.data.base_err_union_code.err_union_val = err_union_val;
            const_val->data.x_ptr.mut = ptr_val->data.x_ptr.mut;
            return result;
        }
    }

    return ir_build_unwrap_err_code_gen(ira, scope, source_node, base_ptr, result_type);
}

static Stage1AirInst *ir_analyze_instruction_unwrap_err_code(IrAnalyze *ira, Stage1ZirInstUnwrapErrCode *instruction) {
    Stage1AirInst *base_ptr = instruction->err_union_ptr->child;
    if (type_is_invalid(base_ptr->value->type))
        return ira->codegen->invalid_inst_gen;
    return ir_analyze_unwrap_err_code(ira, instruction->base.scope, instruction->base.source_node, base_ptr, false);
}

static Stage1AirInst *ir_analyze_unwrap_error_payload(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        Stage1AirInst *base_ptr, bool safety_check_on, bool initializing)
{
    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_inst_gen;

    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_inst_gen;
    }

    ZigType *payload_type = type_entry->data.error_union.payload_type;
    if (type_is_invalid(payload_type))
        return ira->codegen->invalid_inst_gen;

    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, false);

    if (instr_is_comptime(base_ptr)) {
        ZigValue *ptr_val = ir_resolve_const(ira, base_ptr, UndefBad);
        if (!ptr_val)
            return ira->codegen->invalid_inst_gen;
        if (ptr_val->data.x_ptr.mut != ConstPtrMutRuntimeVar) {
            ZigValue *err_union_val = const_ptr_pointee(ira, ira->codegen, ptr_val, source_node);
            if (err_union_val == nullptr)
                return ira->codegen->invalid_inst_gen;
            if (initializing && err_union_val->special == ConstValSpecialUndef) {
                ZigValue *vals = ira->codegen->pass1_arena->allocate<ZigValue>(2);
                ZigValue *err_set_val = &vals[0];
                ZigValue *payload_val = &vals[1];

                err_set_val->special = ConstValSpecialStatic;
                err_set_val->type = type_entry->data.error_union.err_set_type;
                err_set_val->data.x_err_set = nullptr;

                payload_val->special = ConstValSpecialUndef;
                payload_val->type = payload_type;

                err_union_val->special = ConstValSpecialStatic;
                err_union_val->data.x_err_union.error_set = err_set_val;
                err_union_val->data.x_err_union.payload = payload_val;
            }

            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_node(ira, source_node,
                                 buf_sprintf("caught unexpected error '%s'", buf_ptr(&err->name)));
                    return ira->codegen->invalid_inst_gen;
                }

                Stage1AirInst *result;
                if (ptr_val->data.x_ptr.mut == ConstPtrMutInfer) {
                    result = ir_build_unwrap_err_payload_gen(ira, scope,
                        source_node, base_ptr, safety_check_on, initializing, result_type);
                    result->value->special = ConstValSpecialStatic;
                } else {
                    result = ir_const(ira, scope, source_node, 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;
                result->value->data.x_ptr.mut = ptr_val->data.x_ptr.mut;
                return result;
            }
        }
    }

    return ir_build_unwrap_err_payload_gen(ira, scope, source_node,
            base_ptr, safety_check_on, initializing, result_type);
}

static Stage1AirInst *ir_analyze_instruction_unwrap_err_payload(IrAnalyze *ira,
    Stage1ZirInstUnwrapErrPayload *instruction)
{
    assert(instruction->value->child);
    Stage1AirInst *value = instruction->value->child;
    if (type_is_invalid(value->value->type))
        return ira->codegen->invalid_inst_gen;

    return ir_analyze_unwrap_error_payload(ira, instruction->base.scope, instruction->base.source_node, value, instruction->safety_check_on, false);
}

static Stage1AirInst *ir_analyze_instruction_fn_proto(IrAnalyze *ira, Stage1ZirInstFnProto *instruction) {
    AstNode *proto_node = instruction->base.source_node;
    assert(proto_node->type == NodeTypeFnProto);

    Stage1AirInst *result = ir_const(ira, instruction->base.scope, instruction->base.source_node, ira->codegen->builtin_types.entry_type);
    result->value->special = ConstValSpecialLazy;

    LazyValueFnType *lazy_fn_type = heap::c_allocator.create<LazyValueFnType>();
    lazy_fn_type->ira = ira; ira_ref(ira);
    result->value->data.x_lazy = &lazy_fn_type->base;
    lazy_fn_type->base.id = LazyValueIdFnType;

    if (proto_node->data.fn_proto.auto_err_set) {
        ir_add_error_node(ira, instruction->base.source_node,
            buf_sprintf("inferring error set of return type valid only for function definitions"));
        return ira->codegen->invalid_inst_gen;
    }

    lazy_fn_type->cc = cc_from_fn_proto(&proto_node->data.fn_proto);
    if (instruction->callconv_value != nullptr) {
        ZigType *cc_enum_type = get_builtin_type(ira->codegen, "CallingConvention");

        Stage1AirInst *casted_value = ir_implicit_cast(ira, instruction->callconv_value->child, cc_enum_type);
        if (type_is_invalid(casted_value->value->type))
            return ira->codegen->invalid_inst_gen;

        ZigValue *const_value = ir_resolve_const(ira, casted_value, UndefBad);
        if (const_value == nullptr)
            return ira->codegen->invalid_inst_gen;

        lazy_fn_type->cc = (CallingConvention)bigint_as_u32(&const_value->data.x_enum_tag);
    }

    size_t param_count = proto_node->data.fn_proto.params.length;
    lazy_fn_type->proto_node = proto_node;
    lazy_fn_type->param_types = heap::c_allocator.allocate<Stage1AirInst *>(param_count);

    for (size_t param_index = 0; param_index < param_count; param_index += 1) {
        AstNode *param_node = proto_node->data.fn_proto.params.at(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) {
            const CallingConvention cc = lazy_fn_type->cc;

            if (cc == CallingConventionC) {
                break;
            } else {
                ir_add_error_node(ira, instruction->base.source_node,
                        buf_sprintf("var args only allowed in functions with C calling convention"));
                return ira->codegen->invalid_inst_gen;
            }
        }

        if (instruction->param_types[param_index] == nullptr) {
            lazy_fn_type->is_generic = true;
            return result;
        }

        Stage1AirInst *param_type_value = instruction->param_types[param_index]->child;
        if (type_is_invalid(param_type_value->value->type))
            return ira->codegen->invalid_inst_gen;
        if (ir_resolve_const(ira, param_type_value, LazyOk) == nullptr)
            return ira->codegen->invalid_inst_gen;
        lazy_fn_type->param_types[param_index] = param_type_value;
    }

    if (instruction->align_value != nullptr) {
        lazy_fn_type->align_inst = instruction->align_value->child;
        if (ir_resolve_const(ira, lazy_fn_type->align_inst, LazyOk) == nullptr)
            return ira->codegen->invalid_inst_gen;
    }

    lazy_fn_type->return_type = instruction->return_type->child;
    if (ir_resolve_const(ira, lazy_fn_type->return_type, LazyOk) == nullptr)
         return ira->codegen->invalid_inst_gen;

    return result;
}

static Stage1AirInst *ir_analyze_instruction_test_comptime(IrAnalyze *ira, Stage1ZirInstTestComptime *instruction) {
    Stage1AirInst *value = instruction->value->child;
    if (type_is_invalid(value->value->type))
        return ira->codegen->invalid_inst_gen;

    return ir_const_bool(ira, instruction->base.scope, instruction->base.source_node, instr_is_comptime(value));
}

static Stage1AirInst *ir_analyze_instruction_check_switch_prongs(IrAnalyze *ira,
        Stage1ZirInstCheckSwitchProngs *instruction, bool have_underscore_prong)
{
    Stage1AirInst *target_value = instruction->target_value->child;
    ZigType *switch_type = target_value->value->type;
    if (type_is_invalid(switch_type))
        return ira->codegen->invalid_inst_gen;

    ZigValue *original_value = ((Stage1ZirInstSwitchTarget *)(instruction->target_value))->target_value_ptr->child->value;
    bool target_is_originally_union = original_value->type->id == ZigTypeIdPointer &&
        original_value->type->data.pointer.child_type->id == ZigTypeIdUnion;

    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) {
            Stage1ZirInstCheckSwitchProngsRange *range = &instruction->ranges[range_i];

            Stage1AirInst *start_value_uncasted = range->start->child;
            if (type_is_invalid(start_value_uncasted->value->type))
                return ira->codegen->invalid_inst_gen;
            Stage1AirInst *start_value = ir_implicit_cast(ira, start_value_uncasted, switch_type);
            if (type_is_invalid(start_value->value->type))
                return ira->codegen->invalid_inst_gen;

            Stage1AirInst *end_value_uncasted = range->end->child;
            if (type_is_invalid(end_value_uncasted->value->type))
                return ira->codegen->invalid_inst_gen;
            Stage1AirInst *end_value = ir_implicit_cast(ira, end_value_uncasted, switch_type);
            if (type_is_invalid(end_value->value->type))
                return ira->codegen->invalid_inst_gen;

            assert(start_value->value->type->id == ZigTypeIdEnum);
            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);

            if (bigint_cmp(&start_index, &end_index) == CmpGT) {
                ir_add_error(ira, start_value,
                    buf_sprintf("range start value is greater than the end value"));
            }

            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 here"));
                }
                bigint_incr(&field_index);
            }
        }
        if (have_underscore_prong) {
            if (!switch_type->data.enumeration.non_exhaustive) {
                ir_add_error_node(ira, instruction->base.source_node,
                    buf_sprintf("switch on exhaustive enum has `_` prong"));
            } else if (target_is_originally_union) {
                ir_add_error_node(ira, instruction->base.source_node,
                    buf_sprintf("`_` prong not allowed when switching on tagged union"));
            }
            for (uint32_t i = 0; i < switch_type->data.enumeration.src_field_count; i += 1) {
                TypeEnumField *enum_field = &switch_type->data.enumeration.fields[i];
                if (buf_eql_str(enum_field->name, "_"))
                    continue;

                auto entry = field_prev_uses.maybe_get(enum_field->value);
                if (!entry) {
                    ir_add_error_node(ira, instruction->base.source_node,
                        buf_sprintf("enumeration value '%s.%s' not handled in switch", buf_ptr(&switch_type->name),
                            buf_ptr(enum_field->name)));
                }
            }
        } else if (instruction->else_prong == nullptr) {
            if (switch_type->data.enumeration.non_exhaustive && !target_is_originally_union) {
                ir_add_error_node(ira, instruction->base.source_node,
                    buf_sprintf("switch on non-exhaustive enum must include `else` or `_` 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_node(ira, instruction->base.source_node,
                        buf_sprintf("enumeration value '%s.%s' not handled in switch", buf_ptr(&switch_type->name),
                            buf_ptr(enum_field->name)));
                }
            }
        } else if(!switch_type->data.enumeration.non_exhaustive && switch_type->data.enumeration.src_field_count == instruction->range_count) {
            ir_add_error_node(ira, instruction->else_prong,
                buf_sprintf("unreachable else prong, all cases already handled"));
            return ira->codegen->invalid_inst_gen;
        }
    } else if (switch_type->id == ZigTypeIdErrorSet) {
        if (!resolve_inferred_error_set(ira->codegen, switch_type, target_value->source_node)) {
            return ira->codegen->invalid_inst_gen;
        }

        size_t field_prev_uses_count = ira->codegen->errors_by_index.length;
        AstNode **field_prev_uses = heap::c_allocator.allocate<AstNode *>(field_prev_uses_count);

        for (size_t range_i = 0; range_i < instruction->range_count; range_i += 1) {
            Stage1ZirInstCheckSwitchProngsRange *range = &instruction->ranges[range_i];

            Stage1AirInst *start_value_uncasted = range->start->child;
            if (type_is_invalid(start_value_uncasted->value->type))
                return ira->codegen->invalid_inst_gen;
            Stage1AirInst *start_value = ir_implicit_cast(ira, start_value_uncasted, switch_type);
            if (type_is_invalid(start_value->value->type))
                return ira->codegen->invalid_inst_gen;

            Stage1AirInst *end_value_uncasted = range->end->child;
            if (type_is_invalid(end_value_uncasted->value->type))
                return ira->codegen->invalid_inst_gen;
            Stage1AirInst *end_value = ir_implicit_cast(ira, end_value_uncasted, switch_type);
            if (type_is_invalid(end_value->value->type))
                return ira->codegen->invalid_inst_gen;

            src_assert(start_value->value->type->id == ZigTypeIdErrorSet, instruction->base.source_node);
            uint32_t start_index = start_value->value->data.x_err_set->value;

            src_assert(end_value->value->type->id == ZigTypeIdErrorSet, instruction->base.source_node);
            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_inst_gen;
            }

            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 here"));
            }
            field_prev_uses[start_index] = start_value->source_node;
        }
        if (instruction->else_prong == nullptr) {
            if (type_is_global_error_set(switch_type)) {
                ir_add_error_node(ira, instruction->base.source_node,
                    buf_sprintf("else prong required when switching on type 'anyerror'"));
                return ira->codegen->invalid_inst_gen;
            } 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_node(ira, instruction->base.source_node,
                            buf_sprintf("error.%s not handled in switch", buf_ptr(&err_entry->name)));
                    }
                }
            }
        }

        heap::c_allocator.deallocate(field_prev_uses, field_prev_uses_count);
    } else if (switch_type->id == ZigTypeIdInt) {
        RangeSet rs = {0};
        for (size_t range_i = 0; range_i < instruction->range_count; range_i += 1) {
            Stage1ZirInstCheckSwitchProngsRange *range = &instruction->ranges[range_i];

            Stage1AirInst *start_value = range->start->child;
            if (type_is_invalid(start_value->value->type))
                return ira->codegen->invalid_inst_gen;
            Stage1AirInst *casted_start_value = ir_implicit_cast(ira, start_value, switch_type);
            if (type_is_invalid(casted_start_value->value->type))
                return ira->codegen->invalid_inst_gen;

            Stage1AirInst *end_value = range->end->child;
            if (type_is_invalid(end_value->value->type))
                return ira->codegen->invalid_inst_gen;
            Stage1AirInst *casted_end_value = ir_implicit_cast(ira, end_value, switch_type);
            if (type_is_invalid(casted_end_value->value->type))
                return ira->codegen->invalid_inst_gen;

            ZigValue *start_val = ir_resolve_const(ira, casted_start_value, UndefBad);
            if (!start_val)
                return ira->codegen->invalid_inst_gen;

            ZigValue *end_val = ir_resolve_const(ira, casted_end_value, UndefBad);
            if (!end_val)
                return ira->codegen->invalid_inst_gen;

            assert(start_val->type->id == ZigTypeIdInt || start_val->type->id == ZigTypeIdComptimeInt);
            assert(end_val->type->id == ZigTypeIdInt || end_val->type->id == ZigTypeIdComptimeInt);

            if (bigint_cmp(&start_val->data.x_bigint, &end_val->data.x_bigint) == CmpGT) {
                ir_add_error(ira, start_value,
                    buf_sprintf("range start value is greater than the end value"));
            }

            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 here"));
                return ira->codegen->invalid_inst_gen;
            }
        }

            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);
        bool handles_all_cases = rangeset_spans(&rs, &min_val, &max_val);
        if (!handles_all_cases && instruction->else_prong == nullptr) {
                ir_add_error_node(ira, instruction->base.source_node, buf_sprintf("switch must handle all possibilities"));
                return ira->codegen->invalid_inst_gen;
        } else if(handles_all_cases && instruction->else_prong != nullptr) {
            ir_add_error_node(ira, instruction->else_prong,
                buf_sprintf("unreachable else prong, all cases already handled"));
            return ira->codegen->invalid_inst_gen;
            }
    } 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) {
            Stage1ZirInstCheckSwitchProngsRange *range = &instruction->ranges[range_i];

            Stage1AirInst *value = range->start->child;

            Stage1AirInst *casted_value = ir_implicit_cast(ira, value, switch_type);
            if (type_is_invalid(casted_value->value->type))
                return ira->codegen->invalid_inst_gen;

            ZigValue *const_expr_val = ir_resolve_const(ira, casted_value, UndefBad);
            if (!const_expr_val)
                return ira->codegen->invalid_inst_gen;

            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_inst_gen;
            }
        }
        if (((seenTrue < 1) || (seenFalse < 1)) && instruction->else_prong == nullptr) {
            ir_add_error_node(ira, instruction->base.source_node, buf_sprintf("switch must handle all possibilities"));
            return ira->codegen->invalid_inst_gen;
        }

        if(seenTrue == 1 && seenFalse == 1 && instruction->else_prong != nullptr) {
            ir_add_error_node(ira, instruction->else_prong,
                buf_sprintf("unreachable else prong, all cases already handled"));
            return ira->codegen->invalid_inst_gen;
        }
    } else if (instruction->else_prong == nullptr) {
        ir_add_error_node(ira, instruction->base.source_node,
            buf_sprintf("else prong required when switching on type '%s'", buf_ptr(&switch_type->name)));
        return ira->codegen->invalid_inst_gen;
    } else if(switch_type->id == ZigTypeIdMetaType) {
        HashMap<const ZigType*, Stage1AirInst*, type_ptr_hash, type_ptr_eql> prevs;
        // HashMap doubles capacity when reaching 60% capacity,
        // because we know the size at init we can avoid reallocation by doubling it here
        prevs.init(instruction->range_count * 2);
        for (size_t range_i = 0; range_i < instruction->range_count; range_i += 1) {
            Stage1ZirInstCheckSwitchProngsRange *range = &instruction->ranges[range_i];

            Stage1AirInst *value = range->start->child;
            Stage1AirInst *casted_value = ir_implicit_cast(ira, value, switch_type);
            if (type_is_invalid(casted_value->value->type)) {
                prevs.deinit();
                return ira->codegen->invalid_inst_gen;
            }

            ZigValue *const_expr_val = ir_resolve_const(ira, casted_value, UndefBad);
            if (!const_expr_val) {
                prevs.deinit();
                return ira->codegen->invalid_inst_gen;
            }

            auto entry = prevs.put_unique(const_expr_val->data.x_type, value);
            if(entry != nullptr) {
                ErrorMsg *msg = ir_add_error(ira, value, buf_sprintf("duplicate switch value"));
                add_error_note(ira->codegen, msg, entry->value->source_node, buf_sprintf("previous value here"));
                prevs.deinit();
                return ira->codegen->invalid_inst_gen;
            }
        }
        prevs.deinit();
    }
    return ir_const_void(ira, instruction->base.scope, instruction->base.source_node);
}

static Stage1AirInst *ir_analyze_instruction_check_statement_is_void(IrAnalyze *ira,
        Stage1ZirInstCheckStatementIsVoid *instruction)
{
    Stage1AirInst *statement_value = instruction->statement_value->child;
    ZigType *statement_type = statement_value->value->type;
    if (type_is_invalid(statement_type))
        return ira->codegen->invalid_inst_gen;

    if (statement_type->id != ZigTypeIdVoid && statement_type->id != ZigTypeIdUnreachable) {
        if(statement_type->id == ZigTypeIdErrorUnion || statement_type->id == ZigTypeIdErrorSet) {
            ir_add_error_node(ira, instruction->base.source_node, buf_sprintf("error is ignored. consider using `try`, `catch`, or `if`"));
        }else{
            ir_add_error_node(ira, instruction->base.source_node, buf_sprintf("expression value is ignored"));
        }
    }

    return ir_const_void(ira, instruction->base.scope, instruction->base.source_node);
}

static Stage1AirInst *ir_analyze_instruction_panic(IrAnalyze *ira, Stage1ZirInstPanic *instruction) {
    Stage1AirInst *msg = instruction->msg->child;
    if (type_is_invalid(msg->value->type))
        return ir_unreach_error(ira);

    if (ir_should_inline(ira->zir, instruction->base.scope)) {
        ir_add_error_node(ira, instruction->base.source_node, 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, false);
    ZigType *str_type = get_slice_type(ira->codegen, u8_ptr_type);
    Stage1AirInst *casted_msg = ir_implicit_cast(ira, msg, str_type);
    if (type_is_invalid(casted_msg->value->type))
        return ir_unreach_error(ira);

    Stage1AirInst *new_instruction = ir_build_panic_gen(ira, instruction->base.scope, instruction->base.source_node, casted_msg);
    return ir_finish_anal(ira, new_instruction);
}

static Stage1AirInst *ir_align_cast(IrAnalyze *ira, Stage1AirInst *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;

    ZigType *actual_ptr = target_type;
    if (actual_ptr->id == ZigTypeIdOptional) {
        actual_ptr = actual_ptr->data.maybe.child_type;
    } else if (is_slice(actual_ptr)) {
        actual_ptr = actual_ptr->data.structure.fields[slice_ptr_index]->type_entry;
    }

    if (safety_check_on && !type_has_bits(ira->codegen, actual_ptr)) {
        ir_add_error(ira, target,
            buf_sprintf("cannot adjust alignment of zero sized type '%s'", buf_ptr(&target_type->name)));
        return ira->codegen->invalid_inst_gen;
    }

    if (target_type->id == ZigTypeIdPointer) {
        if ((err = resolve_ptr_align(ira, target_type, &old_align_bytes)))
            return ira->codegen->invalid_inst_gen;
        result_type = adjust_ptr_align(ira->codegen, target_type, align_bytes);
    } 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 == ZigTypeIdAnyFrame) {
        if (align_bytes >= get_async_frame_align_bytes(ira->codegen)) {
            result_type = target_type;
        } else {
            ir_add_error(ira, target, buf_sprintf("sub-aligned anyframe not allowed"));
            return ira->codegen->invalid_inst_gen;
        }
    } 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_inst_gen;
        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_inst_gen;
        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_inst_gen;
    }

    if (instr_is_comptime(target)) {
        ZigValue *val = ir_resolve_const(ira, target, UndefBad);
        if (!val)
            return ira->codegen->invalid_inst_gen;

        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_inst_gen;
        }

        Stage1AirInst *result = ir_const(ira, target->scope, target->source_node, result_type);
        copy_const_val(ira->codegen, result->value, val);
        result->value->type = result_type;
        return result;
    }

    if (safety_check_on && align_bytes > old_align_bytes && align_bytes != 1) {
        return ir_build_align_cast_gen(ira, target->scope, target->source_node, target, result_type);
    } else {
        return ir_build_cast(ira, target->scope, target->source_node, result_type, target, CastOpNoop);
    }
}

static Stage1AirInst *ir_analyze_ptr_cast(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        Stage1AirInst *ptr, AstNode *ptr_src, ZigType *dest_type, AstNode *dest_type_src,
        bool safety_check_on, bool keep_bigger_alignment)
{
    Error err;

    ZigType *src_type = ptr->value->type;
    assert(!type_is_invalid(src_type));

    if (src_type == dest_type) {
        return ptr;
    }

    // We have a check for zero bits later so we use get_src_ptr_type to
    // validate src_type and dest_type.

    ZigType *if_slice_ptr_type;
    if (is_slice(src_type)) {
        TypeStructField *ptr_field = src_type->data.structure.fields[slice_ptr_index];
        if_slice_ptr_type = resolve_struct_field_type(ira->codegen, ptr_field);
    } else {
        if_slice_ptr_type = src_type;

        ZigType *src_ptr_type = get_src_ptr_type(src_type);
        if (src_ptr_type == nullptr) {
            ir_add_error_node(ira, ptr_src,
                    buf_sprintf("expected pointer, found '%s'", buf_ptr(&src_type->name)));
            return ira->codegen->invalid_inst_gen;
        }
    }

    ZigType *dest_ptr_type = get_src_ptr_type(dest_type);
    if (dest_ptr_type == nullptr) {
        ir_add_error_node(ira, dest_type_src,
                buf_sprintf("expected pointer, found '%s'", buf_ptr(&dest_type->name)));
        return ira->codegen->invalid_inst_gen;
    }

    if (get_ptr_const(ira->codegen, src_type) && !get_ptr_const(ira->codegen, dest_type)) {
        ir_add_error_node(ira, source_node, buf_sprintf("cast discards const qualifier"));
        return ira->codegen->invalid_inst_gen;
    }
    uint32_t dest_align_bytes;
    if ((err = resolve_ptr_align(ira, dest_type, &dest_align_bytes)))
        return ira->codegen->invalid_inst_gen;

    uint32_t src_align_bytes = 0;
    if (keep_bigger_alignment || dest_align_bytes != 1) {
        if ((err = resolve_ptr_align(ira, src_type, &src_align_bytes)))
            return ira->codegen->invalid_inst_gen;
    }

    if ((err = type_resolve(ira->codegen, dest_type, ResolveStatusZeroBitsKnown)))
        return ira->codegen->invalid_inst_gen;

    if ((err = type_resolve(ira->codegen, src_type, ResolveStatusZeroBitsKnown)))
        return ira->codegen->invalid_inst_gen;

    if (safety_check_on &&
        type_has_bits(ira->codegen, dest_type) &&
        !type_has_bits(ira->codegen, if_slice_ptr_type))
    {
        ErrorMsg *msg = ir_add_error_node(ira, source_node,
            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_src,
                buf_sprintf("'%s' has no in-memory bits", buf_ptr(&src_type->name)));
        add_error_note(ira->codegen, msg, dest_type_src,
                buf_sprintf("'%s' has in-memory bits", buf_ptr(&dest_type->name)));
        return ira->codegen->invalid_inst_gen;
    }

    // For slices, follow the `ptr` field.
    if (is_slice(src_type)) {
        TypeStructField *ptr_field = src_type->data.structure.fields[slice_ptr_index];
        Stage1AirInst *ptr_ref = ir_get_ref(ira, scope, source_node, ptr, true, false);
        Stage1AirInst *ptr_ptr = ir_analyze_struct_field_ptr(ira, scope, source_node, ptr_field, ptr_ref, src_type, false);
        ptr = ir_get_deref(ira, scope, source_node, ptr_ptr, nullptr);
    }

    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;
        ZigValue *val = ir_resolve_const(ira, ptr, is_undef_allowed);
        if (val == nullptr)
            return ira->codegen->invalid_inst_gen;

        if (value_is_comptime(val) && val->special != ConstValSpecialUndef) {
            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_node(ira, source_node,
                        buf_sprintf("null pointer casted to type '%s'", buf_ptr(&dest_type->name)));
                return ira->codegen->invalid_inst_gen;
            }
        }

        Stage1AirInst *result;
        if (val->data.x_ptr.mut == ConstPtrMutInfer) {
            result = ir_build_ptr_cast_gen(ira, scope, source_node, dest_type, ptr, safety_check_on);
        } else {
            result = ir_const(ira, scope, source_node, dest_type);
        }
        InferredStructField *isf = (val->type->id == ZigTypeIdPointer) ?
            val->type->data.pointer.inferred_struct_field : nullptr;
        if (isf == nullptr) {
            copy_const_val(ira->codegen, result->value, val);
        } else {
            // The destination value should have x_ptr struct pointing to underlying struct value
            result->value->data.x_ptr.mut = val->data.x_ptr.mut;
            TypeStructField *field = find_struct_type_field(isf->inferred_struct_type, isf->field_name);
            assert(field != nullptr);
            if (field->is_comptime) {
                result->value->data.x_ptr.special = ConstPtrSpecialRef;
                result->value->data.x_ptr.data.ref.pointee = field->init_val;
            } else {
                assert(val->data.x_ptr.special == ConstPtrSpecialRef);
                result->value->data.x_ptr.special = ConstPtrSpecialBaseStruct;
                result->value->data.x_ptr.data.base_struct.struct_val = val->data.x_ptr.data.ref.pointee;
                result->value->data.x_ptr.data.base_struct.field_index = field->src_index;
            }
            result->value->special = ConstValSpecialStatic;
        }
        result->value->type = dest_type;

        // Keep the bigger alignment, it can only help- unless the target is zero bits.
        if (keep_bigger_alignment && src_align_bytes > dest_align_bytes && type_has_bits(ira->codegen, dest_type)) {
            result = ir_align_cast(ira, result, src_align_bytes, false);
        }

        return result;
    }

    if (src_align_bytes != 0 && dest_align_bytes > src_align_bytes) {
        ErrorMsg *msg = ir_add_error_node(ira, source_node, buf_sprintf("cast increases pointer alignment"));
        add_error_note(ira->codegen, msg, ptr_src,
                buf_sprintf("'%s' has alignment %" PRIu32, buf_ptr(&src_type->name), src_align_bytes));
        add_error_note(ira->codegen, msg, dest_type_src,
                buf_sprintf("'%s' has alignment %" PRIu32, buf_ptr(&dest_type->name), dest_align_bytes));
        return ira->codegen->invalid_inst_gen;
    }

    Stage1AirInst *casted_ptr = ir_build_ptr_cast_gen(ira, scope, source_node, dest_type, ptr, safety_check_on);

    // Keep the bigger alignment, it can only help- unless the target is zero bits.
    Stage1AirInst *result;
    if (keep_bigger_alignment && src_align_bytes > dest_align_bytes && type_has_bits(ira->codegen, dest_type)) {
        result = ir_align_cast(ira, casted_ptr, src_align_bytes, false);
        if (type_is_invalid(result->value->type))
            return ira->codegen->invalid_inst_gen;
    } else {
        result = casted_ptr;
    }
    return result;
}

static Stage1AirInst *ir_analyze_instruction_ptr_cast(IrAnalyze *ira, Stage1ZirInstPtrCast *instruction) {
    Stage1AirInst *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_inst_gen;

    Stage1AirInst *ptr = instruction->ptr->child;
    ZigType *src_type = ptr->value->type;
    if (type_is_invalid(src_type))
        return ira->codegen->invalid_inst_gen;

    bool keep_bigger_alignment = true;
    return ir_analyze_ptr_cast(ira, instruction->base.scope, instruction->base.source_node, ptr,
            instruction->ptr->source_node, dest_type, dest_type_value->source_node,
            instruction->safety_check_on, keep_bigger_alignment);
}

static void buf_write_value_bytes_array(CodeGen *codegen, uint8_t *buf, ZigValue *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) {
        ZigValue *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);
    }
    if (val->type->id == ZigTypeIdArray && val->type->data.array.sentinel != nullptr) {
        buf_write_value_bytes(codegen, &buf[buf_i], val->type->data.array.sentinel);
    }
}

static void buf_write_value_bytes(CodeGen *codegen, uint8_t *buf, ZigValue *val) {
    if (val->special == ConstValSpecialUndef) {
        expand_undef_struct(codegen, val);
        val->special = ConstValSpecialStatic;
    }
    assert(val->special == ConstValSpecialStatic);
    switch (val->type->id) {
        case ZigTypeIdInvalid:
        case ZigTypeIdMetaType:
        case ZigTypeIdOpaque:
        case ZigTypeIdBoundFn:
        case ZigTypeIdUnreachable:
        case ZigTypeIdComptimeFloat:
        case ZigTypeIdComptimeInt:
        case ZigTypeIdEnumLiteral:
        case ZigTypeIdUndefined:
        case ZigTypeIdNull:
        case ZigTypeIdErrorUnion:
        case ZigTypeIdErrorSet:
            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 *struct_field = val->type->data.structure.fields[field_i];
                        if (struct_field->gen_index == SIZE_MAX || struct_field->is_comptime)
                            continue;
                        ZigValue *field_val = val->data.x_struct.fields[field_i];
                        size_t offset = struct_field->offset;
                        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) {
                        size_t big_int_byte_count = val->type->data.structure.host_int_bytes[gen_i];
                        if (big_int_byte_count > child_buf_len) {
                            child_buf = heap::c_allocator.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];
                            if (field->is_comptime) {
                                src_i += 1;
                                continue;
                            }
                            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_byte_count * 8, is_big_endian);
                        offset += big_int_byte_count;
                        gen_i += 1;
                    }
                    return;
                }
            }
            zig_unreachable();
        case ZigTypeIdOptional:
            zig_panic("TODO buf_write_value_bytes maybe type");
        case ZigTypeIdFn:
            zig_panic("TODO buf_write_value_bytes fn type");
        case ZigTypeIdUnion:
            zig_panic("TODO buf_write_value_bytes union type");
        case ZigTypeIdFnFrame:
            zig_panic("TODO buf_write_value_bytes async fn frame type");
        case ZigTypeIdAnyFrame:
            zig_panic("TODO buf_write_value_bytes anyframe type");
    }
    zig_unreachable();
}

static Error buf_read_value_bytes_array(IrAnalyze *ira, CodeGen *codegen, AstNode *source_node, uint8_t *buf,
        ZigValue *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 = codegen->pass1_arena->allocate<ZigValue>(len);
            for (size_t i = 0; i < len; i++) {
                ZigValue *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, ZigValue *val) {
    Error err;
    src_assert(val->special == ConstValSpecialStatic, source_node);
    switch (val->type->id) {
        case ZigTypeIdInvalid:
        case ZigTypeIdMetaType:
        case ZigTypeIdOpaque:
        case ZigTypeIdBoundFn:
        case ZigTypeIdUnreachable:
        case ZigTypeIdComptimeFloat:
        case ZigTypeIdComptimeInt:
        case ZigTypeIdEnumLiteral:
        case ZigTypeIdUndefined:
        case ZigTypeIdNull:
            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_usize(&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: {
            ZigType *tag_int_type = val->type->data.enumeration.tag_int_type;
            src_assert(tag_int_type->id == ZigTypeIdInt, source_node);
            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;
        } case ZigTypeIdStruct:
            switch (val->type->data.structure.layout) {
                case ContainerLayoutAuto: {
                    switch(val->type->data.structure.special){
                        case StructSpecialNone:
                        case StructSpecialInferredTuple:
                        case StructSpecialInferredStruct: {
                            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"));
                            break;
                        }
                        case StructSpecialSlice: {
                            opt_ir_add_error_node(ira, codegen, source_node,
                                buf_sprintf("slice '%s' cannot have its bytes reinterpreted",
                                    buf_ptr(&val->type->name)));
                            break;
                        }
                    }
                    return ErrorSemanticAnalyzeFail;
                }
                case ContainerLayoutExtern: {
                    size_t src_field_count = val->type->data.structure.src_field_count;
                    val->data.x_struct.fields = alloc_const_vals_ptrs(codegen, src_field_count);
                    for (size_t field_i = 0; field_i < src_field_count; field_i += 1) {
                        TypeStructField *struct_field = val->type->data.structure.fields[field_i];
                        if (struct_field->is_comptime)
                            continue;
                        ZigValue *field_val = val->data.x_struct.fields[field_i];
                        field_val->special = ConstValSpecialStatic;
                        field_val->type = struct_field->type_entry;
                        if (struct_field->gen_index == SIZE_MAX)
                            continue;
                        size_t offset = struct_field->offset;
                        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 = alloc_const_vals_ptrs(codegen, 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) {
                        size_t big_int_byte_count = val->type->data.structure.host_int_bytes[gen_i];
                        if (big_int_byte_count > child_buf_len) {
                            child_buf = heap::c_allocator.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_byte_count * 8, is_big_endian, false);
                        uint64_t bit_offset = 0;
                        while (src_i < src_field_count) {
                            TypeStructField *field = val->type->data.structure.fields[src_i];
                            if (field->is_comptime) {
                                src_i += 1;
                                continue;
                            }
                            src_assert(field->gen_index != SIZE_MAX, source_node);
                            if (field->gen_index != gen_i)
                                break;
                            ZigValue *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) {
                                BigInt packed_bits_size_bi;
                                bigint_init_unsigned(&packed_bits_size_bi, big_int_byte_count * 8 - packed_bits_size - bit_offset);
                                BigInt tmp;
                                bigint_shr(&tmp, &big_int, &packed_bits_size_bi);
                                bigint_truncate(&child_val, &tmp, packed_bits_size, false);
                            } 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, packed_bits_size, is_big_endian);
                            if ((err = buf_read_value_bytes(ira, codegen, source_node, child_buf, field_val))) {
                                return err;
                            }

                            bit_offset += packed_bits_size;
                            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");
        case ZigTypeIdFnFrame:
            zig_panic("TODO buf_read_value_bytes async fn frame type");
        case ZigTypeIdAnyFrame:
            zig_panic("TODO buf_read_value_bytes anyframe type");
    }
    zig_unreachable();
}

static Stage1AirInst *ir_analyze_bit_cast(IrAnalyze *ira, Scope *scope, AstNode *source_node, Stage1AirInst *value,
        ZigType *dest_type)
{
    Error err;

    ZigType *src_type = value->value->type;
    src_assert(type_can_bit_cast(src_type), source_node);
    src_assert(type_can_bit_cast(dest_type), source_node);

    if (dest_type->id == ZigTypeIdEnum) {
        ErrorMsg *msg = ir_add_error_node(ira, source_node,
            buf_sprintf("cannot cast a value of type '%s'", buf_ptr(&dest_type->name)));
        add_error_note(ira->codegen, msg, source_node,
            buf_sprintf("use @intToEnum for type coercion"));
        return ira->codegen->invalid_inst_gen;
    }

    if ((err = type_resolve(ira->codegen, dest_type, ResolveStatusSizeKnown)))
        return ira->codegen->invalid_inst_gen;

    if ((err = type_resolve(ira->codegen, src_type, ResolveStatusSizeKnown)))
        return ira->codegen->invalid_inst_gen;

    const bool src_is_ptr = handle_is_ptr(ira->codegen, src_type);
    const bool dest_is_ptr = handle_is_ptr(ira->codegen, dest_type);

    const uint64_t dest_size_bytes = type_size(ira->codegen, dest_type);
    const uint64_t src_size_bytes = type_size(ira->codegen, src_type);
    if (dest_size_bytes != src_size_bytes) {
        ir_add_error_node(ira, source_node,
            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_inst_gen;
    }

    const uint64_t dest_size_bits = type_size_bits(ira->codegen, dest_type);
    const uint64_t src_size_bits = type_size_bits(ira->codegen, src_type);
    if (dest_size_bits != src_size_bits) {
        ir_add_error_node(ira, source_node,
            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_inst_gen;
    }

    if (instr_is_comptime(value)) {
        ZigValue *val = ir_resolve_const(ira, value, UndefBad);
        if (!val)
            return ira->codegen->invalid_inst_gen;

        Stage1AirInst *result = ir_const(ira, scope, source_node, dest_type);
        uint8_t *buf = heap::c_allocator.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_node, buf, result->value)))
            return ira->codegen->invalid_inst_gen;
        heap::c_allocator.deallocate(buf, src_size_bytes);
        return result;
    }

    if (dest_is_ptr && !src_is_ptr) {
        // Spill the scalar into a local memory location and take its address
        value = ir_get_ref(ira, scope, source_node, value, false, false);
    }

    return ir_build_bit_cast_gen(ira, scope, source_node, value, dest_type);
}

static Stage1AirInst *ir_analyze_int_to_ptr(IrAnalyze *ira, Scope *scope, AstNode *source_node, Stage1AirInst *target,
        ZigType *ptr_type)
{
    Error err;

    src_assert(get_src_ptr_type(ptr_type) != nullptr, source_node);
    src_assert(type_has_bits(ira->codegen, ptr_type), source_node);

    Stage1AirInst *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_inst_gen;

    if (instr_is_comptime(casted_int)) {
        ZigValue *val = ir_resolve_const(ira, casted_int, UndefBad);
        if (!val)
            return ira->codegen->invalid_inst_gen;

        uint64_t addr = bigint_as_u64(&val->data.x_bigint);
        if (!ptr_allows_addr_zero(ptr_type) && addr == 0) {
            ir_add_error_node(ira, source_node,
                    buf_sprintf("pointer type '%s' does not allow address zero", buf_ptr(&ptr_type->name)));
            return ira->codegen->invalid_inst_gen;
        }

        uint32_t align_bytes;
        if ((err = resolve_ptr_align(ira, ptr_type, &align_bytes)))
            return ira->codegen->invalid_inst_gen;

        if (addr != 0 && addr % align_bytes != 0) {
            ir_add_error_node(ira, source_node,
                    buf_sprintf("pointer type '%s' requires aligned address",
                                buf_ptr(&ptr_type->name)));
            return ira->codegen->invalid_inst_gen;
        }

        Stage1AirInst *result = ir_const(ira, scope, source_node, ptr_type);
        if (ptr_type->id == ZigTypeIdOptional && addr == 0) {
            result->value->data.x_ptr.special = ConstPtrSpecialNull;
            result->value->data.x_ptr.mut = ConstPtrMutComptimeConst;
        } else {
            result->value->data.x_ptr.special = ConstPtrSpecialHardCodedAddr;
            result->value->data.x_ptr.mut = ConstPtrMutRuntimeVar;
            result->value->data.x_ptr.data.hard_coded_addr.addr = addr;
        }

        return result;
    }

    return ir_build_int_to_ptr_gen(ira, scope, source_node, casted_int, ptr_type);
}

static Stage1AirInst *ir_analyze_instruction_int_to_ptr(IrAnalyze *ira, Stage1ZirInstIntToPtr *instruction) {
    Error err;
    Stage1AirInst *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_inst_gen;

    // 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_inst_gen;
    }

    bool has_bits;
    if ((err = type_has_bits2(ira->codegen, dest_type, &has_bits)))
        return ira->codegen->invalid_inst_gen;

    if (!has_bits) {
        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_inst_gen;
    }

    Stage1AirInst *target = instruction->target->child;
    if (type_is_invalid(target->value->type))
        return ira->codegen->invalid_inst_gen;

    return ir_analyze_int_to_ptr(ira, instruction->base.scope, instruction->base.source_node, target, dest_type);
}

static Stage1AirInst *ir_analyze_instruction_decl_ref(IrAnalyze *ira, Stage1ZirInstDeclRef *instruction) {
    Stage1AirInst *ref_instruction = ir_analyze_decl_ref(ira, instruction->base.scope, instruction->base.source_node, instruction->tld);
    if (type_is_invalid(ref_instruction->value->type)) {
        return ira->codegen->invalid_inst_gen;
    }

    if (instruction->lval == LValPtr || instruction->lval == LValAssign) {
        return ref_instruction;
    } else {
        return ir_get_deref(ira, instruction->base.scope, instruction->base.source_node, ref_instruction, nullptr);
    }
}

static Stage1AirInst *ir_analyze_instruction_ptr_to_int(IrAnalyze *ira, Stage1ZirInstPtrToInt *instruction) {
    Error err;
    Stage1AirInst *target = instruction->target->child;
    if (type_is_invalid(target->value->type))
        return ira->codegen->invalid_inst_gen;

    ZigType *usize = ira->codegen->builtin_types.entry_usize;

    ZigType *src_ptr_type = get_src_ptr_type(target->value->type);
    if (src_ptr_type == nullptr) {
        ir_add_error(ira, target,
                buf_sprintf("expected pointer, found '%s'", buf_ptr(&target->value->type->name)));
        return ira->codegen->invalid_inst_gen;
    }

    bool has_bits;
    if ((err = type_has_bits2(ira->codegen, src_ptr_type, &has_bits)))
        return ira->codegen->invalid_inst_gen;

    if (!has_bits) {
        ir_add_error(ira, target,
                buf_sprintf("pointer to size 0 type has no address"));
        return ira->codegen->invalid_inst_gen;
    }

    if (instr_is_comptime(target)) {
        ZigValue *val = ir_resolve_const(ira, target, UndefBad);
        if (!val)
            return ira->codegen->invalid_inst_gen;

        // Since we've already run this type trough get_src_ptr_type it is
        // safe to access the x_ptr fields
        if (val->data.x_ptr.special == ConstPtrSpecialHardCodedAddr) {
            Stage1AirInst *result = ir_const(ira, instruction->base.scope, instruction->base.source_node, usize);
            bigint_init_unsigned(&result->value->data.x_bigint, val->data.x_ptr.data.hard_coded_addr.addr);
            result->value->type = usize;
            return result;
        } else if (val->data.x_ptr.special == ConstPtrSpecialNull) {
            Stage1AirInst *result = ir_const(ira, instruction->base.scope, instruction->base.source_node, usize);
            bigint_init_unsigned(&result->value->data.x_bigint, 0);
            result->value->type = usize;
            return result;
        }
    }

    return ir_build_ptr_to_int_gen(ira, instruction->base.scope, instruction->base.source_node, target);
}

static Stage1AirInst *ir_analyze_instruction_ptr_type_simple(IrAnalyze *ira,
        Stage1ZirInstPtrTypeSimple *instruction, bool is_const)
{
    Stage1AirInst *result = ir_const(ira, instruction->base.scope, instruction->base.source_node, ira->codegen->builtin_types.entry_type);
    result->value->special = ConstValSpecialLazy;

    LazyValuePtrTypeSimple *lazy_ptr_type = heap::c_allocator.create<LazyValuePtrTypeSimple>();
    lazy_ptr_type->ira = ira; ira_ref(ira);
    result->value->data.x_lazy = &lazy_ptr_type->base;
    lazy_ptr_type->base.id = is_const ? LazyValueIdPtrTypeSimpleConst : LazyValueIdPtrTypeSimple;

    lazy_ptr_type->elem_type = instruction->child_type->child;
    if (ir_resolve_type_lazy(ira, lazy_ptr_type->elem_type) == nullptr)
        return ira->codegen->invalid_inst_gen;

    return result;
}

static Stage1AirInst *ir_analyze_instruction_ptr_type(IrAnalyze *ira, Stage1ZirInstPtrType *instruction) {
    Stage1AirInst *result = ir_const(ira, instruction->base.scope, instruction->base.source_node, ira->codegen->builtin_types.entry_type);
    result->value->special = ConstValSpecialLazy;

    LazyValuePtrType *lazy_ptr_type = heap::c_allocator.create<LazyValuePtrType>();
    lazy_ptr_type->ira = ira; ira_ref(ira);
    result->value->data.x_lazy = &lazy_ptr_type->base;
    lazy_ptr_type->base.id = LazyValueIdPtrType;

    if (instruction->sentinel != nullptr) {
        if (instruction->ptr_len != PtrLenUnknown) {
            ir_add_error_node(ira, instruction->base.source_node,
                buf_sprintf("sentinels are only allowed on unknown-length pointers"));
            return ira->codegen->invalid_inst_gen;
        }

        lazy_ptr_type->sentinel = instruction->sentinel->child;
        if (ir_resolve_const(ira, lazy_ptr_type->sentinel, LazyOk) == nullptr)
            return ira->codegen->invalid_inst_gen;
    }

    lazy_ptr_type->elem_type = instruction->child_type->child;
    if (ir_resolve_type_lazy(ira, lazy_ptr_type->elem_type) == nullptr)
        return ira->codegen->invalid_inst_gen;

    if (instruction->align_value != nullptr) {
        lazy_ptr_type->align_inst = instruction->align_value->child;
        if (ir_resolve_const(ira, lazy_ptr_type->align_inst, LazyOk) == nullptr)
            return ira->codegen->invalid_inst_gen;
    }

    lazy_ptr_type->ptr_len = instruction->ptr_len;
    lazy_ptr_type->is_const = instruction->is_const;
    lazy_ptr_type->is_volatile = instruction->is_volatile;
    lazy_ptr_type->is_allowzero = instruction->is_allow_zero;
    lazy_ptr_type->bit_offset_in_host = instruction->bit_offset_start;
    lazy_ptr_type->host_int_bytes = instruction->host_int_bytes;

    return result;
}

static Stage1AirInst *ir_analyze_instruction_align_cast(IrAnalyze *ira, Stage1ZirInstAlignCast *instruction) {
    Stage1AirInst *target = instruction->target->child;
    if (type_is_invalid(target->value->type))
        return ira->codegen->invalid_inst_gen;

    ZigType *elem_type = nullptr;
    if (is_slice(target->value->type)) {
        ZigType *slice_ptr_type = target->value->type->data.structure.fields[slice_ptr_index]->type_entry;
        elem_type = slice_ptr_type->data.pointer.child_type;
    } else if (target->value->type->id == ZigTypeIdPointer) {
        elem_type = target->value->type->data.pointer.child_type;
    }

    uint32_t align_bytes;
    Stage1AirInst *align_bytes_inst = instruction->align_bytes->child;
    if (!ir_resolve_align(ira, align_bytes_inst, elem_type, &align_bytes))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *result = ir_align_cast(ira, target, align_bytes, true);
    if (type_is_invalid(result->value->type))
        return ira->codegen->invalid_inst_gen;

    return result;
}

static Stage1AirInst *ir_analyze_instruction_set_align_stack(IrAnalyze *ira, Stage1ZirInstSetAlignStack *instruction) {
    uint32_t align_bytes;
    Stage1AirInst *align_bytes_inst = instruction->align_bytes->child;
    if (!ir_resolve_align(ira, align_bytes_inst, nullptr, &align_bytes))
        return ira->codegen->invalid_inst_gen;

    if (align_bytes > 256) {
        ir_add_error_node(ira, instruction->base.source_node, buf_sprintf("attempt to @setAlignStack(%" PRIu32 "); maximum is 256", align_bytes));
        return ira->codegen->invalid_inst_gen;
    }

    ZigFn *fn_entry = ira->fn;
    if (fn_entry == nullptr) {
        ir_add_error_node(ira, instruction->base.source_node, buf_sprintf("@setAlignStack outside function"));
        return ira->codegen->invalid_inst_gen;
    }
    if (fn_entry->type_entry->data.fn.fn_type_id.cc == CallingConventionNaked) {
        ir_add_error_node(ira, instruction->base.source_node, buf_sprintf("@setAlignStack in naked function"));
        return ira->codegen->invalid_inst_gen;
    }

    if (fn_entry->type_entry->data.fn.fn_type_id.cc == CallingConventionInline) {
        ir_add_error_node(ira, instruction->base.source_node, buf_sprintf("@setAlignStack in inline function"));
        return ira->codegen->invalid_inst_gen;
    }

    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_inst_gen;
    }

    fn_entry->set_alignstack_node = instruction->base.source_node;
    fn_entry->alignstack_value = align_bytes;

    return ir_const_void(ira, instruction->base.scope, instruction->base.source_node);
}

static Stage1AirInst *ir_analyze_instruction_arg_type(IrAnalyze *ira, Stage1ZirInstArgType *instruction,
        bool allow_var)
{
    Stage1AirInst *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_inst_gen;

    Stage1AirInst *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_inst_gen;

    if (fn_type->id == ZigTypeIdBoundFn) {
        fn_type = fn_type->data.bound_fn.fn_type;
        arg_index += 1;
    }
    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_inst_gen;
    }

    FnTypeId *fn_type_id = &fn_type->data.fn.fn_type_id;
    if (arg_index >= fn_type_id->param_count) {
        if (allow_var) {
            // TODO remove this with var args
            return ir_const_type(ira, instruction->base.scope, instruction->base.source_node, ira->codegen->builtin_types.entry_anytype);
        }
        ir_add_error(ira, arg_index_inst,
                buf_sprintf("arg index %" ZIG_PRI_u64 " out of bounds; '%s' has %" ZIG_PRI_usize " argument(s)",
                    arg_index, buf_ptr(&fn_type->name), fn_type_id->param_count));
        return ira->codegen->invalid_inst_gen;
    }

    ZigType *result_type = fn_type_id->param_info[arg_index].type;
    if (result_type == nullptr) {
        // Args are only unresolved if our function is generic.
        src_assert(fn_type->data.fn.is_generic, instruction->base.source_node);

        if (allow_var) {
            return ir_const_type(ira, instruction->base.scope, instruction->base.source_node, ira->codegen->builtin_types.entry_anytype);
        } else {
            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_inst_gen;
        }
    }
    return ir_const_type(ira, instruction->base.scope, instruction->base.source_node, result_type);
}

static ZigType *ir_resolve_atomic_operand_type(IrAnalyze *ira, Stage1AirInst *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 || operand_type->id == ZigTypeIdEnum) {
        ZigType *int_type;
        if (operand_type->id == ZigTypeIdEnum) {
            int_type = operand_type->data.enumeration.tag_int_type;
        } else {
            int_type = operand_type;
        }
        auto bit_count = int_type->data.integral.bit_count;
        uint32_t max_atomic_bits = target_arch_largest_atomic_bits(ira->codegen->zig_target->arch);

        if (bit_count > max_atomic_bits) {
            ir_add_error(ira, op,
                buf_sprintf("expected %" PRIu32 "-bit integer type or smaller, found %" PRIu32 "-bit integer type",
                    max_atomic_bits, bit_count));
            return ira->codegen->builtin_types.entry_invalid;
        }
    } else if (operand_type->id == ZigTypeIdFloat) {
        uint32_t max_atomic_bits = target_arch_largest_atomic_bits(ira->codegen->zig_target->arch);
        if (operand_type->data.floating.bit_count > max_atomic_bits) {
            ir_add_error(ira, op,
                buf_sprintf("expected %" PRIu32 "-bit float or smaller, found %" PRIu32 "-bit float",
                    max_atomic_bits, (uint32_t) operand_type->data.floating.bit_count));
            return ira->codegen->builtin_types.entry_invalid;
        }
    } else if (operand_type->id == ZigTypeIdBool) {
        // will be treated as u8
    } else {
        Error err;
        ZigType *operand_ptr_type;
        if ((err = get_codegen_ptr_type(ira->codegen, operand_type, &operand_ptr_type)))
            return ira->codegen->builtin_types.entry_invalid;
        if (operand_ptr_type == nullptr) {
            ir_add_error(ira, op,
                buf_sprintf("expected bool, integer, float, enum or pointer type, found '%s'",
                    buf_ptr(&operand_type->name)));
            return ira->codegen->builtin_types.entry_invalid;
        }
    }

    return operand_type;
}

static Stage1AirInst *ir_analyze_instruction_atomic_rmw(IrAnalyze *ira, Stage1ZirInstAtomicRmw *instruction) {
    ZigType *operand_type = ir_resolve_atomic_operand_type(ira, instruction->operand_type->child);
    if (type_is_invalid(operand_type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *ptr_inst = instruction->ptr->child;
    if (type_is_invalid(ptr_inst->value->type))
        return ira->codegen->invalid_inst_gen;

    // TODO let this be volatile
    ZigType *ptr_type = get_pointer_to_type(ira->codegen, operand_type, false);
    Stage1AirInst *casted_ptr = ir_implicit_cast(ira, ptr_inst, ptr_type);
    if (type_is_invalid(casted_ptr->value->type))
        return ira->codegen->invalid_inst_gen;

    AtomicRmwOp op;
    if (!ir_resolve_atomic_rmw_op(ira, instruction->op->child, &op)) {
        return ira->codegen->invalid_inst_gen;
    }

    if (operand_type->id == ZigTypeIdEnum && op != AtomicRmwOp_xchg) {
        ir_add_error_node(ira, instruction->op->source_node,
            buf_sprintf("@atomicRmw with enum only allowed with .Xchg"));
        return ira->codegen->invalid_inst_gen;
    } else if (operand_type->id == ZigTypeIdBool && op != AtomicRmwOp_xchg) {
        ir_add_error_node(ira, instruction->op->source_node,
            buf_sprintf("@atomicRmw with bool only allowed with .Xchg"));
        return ira->codegen->invalid_inst_gen;
    } else if (operand_type->id == ZigTypeIdFloat && op > AtomicRmwOp_sub) {
        ir_add_error_node(ira, instruction->op->source_node,
            buf_sprintf("@atomicRmw with float only allowed with .Xchg, .Add and .Sub"));
        return ira->codegen->invalid_inst_gen;
    }

    Stage1AirInst *operand = instruction->operand->child;
    if (type_is_invalid(operand->value->type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *casted_operand = ir_implicit_cast(ira, operand, operand_type);
    if (type_is_invalid(casted_operand->value->type))
        return ira->codegen->invalid_inst_gen;

    AtomicOrder ordering;
    if (!ir_resolve_atomic_order(ira, instruction->ordering->child, &ordering))
        return ira->codegen->invalid_inst_gen;
    if (ordering == AtomicOrderUnordered) {
        ir_add_error_node(ira, instruction->ordering->source_node,
            buf_sprintf("@atomicRmw atomic ordering must not be Unordered"));
        return ira->codegen->invalid_inst_gen;
    }

    // special case zero bit types
    switch (type_has_one_possible_value(ira->codegen, operand_type)) {
        case OnePossibleValueInvalid:
            return ira->codegen->invalid_inst_gen;
        case OnePossibleValueYes:
            return ir_const_move(ira, instruction->base.scope, instruction->base.source_node, get_the_one_possible_value(ira->codegen, operand_type));
        case OnePossibleValueNo:
            break;
    }

    Scope *scope = instruction->base.scope;
    AstNode *source_node = instruction->base.source_node;
    if (instr_is_comptime(casted_operand) && instr_is_comptime(casted_ptr) && casted_ptr->value->data.x_ptr.mut == ConstPtrMutComptimeVar) {
        ZigValue *ptr_val = ir_resolve_const(ira, casted_ptr, UndefBad);
        if (ptr_val == nullptr)
            return ira->codegen->invalid_inst_gen;

        ZigValue *op1_val = const_ptr_pointee(ira, ira->codegen, ptr_val, instruction->base.source_node);
        if (op1_val == nullptr)
            return ira->codegen->invalid_inst_gen;

        ZigValue *op2_val = ir_resolve_const(ira, casted_operand, UndefBad);
        if (op2_val == nullptr)
            return ira->codegen->invalid_inst_gen;

        Stage1AirInst *result = ir_const(ira, scope, source_node, operand_type);
        copy_const_val(ira->codegen, result->value, op1_val);
        if (op == AtomicRmwOp_xchg) {
            copy_const_val(ira->codegen, op1_val, op2_val);
            return result;
        }

        if (operand_type->id == ZigTypeIdPointer || operand_type->id == ZigTypeIdOptional) {
            ir_add_error_node(ira, instruction->ordering->source_node,
                buf_sprintf("TODO comptime @atomicRmw with pointers other than .Xchg"));
            return ira->codegen->invalid_inst_gen;
        }

        ErrorMsg *msg;
        if (op == AtomicRmwOp_min || op == AtomicRmwOp_max) {
            IrBinOp bin_op;
            if (op == AtomicRmwOp_min)
                // store op2 if op2 < op1
                bin_op = IrBinOpCmpGreaterThan;
            else
                // store op2 if op2 > op1
                bin_op = IrBinOpCmpLessThan;

            Stage1AirInst *dummy_value = ir_const(ira, scope, source_node, operand_type);
            msg = ir_eval_bin_op_cmp_scalar(ira, scope, source_node, op1_val, bin_op, op2_val, dummy_value->value);
            if (msg != nullptr) {
                return ira->codegen->invalid_inst_gen;
            }
            if (dummy_value->value->data.x_bool)
                copy_const_val(ira->codegen, op1_val, op2_val);
        } else {
            IrBinOp bin_op;
            switch (op) {
                case AtomicRmwOp_xchg:
                case AtomicRmwOp_max:
                case AtomicRmwOp_min:
                    zig_unreachable();
                case AtomicRmwOp_add:
                    if (operand_type->id == ZigTypeIdFloat)
                        bin_op = IrBinOpAdd;
                    else
                        bin_op = IrBinOpAddWrap;
                    break;
                case AtomicRmwOp_sub:
                    if (operand_type->id == ZigTypeIdFloat)
                        bin_op = IrBinOpSub;
                    else
                        bin_op = IrBinOpSubWrap;
                    break;
                case AtomicRmwOp_and:
                case AtomicRmwOp_nand:
                    bin_op = IrBinOpBinAnd;
                    break;
                case AtomicRmwOp_or:
                    bin_op = IrBinOpBinOr;
                    break;
                case AtomicRmwOp_xor:
                    bin_op = IrBinOpBinXor;
                    break;
            }
            msg = ir_eval_math_op_scalar(ira, scope, source_node, operand_type, op1_val, bin_op, op2_val, op1_val);
            if (msg != nullptr) {
                return ira->codegen->invalid_inst_gen;
            }
            if (op == AtomicRmwOp_nand) {
                bigint_not(&op1_val->data.x_bigint, &op1_val->data.x_bigint,
                        operand_type->data.integral.bit_count, operand_type->data.integral.is_signed);
            }
        }
        return result;
    }

    return ir_build_atomic_rmw_gen(ira, scope, source_node, casted_ptr, casted_operand, op,
            ordering, operand_type);
}

static Stage1AirInst *ir_analyze_instruction_atomic_load(IrAnalyze *ira, Stage1ZirInstAtomicLoad *instruction) {
    ZigType *operand_type = ir_resolve_atomic_operand_type(ira, instruction->operand_type->child);
    if (type_is_invalid(operand_type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *ptr_inst = instruction->ptr->child;
    if (type_is_invalid(ptr_inst->value->type))
        return ira->codegen->invalid_inst_gen;

    ZigType *ptr_type = get_pointer_to_type(ira->codegen, operand_type, true);
    Stage1AirInst *casted_ptr = ir_implicit_cast(ira, ptr_inst, ptr_type);
    if (type_is_invalid(casted_ptr->value->type))
        return ira->codegen->invalid_inst_gen;

    AtomicOrder ordering;
    if (!ir_resolve_atomic_order(ira, instruction->ordering->child, &ordering))
        return ira->codegen->invalid_inst_gen;

    if (ordering == AtomicOrderRelease || ordering == AtomicOrderAcqRel) {
        src_assert(instruction->ordering != nullptr, instruction->base.source_node);
        ir_add_error_node(ira, instruction->ordering->source_node,
            buf_sprintf("@atomicLoad atomic ordering must not be Release or AcqRel"));
        return ira->codegen->invalid_inst_gen;
    }

    if (instr_is_comptime(casted_ptr)) {
        Stage1AirInst *result = ir_get_deref(ira, instruction->base.scope,
                instruction->base.source_node, casted_ptr, nullptr);
        src_assert(result->value->type != nullptr, instruction->base.source_node);
        return result;
    }

    return ir_build_atomic_load_gen(ira, instruction->base.scope, instruction->base.source_node, casted_ptr, ordering, operand_type);
}

static Stage1AirInst *ir_analyze_instruction_atomic_store(IrAnalyze *ira, Stage1ZirInstAtomicStore *instruction) {
    ZigType *operand_type = ir_resolve_atomic_operand_type(ira, instruction->operand_type->child);
    if (type_is_invalid(operand_type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *ptr_inst = instruction->ptr->child;
    if (type_is_invalid(ptr_inst->value->type))
        return ira->codegen->invalid_inst_gen;

    ZigType *ptr_type = get_pointer_to_type(ira->codegen, operand_type, false);
    Stage1AirInst *casted_ptr = ir_implicit_cast(ira, ptr_inst, ptr_type);
    if (type_is_invalid(casted_ptr->value->type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *value = instruction->value->child;
    if (type_is_invalid(value->value->type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *casted_value = ir_implicit_cast(ira, value, operand_type);
    if (type_is_invalid(casted_value->value->type))
        return ira->codegen->invalid_inst_gen;


    AtomicOrder ordering;
    if (!ir_resolve_atomic_order(ira, instruction->ordering->child, &ordering))
        return ira->codegen->invalid_inst_gen;

    if (ordering == AtomicOrderAcquire || ordering == AtomicOrderAcqRel) {
        src_assert(instruction->ordering != nullptr, instruction->base.source_node);
        ir_add_error_node(ira, instruction->ordering->source_node,
            buf_sprintf("@atomicStore atomic ordering must not be Acquire or AcqRel"));
        return ira->codegen->invalid_inst_gen;
    }

    // special case zero bit types
    switch (type_has_one_possible_value(ira->codegen, operand_type)) {
        case OnePossibleValueInvalid:
            return ira->codegen->invalid_inst_gen;
        case OnePossibleValueYes:
            return ir_const_void(ira, instruction->base.scope, instruction->base.source_node);
        case OnePossibleValueNo:
            break;
    }

    if (instr_is_comptime(casted_value) && instr_is_comptime(casted_ptr)) {
        Stage1AirInst *result = ir_analyze_store_ptr(ira, instruction->base.scope, instruction->base.source_node, casted_ptr, value, false);
        result->value->type = ira->codegen->builtin_types.entry_void;
        return result;
    }

    return ir_build_atomic_store_gen(ira, instruction->base.scope, instruction->base.source_node, casted_ptr, casted_value, ordering);
}

static Stage1AirInst *ir_analyze_instruction_save_err_ret_addr(IrAnalyze *ira, Stage1ZirInstSaveErrRetAddr *instruction) {
    return ir_build_save_err_ret_addr_gen(ira, instruction->base.scope, instruction->base.source_node);
}

static ErrorMsg *ir_eval_float_op(IrAnalyze *ira, Scope *scope, AstNode *source_node, BuiltinFnId fop, ZigType *float_type,
        ZigValue *op, ZigValue *out_val)
{
    assert(ira && source_node && float_type && out_val && op);
    assert(float_type->id == ZigTypeIdFloat ||
           float_type->id == ZigTypeIdComptimeFloat);

    unsigned bits;

    switch (float_type->id) {
    case ZigTypeIdComptimeFloat:
        bits = 128;
        break;
    case ZigTypeIdFloat:
        bits = float_type->data.floating.bit_count;
        break;
    default:
        zig_unreachable();
    }

    switch (bits) {
    case 16: {
        switch (fop) {
        case BuiltinFnIdSqrt:
            out_val->data.x_f16 = f16_sqrt(op->data.x_f16);
            break;
        case BuiltinFnIdSin:
            out_val->data.x_f16 = zig_double_to_f16(sin(zig_f16_to_double(op->data.x_f16)));
            break;
        case BuiltinFnIdCos:
            out_val->data.x_f16 = zig_double_to_f16(cos(zig_f16_to_double(op->data.x_f16)));
            break;
        case BuiltinFnIdExp:
            out_val->data.x_f16 = zig_double_to_f16(exp(zig_f16_to_double(op->data.x_f16)));
            break;
        case BuiltinFnIdExp2:
            out_val->data.x_f16 = zig_double_to_f16(exp2(zig_f16_to_double(op->data.x_f16)));
            break;
        case BuiltinFnIdLog:
            out_val->data.x_f16 = zig_double_to_f16(log(zig_f16_to_double(op->data.x_f16)));
            break;
        case BuiltinFnIdLog10:
            out_val->data.x_f16 = zig_double_to_f16(log10(zig_f16_to_double(op->data.x_f16)));
            break;
        case BuiltinFnIdLog2:
            out_val->data.x_f16 = zig_double_to_f16(log2(zig_f16_to_double(op->data.x_f16)));
            break;
        case BuiltinFnIdFabs:
            out_val->data.x_f16 = zig_double_to_f16(fabs(zig_f16_to_double(op->data.x_f16)));
            break;
        case BuiltinFnIdFloor:
            out_val->data.x_f16 = zig_double_to_f16(floor(zig_f16_to_double(op->data.x_f16)));
            break;
        case BuiltinFnIdCeil:
            out_val->data.x_f16 = zig_double_to_f16(ceil(zig_f16_to_double(op->data.x_f16)));
            break;
        case BuiltinFnIdTrunc:
            out_val->data.x_f16 = zig_double_to_f16(trunc(zig_f16_to_double(op->data.x_f16)));
            break;
        case BuiltinFnIdNearbyInt:
            out_val->data.x_f16 = zig_double_to_f16(nearbyint(zig_f16_to_double(op->data.x_f16)));
            break;
        case BuiltinFnIdRound:
            out_val->data.x_f16 = zig_double_to_f16(round(zig_f16_to_double(op->data.x_f16)));
            break;
        default:
            zig_unreachable();
        };
        break;
    }
    case 32: {
        switch (fop) {
        case BuiltinFnIdSqrt:
            out_val->data.x_f32 = sqrtf(op->data.x_f32);
            break;
        case BuiltinFnIdSin:
            out_val->data.x_f32 = sinf(op->data.x_f32);
            break;
        case BuiltinFnIdCos:
            out_val->data.x_f32 = cosf(op->data.x_f32);
            break;
        case BuiltinFnIdExp:
            out_val->data.x_f32 = expf(op->data.x_f32);
            break;
        case BuiltinFnIdExp2:
            out_val->data.x_f32 = exp2f(op->data.x_f32);
            break;
        case BuiltinFnIdLog:
            out_val->data.x_f32 = logf(op->data.x_f32);
            break;
        case BuiltinFnIdLog10:
            out_val->data.x_f32 = log10f(op->data.x_f32);
            break;
        case BuiltinFnIdLog2:
            out_val->data.x_f32 = log2f(op->data.x_f32);
            break;
        case BuiltinFnIdFabs:
            out_val->data.x_f32 = fabsf(op->data.x_f32);
            break;
        case BuiltinFnIdFloor:
            out_val->data.x_f32 = floorf(op->data.x_f32);
            break;
        case BuiltinFnIdCeil:
            out_val->data.x_f32 = ceilf(op->data.x_f32);
            break;
        case BuiltinFnIdTrunc:
            out_val->data.x_f32 = truncf(op->data.x_f32);
            break;
        case BuiltinFnIdNearbyInt:
            out_val->data.x_f32 = nearbyintf(op->data.x_f32);
            break;
        case BuiltinFnIdRound:
            out_val->data.x_f32 = roundf(op->data.x_f32);
            break;
        default:
            zig_unreachable();
        };
        break;
    }
    case 64: {
        switch (fop) {
        case BuiltinFnIdSqrt:
            out_val->data.x_f64 = sqrt(op->data.x_f64);
            break;
        case BuiltinFnIdSin:
            out_val->data.x_f64 = sin(op->data.x_f64);
            break;
        case BuiltinFnIdCos:
            out_val->data.x_f64 = cos(op->data.x_f64);
            break;
        case BuiltinFnIdExp:
            out_val->data.x_f64 = exp(op->data.x_f64);
            break;
        case BuiltinFnIdExp2:
            out_val->data.x_f64 = exp2(op->data.x_f64);
            break;
        case BuiltinFnIdLog:
            out_val->data.x_f64 = log(op->data.x_f64);
            break;
        case BuiltinFnIdLog10:
            out_val->data.x_f64 = log10(op->data.x_f64);
            break;
        case BuiltinFnIdLog2:
            out_val->data.x_f64 = log2(op->data.x_f64);
            break;
        case BuiltinFnIdFabs:
            out_val->data.x_f64 = fabs(op->data.x_f64);
            break;
        case BuiltinFnIdFloor:
            out_val->data.x_f64 = floor(op->data.x_f64);
            break;
        case BuiltinFnIdCeil:
            out_val->data.x_f64 = ceil(op->data.x_f64);
            break;
        case BuiltinFnIdTrunc:
            out_val->data.x_f64 = trunc(op->data.x_f64);
            break;
        case BuiltinFnIdNearbyInt:
            out_val->data.x_f64 = nearbyint(op->data.x_f64);
            break;
        case BuiltinFnIdRound:
            out_val->data.x_f64 = round(op->data.x_f64);
            break;
        default:
            zig_unreachable();
        }
        break;
    }
    case 80:
        return ir_add_error_node(ira, source_node,
            buf_sprintf("compiler bug: TODO: implement '%s' for type '%s'. See https://github.com/ziglang/zig/issues/4026",
                float_op_to_name(fop), buf_ptr(&float_type->name)));
    case 128: {
        float128_t *out, *in;
        if (float_type->id == ZigTypeIdComptimeFloat) {
            out = &out_val->data.x_bigfloat.value;
            in = &op->data.x_bigfloat.value;
        } else {
            out = &out_val->data.x_f128;
            in = &op->data.x_f128;
        }
        switch (fop) {
        case BuiltinFnIdSqrt:
            f128M_sqrt(in, out);
            break;
        case BuiltinFnIdFabs:
            f128M_abs(in, out);
            break;
        case BuiltinFnIdFloor:
            f128M_roundToInt(in, softfloat_round_min, false, out);
            break;
        case BuiltinFnIdCeil:
            f128M_roundToInt(in, softfloat_round_max, false, out);
        break;
        case BuiltinFnIdTrunc:
            f128M_trunc(in, out);
            break;
        case BuiltinFnIdRound:
            f128M_roundToInt(in, softfloat_round_near_maxMag, false, out);
            break;
        case BuiltinFnIdNearbyInt:
        case BuiltinFnIdSin:
        case BuiltinFnIdCos:
        case BuiltinFnIdExp:
        case BuiltinFnIdExp2:
        case BuiltinFnIdLog:
        case BuiltinFnIdLog10:
        case BuiltinFnIdLog2:
            return ir_add_error_node(ira, source_node,
                buf_sprintf("compiler bug: TODO: implement '%s' for type '%s'. See https://github.com/ziglang/zig/issues/4026",
                    float_op_to_name(fop), buf_ptr(&float_type->name)));
        default:
            zig_unreachable();
        }
        break;
    }
    default:
        zig_unreachable();
    }
    out_val->special = ConstValSpecialStatic;
    return nullptr;
}

static Stage1AirInst *ir_analyze_instruction_float_op(IrAnalyze *ira, Stage1ZirInstFloatOp *instruction) {
    Stage1AirInst *operand = instruction->operand->child;
    ZigType *operand_type = operand->value->type;
    if (type_is_invalid(operand_type))
        return ira->codegen->invalid_inst_gen;

    // This instruction accepts floats and vectors of floats.
    ZigType *scalar_type = (operand_type->id == ZigTypeIdVector) ?
        operand_type->data.vector.elem_type : operand_type;

    if (scalar_type->id != ZigTypeIdFloat && scalar_type->id != ZigTypeIdComptimeFloat) {
        ir_add_error(ira, operand,
            buf_sprintf("expected float type, found '%s'", buf_ptr(&scalar_type->name)));
        return ira->codegen->invalid_inst_gen;
    }

    if (instr_is_comptime(operand)) {
        ZigValue *operand_val = ir_resolve_const(ira, operand, UndefOk);
        if (operand_val == nullptr)
            return ira->codegen->invalid_inst_gen;
        if (operand_val->special == ConstValSpecialUndef)
            return ir_const_undef(ira, instruction->base.scope, instruction->base.source_node, operand_type);

        Stage1AirInst *result = ir_const(ira, instruction->base.scope, instruction->base.source_node, operand_type);
        ZigValue *out_val = result->value;

        if (operand_type->id == ZigTypeIdVector) {
            expand_undef_array(ira->codegen, operand_val);
            out_val->special = ConstValSpecialUndef;
            expand_undef_array(ira->codegen, out_val);
            size_t len = operand_type->data.vector.len;
            for (size_t i = 0; i < len; i += 1) {
                ZigValue *elem_operand = &operand_val->data.x_array.data.s_none.elements[i];
                ZigValue *float_out_val = &out_val->data.x_array.data.s_none.elements[i];
                src_assert(elem_operand->type == scalar_type, instruction->base.source_node);
                src_assert(float_out_val->type == scalar_type, instruction->base.source_node);
                ErrorMsg *msg = ir_eval_float_op(ira, instruction->base.scope, instruction->base.source_node, instruction->fn_id, scalar_type,
                        elem_operand, float_out_val);
                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_inst_gen;
                }
                float_out_val->type = scalar_type;
            }
            out_val->type = operand_type;
            out_val->special = ConstValSpecialStatic;
        } else {
            if (ir_eval_float_op(ira, instruction->base.scope, instruction->base.source_node, instruction->fn_id, scalar_type,
                    operand_val, out_val) != nullptr)
            {
                return ira->codegen->invalid_inst_gen;
            }
        }
        return result;
    }

    src_assert(scalar_type->id == ZigTypeIdFloat, instruction->base.source_node);

    return ir_build_float_op_gen(ira, instruction->base.scope, instruction->base.source_node, operand, instruction->fn_id, operand_type);
}

static Stage1AirInst *ir_analyze_instruction_bswap(IrAnalyze *ira, Stage1ZirInstBswap *instruction) {
    Error err;

    ZigType *int_type = ir_resolve_int_type(ira, instruction->type->child);
    if (type_is_invalid(int_type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *uncasted_op = instruction->op->child;
    if (type_is_invalid(uncasted_op->value->type))
        return ira->codegen->invalid_inst_gen;

    uint32_t vector_len = UINT32_MAX; // means not a vector
    if (uncasted_op->value->type->id == ZigTypeIdArray) {
        bool can_be_vec_elem;
        if ((err = is_valid_vector_elem_type(ira->codegen, uncasted_op->value->type->data.array.child_type,
                        &can_be_vec_elem)))
        {
            return ira->codegen->invalid_inst_gen;
        }
        if (can_be_vec_elem) {
            vector_len = uncasted_op->value->type->data.array.len;
        }
    } else if (uncasted_op->value->type->id == ZigTypeIdVector) {
        vector_len = uncasted_op->value->type->data.vector.len;
    }

    bool is_vector = (vector_len != UINT32_MAX);
    ZigType *op_type = is_vector ? get_vector_type(ira->codegen, vector_len, int_type) : int_type;

    Stage1AirInst *op = ir_implicit_cast(ira, uncasted_op, op_type);
    if (type_is_invalid(op->value->type))
        return ira->codegen->invalid_inst_gen;

    if (int_type->data.integral.bit_count == 8 || int_type->data.integral.bit_count == 0)
        return op;

    if (int_type->data.integral.bit_count % 8 != 0) {
        ir_add_error_node(ira, instruction->op->source_node,
            buf_sprintf("@byteSwap 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_inst_gen;
    }

    if (instr_is_comptime(op)) {
        ZigValue *val = ir_resolve_const(ira, op, UndefOk);
        if (val == nullptr)
            return ira->codegen->invalid_inst_gen;
        if (val->special == ConstValSpecialUndef)
            return ir_const_undef(ira, instruction->base.scope, instruction->base.source_node, op_type);

        Stage1AirInst *result = ir_const(ira, instruction->base.scope, instruction->base.source_node, op_type);
        const size_t buf_size = int_type->data.integral.bit_count / 8;
        uint8_t *buf = heap::c_allocator.allocate_nonzero<uint8_t>(buf_size);
        if (is_vector) {
            expand_undef_array(ira->codegen, val);
            result->value->data.x_array.data.s_none.elements = ira->codegen->pass1_arena->allocate<ZigValue>(op_type->data.vector.len);
            for (unsigned i = 0; i < op_type->data.vector.len; i += 1) {
                ZigValue *op_elem_val = &val->data.x_array.data.s_none.elements[i];
                if ((err = ir_resolve_const_val(ira->codegen, ira->new_irb.exec, instruction->base.source_node,
                    op_elem_val, UndefOk)))
                {
                    return ira->codegen->invalid_inst_gen;
                }
                ZigValue *result_elem_val = &result->value->data.x_array.data.s_none.elements[i];
                result_elem_val->type = int_type;
                result_elem_val->special = op_elem_val->special;
                if (op_elem_val->special == ConstValSpecialUndef)
                    continue;

                bigint_write_twos_complement(&op_elem_val->data.x_bigint, buf, int_type->data.integral.bit_count, true);
                bigint_read_twos_complement(&result->value->data.x_array.data.s_none.elements[i].data.x_bigint,
                        buf, int_type->data.integral.bit_count, false,
                        int_type->data.integral.is_signed);
            }
        } else {
            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);
        }
        heap::c_allocator.deallocate(buf, buf_size);
        return result;
    }

    return ir_build_bswap_gen(ira, instruction->base.scope, instruction->base.source_node, op_type, op);
}

static Stage1AirInst *ir_analyze_instruction_bit_reverse(IrAnalyze *ira, Stage1ZirInstBitReverse *instruction) {
    ZigType *int_type = ir_resolve_int_type(ira, instruction->type->child);
    if (type_is_invalid(int_type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *op = ir_implicit_cast(ira, instruction->op->child, int_type);
    if (type_is_invalid(op->value->type))
        return ira->codegen->invalid_inst_gen;

    if (int_type->data.integral.bit_count == 0) {
        Stage1AirInst *result = ir_const(ira, instruction->base.scope, instruction->base.source_node, int_type);
        bigint_init_unsigned(&result->value->data.x_bigint, 0);
        return result;
    }

    if (instr_is_comptime(op)) {
        ZigValue *val = ir_resolve_const(ira, op, UndefOk);
        if (val == nullptr)
            return ira->codegen->invalid_inst_gen;
        if (val->special == ConstValSpecialUndef)
            return ir_const_undef(ira, instruction->base.scope, instruction->base.source_node, int_type);

        Stage1AirInst *result = ir_const(ira, instruction->base.scope, instruction->base.source_node, int_type);
        size_t num_bits = int_type->data.integral.bit_count;
        size_t buf_size = (num_bits + 7) / 8;
        uint8_t *comptime_buf = heap::c_allocator.allocate_nonzero<uint8_t>(buf_size);
        uint8_t *result_buf = heap::c_allocator.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);

        heap::c_allocator.deallocate(comptime_buf, buf_size);
        heap::c_allocator.deallocate(result_buf, buf_size);
        return result;
    }

    return ir_build_bit_reverse_gen(ira, instruction->base.scope, instruction->base.source_node, int_type, op);
}


static Stage1AirInst *ir_analyze_instruction_enum_to_int(IrAnalyze *ira, Stage1ZirInstEnumToInt *instruction) {
    Stage1AirInst *target = instruction->target->child;
    if (type_is_invalid(target->value->type))
        return ira->codegen->invalid_inst_gen;

    return ir_analyze_enum_to_int(ira, instruction->base.scope, instruction->base.source_node, target);
}

static Stage1AirInst *ir_analyze_instruction_int_to_enum(IrAnalyze *ira, Stage1ZirInstIntToEnum *instruction) {
    Error err;
    Stage1AirInst *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_inst_gen;

    if (dest_type->id != ZigTypeIdEnum) {
        ir_add_error_node(ira, instruction->dest_type->source_node,
            buf_sprintf("expected enum, found type '%s'", buf_ptr(&dest_type->name)));
        return ira->codegen->invalid_inst_gen;
    }

    if ((err = type_resolve(ira->codegen, dest_type, ResolveStatusZeroBitsKnown)))
        return ira->codegen->invalid_inst_gen;

    ZigType *tag_type = dest_type->data.enumeration.tag_int_type;

    Stage1AirInst *target = instruction->target->child;
    if (type_is_invalid(target->value->type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *casted_target = ir_analyze_int_cast(ira, instruction->base.scope,
            instruction->base.source_node, tag_type, instruction->dest_type->source_node,
            target, instruction->target->source_node);
    if (type_is_invalid(casted_target->value->type))
        return ira->codegen->invalid_inst_gen;

    return ir_analyze_int_to_enum(ira, instruction->base.scope, instruction->base.source_node, casted_target, dest_type);
}

static Stage1AirInst *ir_analyze_instruction_check_runtime_scope(IrAnalyze *ira, Stage1ZirInstCheckRuntimeScope *instruction) {
    Stage1AirInst *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_inst_gen;

    Stage1AirInst *is_comptime_inst = instruction->is_comptime->child;
    bool is_comptime;
    if (!ir_resolve_bool(ira, is_comptime_inst, &is_comptime))
        return ira->codegen->invalid_inst_gen;

    if (!scope_is_comptime && is_comptime) {
        ErrorMsg *msg = ir_add_error_node(ira, instruction->base.source_node,
            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_inst_gen;
    }

    return ir_const_void(ira, instruction->base.scope, instruction->base.source_node);
}

static Stage1AirInst *ir_analyze_instruction_has_decl(IrAnalyze *ira, Stage1ZirInstHasDecl *instruction) {
    ZigType *container_type = ir_resolve_type(ira, instruction->container->child);
    if (type_is_invalid(container_type))
        return ira->codegen->invalid_inst_gen;

    Buf *name = ir_resolve_str(ira, instruction->name->child);
    if (name == nullptr)
        return ira->codegen->invalid_inst_gen;

    if (!is_container(container_type)) {
        ir_add_error_node(ira, instruction->container->source_node,
            buf_sprintf("expected struct, enum, or union; found '%s'", buf_ptr(&container_type->name)));
        return ira->codegen->invalid_inst_gen;
    }

    ScopeDecls *container_scope = get_container_scope(container_type);
    Tld *tld = find_container_decl(ira->codegen, container_scope, name);
    if (tld == nullptr)
        return ir_const_bool(ira, instruction->base.scope, instruction->base.source_node, false);

    if (tld->visib_mod == VisibModPrivate && tld->import != get_scope_import(instruction->base.scope)) {
        return ir_const_bool(ira, instruction->base.scope, instruction->base.source_node, false);
    }

    return ir_const_bool(ira, instruction->base.scope, instruction->base.source_node, true);
}

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 = heap::c_allocator.create<TldVar>();
    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_inst_gen->value, &tld_var->base, g->builtin_types.entry_invalid);
    scope_decls->decl_table.put(var_name, &tld_var->base);
}

static Stage1AirInst *ir_analyze_instruction_undeclared_ident(IrAnalyze *ira, Stage1ZirInstUndeclaredIdent *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(ira->codegen, instruction->base.scope,
            instruction->base.source_node, instruction->name);
    ir_add_error_node(ira, instruction->base.source_node,
            buf_sprintf("use of undeclared identifier '%s'", buf_ptr(instruction->name)));
    return ira->codegen->invalid_inst_gen;
}

static Stage1AirInst *ir_analyze_instruction_end_expr(IrAnalyze *ira, Stage1ZirInstEndExpr *instruction) {
    Stage1AirInst *value = instruction->value->child;
    if (type_is_invalid(value->value->type))
        return ira->codegen->invalid_inst_gen;

    bool was_written = instruction->result_loc->written;
    Stage1AirInst *result_loc = ir_resolve_result(ira, &instruction->base, instruction->result_loc,
            value->value->type, value, false, true);
    if (result_loc != nullptr) {
        if (type_is_invalid(result_loc->value->type))
            return ira->codegen->invalid_inst_gen;
        if (result_loc->value->type->id == ZigTypeIdUnreachable)
            return result_loc;

        if (!was_written || instruction->result_loc->id == ResultLocIdPeer) {
            Stage1AirInst *store_ptr = ir_analyze_store_ptr(ira, instruction->base.scope, instruction->base.source_node, result_loc, value,
                    instruction->result_loc->allow_write_through_const);
            if (type_is_invalid(store_ptr->value->type)) {
                if (instruction->result_loc->id == ResultLocIdReturn &&
                    (value->value->type->id == ZigTypeIdErrorUnion || value->value->type->id == ZigTypeIdErrorSet) &&
                    ira->explicit_return_type->id != ZigTypeIdErrorUnion && ira->explicit_return_type->id != ZigTypeIdErrorSet)
                {
                    add_error_note(ira->codegen, ira->new_irb.exec->first_err_trace_msg,
                        ira->explicit_return_type_source_node, buf_create_from_str("function cannot return an error"));
                }
                return ira->codegen->invalid_inst_gen;
            }
        }

        if (result_loc->value->data.x_ptr.mut == ConstPtrMutInfer &&
            instruction->result_loc->id != ResultLocIdPeer)
        {
            if (instr_is_comptime(value)) {
                result_loc->value->data.x_ptr.mut = ConstPtrMutComptimeConst;
            } else {
                result_loc->value->special = ConstValSpecialRuntime;
            }
        }
    }

    return ir_const_void(ira, instruction->base.scope, instruction->base.source_node);
}

static Stage1AirInst *ir_analyze_instruction_implicit_cast(IrAnalyze *ira, Stage1ZirInstImplicitCast *instruction) {
    Stage1AirInst *operand = instruction->operand->child;
    if (type_is_invalid(operand->value->type))
        return operand;

    ZigType *dest_type = ir_resolve_type(ira, instruction->result_loc_cast->base.source_instruction->child);
    if (type_is_invalid(dest_type))
        return ira->codegen->invalid_inst_gen;
    return ir_implicit_cast2(ira, instruction->base.scope, instruction->base.source_node,
            operand, dest_type);
}

static Stage1AirInst *ir_analyze_instruction_bit_cast_src(IrAnalyze *ira, Stage1ZirInstBitCast *instruction) {
    Stage1AirInst *operand = instruction->operand->child;
    if (type_is_invalid(operand->value->type))
        return operand;

    Stage1AirInst *result_loc = ir_resolve_result(ira, &instruction->base,
            &instruction->result_loc_bit_cast->base, operand->value->type, operand, false, true);
    if (result_loc != nullptr &&
            (type_is_invalid(result_loc->value->type) || result_loc->value->type->id == ZigTypeIdUnreachable))
    {
        return result_loc;
    }

    ZigType *dest_type = ir_resolve_type(ira,
            instruction->result_loc_bit_cast->base.source_instruction->child);
    if (type_is_invalid(dest_type))
        return ira->codegen->invalid_inst_gen;
    return ir_analyze_bit_cast(ira, instruction->base.scope, instruction->base.source_node, operand, dest_type);
}

static Stage1AirInst *ir_analyze_instruction_union_init_named_field(IrAnalyze *ira,
        Stage1ZirInstUnionInitNamedField *instruction)
{
    ZigType *union_type = ir_resolve_type(ira, instruction->union_type->child);
    if (type_is_invalid(union_type))
        return ira->codegen->invalid_inst_gen;

    if (union_type->id != ZigTypeIdUnion) {
        ir_add_error_node(ira, instruction->union_type->source_node,
            buf_sprintf("non-union type '%s' passed to @unionInit", buf_ptr(&union_type->name)));
        return ira->codegen->invalid_inst_gen;
    }

    Buf *field_name = ir_resolve_str(ira, instruction->field_name->child);
    if (field_name == nullptr)
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *field_result_loc = instruction->field_result_loc->child;
    if (type_is_invalid(field_result_loc->value->type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *result_loc = instruction->result_loc->child;
    if (type_is_invalid(result_loc->value->type))
        return ira->codegen->invalid_inst_gen;

    return ir_analyze_union_init(ira, instruction->base.scope, instruction->base.source_node, instruction->base.source_node,
            union_type, field_name, field_result_loc, result_loc);
}

static Stage1AirInst *ir_analyze_instruction_suspend_begin(IrAnalyze *ira, Stage1ZirInstSuspendBegin *instruction) {
    return ir_build_suspend_begin_gen(ira, instruction->base.scope, instruction->base.source_node);
}

static Stage1AirInst *ir_analyze_instruction_suspend_finish(IrAnalyze *ira, Stage1ZirInstSuspendFinish *instruction) {
    Stage1AirInst *begin_base = instruction->begin->base.child;
    if (type_is_invalid(begin_base->value->type))
        return ira->codegen->invalid_inst_gen;
    src_assert(begin_base->id == Stage1AirInstIdSuspendBegin, instruction->base.source_node);
    Stage1AirInstSuspendBegin *begin = reinterpret_cast<Stage1AirInstSuspendBegin *>(begin_base);

    ZigFn *fn_entry = ira->fn;
    src_assert(fn_entry != nullptr, instruction->base.source_node);

    if (fn_entry->inferred_async_node == nullptr) {
        fn_entry->inferred_async_node = instruction->base.source_node;
    }

    return ir_build_suspend_finish_gen(ira, instruction->base.scope, instruction->base.source_node, begin);
}

static Stage1AirInst *analyze_frame_ptr_to_anyframe_T(IrAnalyze *ira, Scope *scope, AstNode *source_node,
        Stage1AirInst *frame_ptr, ZigFn **target_fn)
{
    if (type_is_invalid(frame_ptr->value->type))
        return ira->codegen->invalid_inst_gen;

    *target_fn = nullptr;

    ZigType *result_type;
    Stage1AirInst *frame;
    if (frame_ptr->value->type->id == ZigTypeIdPointer &&
        frame_ptr->value->type->data.pointer.ptr_len == PtrLenSingle &&
        frame_ptr->value->type->data.pointer.child_type->id == ZigTypeIdFnFrame)
    {
        ZigFn *func = frame_ptr->value->type->data.pointer.child_type->data.frame.fn;
        result_type = func->type_entry->data.fn.fn_type_id.return_type;
        *target_fn = func;
        frame = frame_ptr;
    } else {
        frame = ir_get_deref(ira, scope, source_node, frame_ptr, nullptr);
        if (frame->value->type->id == ZigTypeIdPointer &&
            frame->value->type->data.pointer.ptr_len == PtrLenSingle &&
            frame->value->type->data.pointer.child_type->id == ZigTypeIdFnFrame)
        {
            ZigFn *func = frame->value->type->data.pointer.child_type->data.frame.fn;
            result_type = func->type_entry->data.fn.fn_type_id.return_type;
            *target_fn = func;
        } else if (frame->value->type->id != ZigTypeIdAnyFrame ||
            frame->value->type->data.any_frame.result_type == nullptr)
        {
            ir_add_error_node(ira, source_node,
                buf_sprintf("expected anyframe->T, found '%s'", buf_ptr(&frame->value->type->name)));
            return ira->codegen->invalid_inst_gen;
        } else {
            result_type = frame->value->type->data.any_frame.result_type;
        }
    }

    ZigType *any_frame_type = get_any_frame_type(ira->codegen, result_type);
    Stage1AirInst *casted_frame = ir_implicit_cast(ira, frame, any_frame_type);
    if (type_is_invalid(casted_frame->value->type))
        return ira->codegen->invalid_inst_gen;

    return casted_frame;
}

static Stage1AirInst *ir_analyze_instruction_await(IrAnalyze *ira, Stage1ZirInstAwait *instruction) {
    Stage1AirInst *operand = instruction->frame->child;
    if (type_is_invalid(operand->value->type))
        return ira->codegen->invalid_inst_gen;
    ZigFn *target_fn;
    Stage1AirInst *frame = analyze_frame_ptr_to_anyframe_T(ira, instruction->base.scope, instruction->base.source_node, operand, &target_fn);
    if (type_is_invalid(frame->value->type))
        return ira->codegen->invalid_inst_gen;

    ZigType *result_type = frame->value->type->data.any_frame.result_type;

    ZigFn *fn_entry = ira->fn;
    src_assert(fn_entry != nullptr, instruction->base.source_node);

    // If it's not @Frame(func) then it's definitely a suspend point
    if (target_fn == nullptr && !instruction->is_nosuspend) {
        if (fn_entry->inferred_async_node == nullptr) {
            fn_entry->inferred_async_node = instruction->base.source_node;
        }
    }

    if (type_can_fail(result_type)) {
        fn_entry->calls_or_awaits_errorable_fn = true;
    }

    Stage1AirInst *result_loc;
    if (type_has_bits(ira->codegen, result_type)) {
        result_loc = ir_resolve_result(ira, &instruction->base, instruction->result_loc,
                result_type, nullptr, true, true);
        if (result_loc != nullptr &&
                (type_is_invalid(result_loc->value->type) || result_loc->value->type->id == ZigTypeIdUnreachable))
        {
            return result_loc;
        }
    } else {
        result_loc = nullptr;
    }

    Stage1AirInstAwait *result = ir_build_await_gen(ira, instruction->base.scope, instruction->base.source_node, frame, result_type, result_loc,
            instruction->is_nosuspend);
    result->target_fn = target_fn;
    fn_entry->await_list.append(result);
    return ir_finish_anal(ira, &result->base);
}

static Stage1AirInst *ir_analyze_instruction_resume(IrAnalyze *ira, Stage1ZirInstResume *instruction) {
    Stage1AirInst *frame_ptr = instruction->frame->child;
    if (type_is_invalid(frame_ptr->value->type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *frame;
    if (frame_ptr->value->type->id == ZigTypeIdPointer &&
        frame_ptr->value->type->data.pointer.ptr_len == PtrLenSingle &&
        frame_ptr->value->type->data.pointer.child_type->id == ZigTypeIdFnFrame)
    {
        frame = frame_ptr;
    } else {
        frame = ir_get_deref(ira, instruction->base.scope, instruction->base.source_node,
                frame_ptr, nullptr);
    }

    ZigType *any_frame_type = get_any_frame_type(ira->codegen, nullptr);
    Stage1AirInst *casted_frame = ir_implicit_cast2(ira, instruction->frame->scope,
            instruction->frame->source_node, frame, any_frame_type);
    if (type_is_invalid(casted_frame->value->type))
        return ira->codegen->invalid_inst_gen;

    return ir_build_resume_gen(ira, instruction->base.scope, instruction->base.source_node, casted_frame);
}

static Stage1AirInst *ir_analyze_instruction_spill_begin(IrAnalyze *ira, Stage1ZirInstSpillBegin *instruction) {
    if (ir_should_inline(ira->zir, instruction->base.scope))
        return ir_const_void(ira, instruction->base.scope, instruction->base.source_node);

    Stage1AirInst *operand = instruction->operand->child;
    if (type_is_invalid(operand->value->type))
        return ira->codegen->invalid_inst_gen;

    if (!type_has_bits(ira->codegen, operand->value->type))
        return ir_const_void(ira, instruction->base.scope, instruction->base.source_node);

    switch (instruction->spill_id) {
        case SpillIdInvalid:
            zig_unreachable();
        case SpillIdRetErrCode:
            ira->new_irb.exec->need_err_code_spill = true;
            break;
    }

    return ir_build_spill_begin_gen(ira, instruction->base.scope, instruction->base.source_node, operand, instruction->spill_id);
}

static Stage1AirInst *ir_analyze_instruction_spill_end(IrAnalyze *ira, Stage1ZirInstSpillEnd *instruction) {
    Stage1AirInst *operand = instruction->begin->operand->child;
    if (type_is_invalid(operand->value->type))
        return ira->codegen->invalid_inst_gen;

    if (ir_should_inline(ira->zir, instruction->base.scope) ||
        !type_has_bits(ira->codegen, operand->value->type) ||
        instr_is_comptime(operand))
    {
        return operand;
    }

    src_assert(instruction->begin->base.child->id == Stage1AirInstIdSpillBegin, instruction->base.source_node);
    Stage1AirInstSpillBegin *begin = reinterpret_cast<Stage1AirInstSpillBegin *>(instruction->begin->base.child);

    return ir_build_spill_end_gen(ira, instruction->base.scope, instruction->base.source_node, begin, operand->value->type);
}

static Stage1AirInst *ir_analyze_instruction_src(IrAnalyze *ira, Stage1ZirInstSrc *instruction) {
    ZigFn *fn_entry = scope_fn_entry(instruction->base.scope);
    if (fn_entry == nullptr) {
        ir_add_error_node(ira, instruction->base.source_node, buf_sprintf("@src outside function"));
        return ira->codegen->invalid_inst_gen;
    }

    ZigType *source_location_type = get_builtin_type(ira->codegen, "SourceLocation");
    if (type_resolve(ira->codegen, source_location_type, ResolveStatusSizeKnown)) {
        zig_unreachable();
    }

    ZigValue *result = ira->codegen->pass1_arena->create<ZigValue>();
    result->special = ConstValSpecialStatic;
    result->type = source_location_type;

    ZigValue **fields = alloc_const_vals_ptrs(ira->codegen, 4);
    result->data.x_struct.fields = fields;

    // file: [:0]const u8
    ensure_field_index(source_location_type, "file", 0);
    fields[0]->special = ConstValSpecialStatic;

    ZigType *import = instruction->base.source_node->owner;
    RootStruct *root_struct = import->data.structure.root_struct;
    Buf *path = root_struct->path;
    fields[0] = create_sentineled_str_lit(
        ira->codegen, path,
        ira->codegen->intern.for_zero_byte());

    // fn_name: [:0]const u8
    ensure_field_index(source_location_type, "fn_name", 1);
    fields[1]->special = ConstValSpecialStatic;
    fields[1] = create_sentineled_str_lit(
        ira->codegen, &fn_entry->symbol_name,
        ira->codegen->intern.for_zero_byte());

    TokenLoc tok_loc = root_struct->token_locs[instruction->base.source_node->main_token];

    // line: u32
    ensure_field_index(source_location_type, "line", 2);
    fields[2]->special = ConstValSpecialStatic;
    fields[2]->type = ira->codegen->builtin_types.entry_u32;
    bigint_init_unsigned(&fields[2]->data.x_bigint, tok_loc.line + 1);

    // column: u32
    ensure_field_index(source_location_type, "column", 3);
    fields[3]->special = ConstValSpecialStatic;
    fields[3]->type = ira->codegen->builtin_types.entry_u32;
    bigint_init_unsigned(&fields[3]->data.x_bigint, tok_loc.column + 1);

    return ir_const_move(ira, instruction->base.scope, instruction->base.source_node, result);
}

static Stage1AirInst *ir_analyze_instruction_prefetch(IrAnalyze *ira, Stage1ZirInstPrefetch *instruction) {
    Stage1AirInst *ptr = instruction->ptr->child;
    if (type_is_invalid(ptr->value->type))
        return ira->codegen->invalid_inst_gen;

    Stage1AirInst *raw_options_inst = instruction->options->child;
    if (type_is_invalid(raw_options_inst->value->type))
        return ira->codegen->invalid_inst_gen;

    ZigType *options_type = get_builtin_type(ira->codegen, "PrefetchOptions");
    Stage1AirInst *options_inst = ir_implicit_cast(ira, raw_options_inst, options_type);
    if (type_is_invalid(options_inst->value->type))
        return ira->codegen->invalid_inst_gen;

    ZigValue *options_val = ir_resolve_const(ira, options_inst, UndefBad);
    if (options_val == nullptr)
        return ira->codegen->invalid_inst_gen;

    ZigValue *rw_val = get_const_field(ira, options_inst->source_node, options_val, "rw", 0);
    if (rw_val == nullptr)
        return ira->codegen->invalid_inst_gen;
    PrefetchRw rw = (PrefetchRw)bigint_as_u8(&rw_val->data.x_enum_tag);

    ZigValue *locality_val = get_const_field(ira, options_inst->source_node, options_val, "locality", 1);
    if (locality_val == nullptr)
        return ira->codegen->invalid_inst_gen;
    uint8_t locality = bigint_as_u8(&locality_val->data.x_bigint);
    assert(locality <= 3);

    ZigValue *cache_val = get_const_field(ira, options_inst->source_node, options_val, "cache", 2);
    if (cache_val == nullptr)
        return ira->codegen->invalid_inst_gen;
    PrefetchCache cache = (PrefetchCache)bigint_as_u8(&cache_val->data.x_enum_tag);

    Stage1AirInstPrefetch *air_instruction = ir_build_inst_void<Stage1AirInstPrefetch>(&ira->new_irb,
            instruction->base.scope, instruction->base.source_node);
    air_instruction->ptr = ptr;
    air_instruction->rw = rw;
    air_instruction->locality = locality;
    air_instruction->cache = cache;

    ir_ref_inst_gen(ptr);

    return &air_instruction->base;
}

static Stage1AirInst *ir_analyze_instruction_base(IrAnalyze *ira, Stage1ZirInst *instruction) {
    switch (instruction->id) {
        case Stage1ZirInstIdInvalid:
            zig_unreachable();

        case Stage1ZirInstIdReturn:
            return ir_analyze_instruction_return(ira, (Stage1ZirInstReturn *)instruction);
        case Stage1ZirInstIdConst:
            return ir_analyze_instruction_const(ira, (Stage1ZirInstConst *)instruction);
        case Stage1ZirInstIdUnOp:
            return ir_analyze_instruction_un_op(ira, (Stage1ZirInstUnOp *)instruction);
        case Stage1ZirInstIdBinOp:
            return ir_analyze_instruction_bin_op(ira, (Stage1ZirInstBinOp *)instruction);
        case Stage1ZirInstIdMergeErrSets:
            return ir_analyze_instruction_merge_err_sets(ira, (Stage1ZirInstMergeErrSets *)instruction);
        case Stage1ZirInstIdDeclVar:
            return ir_analyze_instruction_decl_var(ira, (Stage1ZirInstDeclVar *)instruction);
        case Stage1ZirInstIdLoadPtr:
            return ir_analyze_instruction_load_ptr(ira, (Stage1ZirInstLoadPtr *)instruction);
        case Stage1ZirInstIdStorePtr:
            return ir_analyze_instruction_store_ptr(ira, (Stage1ZirInstStorePtr *)instruction);
        case Stage1ZirInstIdElemPtr:
            return ir_analyze_instruction_elem_ptr(ira, (Stage1ZirInstElemPtr *)instruction);
        case Stage1ZirInstIdVarPtr:
            return ir_analyze_instruction_var_ptr(ira, (Stage1ZirInstVarPtr *)instruction);
        case Stage1ZirInstIdFieldPtr:
            return ir_analyze_instruction_field_ptr(ira, (Stage1ZirInstFieldPtr *)instruction);
        case Stage1ZirInstIdCall:
            return ir_analyze_instruction_call(ira, (Stage1ZirInstCall *)instruction);
        case Stage1ZirInstIdCallArgs:
            return ir_analyze_instruction_call_args(ira, (Stage1ZirInstCallArgs *)instruction);
        case Stage1ZirInstIdCallExtra:
            return ir_analyze_instruction_call_extra(ira, (Stage1ZirInstCallExtra *)instruction);
        case Stage1ZirInstIdAsyncCallExtra:
            return ir_analyze_instruction_async_call_extra(ira, (Stage1ZirInstAsyncCallExtra *)instruction);
        case Stage1ZirInstIdBr:
            return ir_analyze_instruction_br(ira, (Stage1ZirInstBr *)instruction);
        case Stage1ZirInstIdCondBr:
            return ir_analyze_instruction_cond_br(ira, (Stage1ZirInstCondBr *)instruction);
        case Stage1ZirInstIdUnreachable:
            return ir_analyze_instruction_unreachable(ira, (Stage1ZirInstUnreachable *)instruction);
        case Stage1ZirInstIdPhi:
            return ir_analyze_instruction_phi(ira, (Stage1ZirInstPhi *)instruction);
        case Stage1ZirInstIdTypeOf:
            return ir_analyze_instruction_typeof(ira, (Stage1ZirInstTypeOf *)instruction);
        case Stage1ZirInstIdSetCold:
            return ir_analyze_instruction_set_cold(ira, (Stage1ZirInstSetCold *)instruction);
        case Stage1ZirInstIdSetRuntimeSafety:
            return ir_analyze_instruction_set_runtime_safety(ira, (Stage1ZirInstSetRuntimeSafety *)instruction);
        case Stage1ZirInstIdSetFloatMode:
            return ir_analyze_instruction_set_float_mode(ira, (Stage1ZirInstSetFloatMode *)instruction);
        case Stage1ZirInstIdAnyFrameType:
            return ir_analyze_instruction_any_frame_type(ira, (Stage1ZirInstAnyFrameType *)instruction);
        case Stage1ZirInstIdSliceType:
            return ir_analyze_instruction_slice_type(ira, (Stage1ZirInstSliceType *)instruction);
        case Stage1ZirInstIdAsm:
            return ir_analyze_instruction_asm(ira, (Stage1ZirInstAsm *)instruction);
        case Stage1ZirInstIdArrayType:
            return ir_analyze_instruction_array_type(ira, (Stage1ZirInstArrayType *)instruction);
        case Stage1ZirInstIdSizeOf:
            return ir_analyze_instruction_size_of(ira, (Stage1ZirInstSizeOf *)instruction);
        case Stage1ZirInstIdTestNonNull:
            return ir_analyze_instruction_test_non_null(ira, (Stage1ZirInstTestNonNull *)instruction);
        case Stage1ZirInstIdOptionalUnwrapPtr:
            return ir_analyze_instruction_optional_unwrap_ptr(ira, (Stage1ZirInstOptionalUnwrapPtr *)instruction);
        case Stage1ZirInstIdClz:
            return ir_analyze_instruction_clz(ira, (Stage1ZirInstClz *)instruction);
        case Stage1ZirInstIdCtz:
            return ir_analyze_instruction_ctz(ira, (Stage1ZirInstCtz *)instruction);
        case Stage1ZirInstIdPopCount:
            return ir_analyze_instruction_pop_count(ira, (Stage1ZirInstPopCount *)instruction);
        case Stage1ZirInstIdBswap:
            return ir_analyze_instruction_bswap(ira, (Stage1ZirInstBswap *)instruction);
        case Stage1ZirInstIdBitReverse:
            return ir_analyze_instruction_bit_reverse(ira, (Stage1ZirInstBitReverse *)instruction);
        case Stage1ZirInstIdSwitchBr:
            return ir_analyze_instruction_switch_br(ira, (Stage1ZirInstSwitchBr *)instruction);
        case Stage1ZirInstIdSwitchTarget:
            return ir_analyze_instruction_switch_target(ira, (Stage1ZirInstSwitchTarget *)instruction);
        case Stage1ZirInstIdSwitchVar:
            return ir_analyze_instruction_switch_var(ira, (Stage1ZirInstSwitchVar *)instruction);
        case Stage1ZirInstIdSwitchElseVar:
            return ir_analyze_instruction_switch_else_var(ira, (Stage1ZirInstSwitchElseVar *)instruction);
        case Stage1ZirInstIdImport:
            return ir_analyze_instruction_import(ira, (Stage1ZirInstImport *)instruction);
        case Stage1ZirInstIdRef:
            return ir_analyze_instruction_ref(ira, (Stage1ZirInstRef *)instruction);
        case Stage1ZirInstIdContainerInitList:
            return ir_analyze_instruction_container_init_list(ira, (Stage1ZirInstContainerInitList *)instruction);
        case Stage1ZirInstIdContainerInitFields:
            return ir_analyze_instruction_container_init_fields(ira, (Stage1ZirInstContainerInitFields *)instruction);
        case Stage1ZirInstIdCompileErr:
            return ir_analyze_instruction_compile_err(ira, (Stage1ZirInstCompileErr *)instruction);
        case Stage1ZirInstIdCompileLog:
            return ir_analyze_instruction_compile_log(ira, (Stage1ZirInstCompileLog *)instruction);
        case Stage1ZirInstIdErrName:
            return ir_analyze_instruction_err_name(ira, (Stage1ZirInstErrName *)instruction);
        case Stage1ZirInstIdTypeName:
            return ir_analyze_instruction_type_name(ira, (Stage1ZirInstTypeName *)instruction);
        case Stage1ZirInstIdCImport:
            return ir_analyze_instruction_c_import(ira, (Stage1ZirInstCImport *)instruction);
        case Stage1ZirInstIdCInclude:
            return ir_analyze_instruction_c_include(ira, (Stage1ZirInstCInclude *)instruction);
        case Stage1ZirInstIdCDefine:
            return ir_analyze_instruction_c_define(ira, (Stage1ZirInstCDefine *)instruction);
        case Stage1ZirInstIdCUndef:
            return ir_analyze_instruction_c_undef(ira, (Stage1ZirInstCUndef *)instruction);
        case Stage1ZirInstIdEmbedFile:
            return ir_analyze_instruction_embed_file(ira, (Stage1ZirInstEmbedFile *)instruction);
        case Stage1ZirInstIdCmpxchg:
            return ir_analyze_instruction_cmpxchg(ira, (Stage1ZirInstCmpxchg *)instruction);
        case Stage1ZirInstIdFence:
            return ir_analyze_instruction_fence(ira, (Stage1ZirInstFence *)instruction);
        case Stage1ZirInstIdReduce:
            return ir_analyze_instruction_reduce(ira, (Stage1ZirInstReduce *)instruction);
        case Stage1ZirInstIdTruncate:
            return ir_analyze_instruction_truncate(ira, (Stage1ZirInstTruncate *)instruction);
        case Stage1ZirInstIdIntCast:
            return ir_analyze_instruction_int_cast(ira, (Stage1ZirInstIntCast *)instruction);
        case Stage1ZirInstIdFloatCast:
            return ir_analyze_instruction_float_cast(ira, (Stage1ZirInstFloatCast *)instruction);
        case Stage1ZirInstIdErrSetCast:
            return ir_analyze_instruction_err_set_cast(ira, (Stage1ZirInstErrSetCast *)instruction);
        case Stage1ZirInstIdIntToFloat:
            return ir_analyze_instruction_int_to_float(ira, (Stage1ZirInstIntToFloat *)instruction);
        case Stage1ZirInstIdFloatToInt:
            return ir_analyze_instruction_float_to_int(ira, (Stage1ZirInstFloatToInt *)instruction);
        case Stage1ZirInstIdBoolToInt:
            return ir_analyze_instruction_bool_to_int(ira, (Stage1ZirInstBoolToInt *)instruction);
        case Stage1ZirInstIdVectorType:
            return ir_analyze_instruction_vector_type(ira, (Stage1ZirInstVectorType *)instruction);
        case Stage1ZirInstIdShuffleVector:
            return ir_analyze_instruction_shuffle_vector(ira, (Stage1ZirInstShuffleVector *)instruction);
        case Stage1ZirInstIdSelect:
            return ir_analyze_instruction_select(ira, (Stage1ZirInstSelect *)instruction);
        case Stage1ZirInstIdSplat:
            return ir_analyze_instruction_splat(ira, (Stage1ZirInstSplat *)instruction);
        case Stage1ZirInstIdBoolNot:
            return ir_analyze_instruction_bool_not(ira, (Stage1ZirInstBoolNot *)instruction);
        case Stage1ZirInstIdMemset:
            return ir_analyze_instruction_memset(ira, (Stage1ZirInstMemset *)instruction);
        case Stage1ZirInstIdMemcpy:
            return ir_analyze_instruction_memcpy(ira, (Stage1ZirInstMemcpy *)instruction);
        case Stage1ZirInstIdSlice:
            return ir_analyze_instruction_slice(ira, (Stage1ZirInstSlice *)instruction);
        case Stage1ZirInstIdBreakpoint:
            return ir_analyze_instruction_breakpoint(ira, (Stage1ZirInstBreakpoint *)instruction);
        case Stage1ZirInstIdReturnAddress:
            return ir_analyze_instruction_return_address(ira, (Stage1ZirInstReturnAddress *)instruction);
        case Stage1ZirInstIdFrameAddress:
            return ir_analyze_instruction_frame_address(ira, (Stage1ZirInstFrameAddress *)instruction);
        case Stage1ZirInstIdFrameHandle:
            return ir_analyze_instruction_frame_handle(ira, (Stage1ZirInstFrameHandle *)instruction);
        case Stage1ZirInstIdFrameType:
            return ir_analyze_instruction_frame_type(ira, (Stage1ZirInstFrameType *)instruction);
        case Stage1ZirInstIdFrameSize:
            return ir_analyze_instruction_frame_size(ira, (Stage1ZirInstFrameSize *)instruction);
        case Stage1ZirInstIdAlignOf:
            return ir_analyze_instruction_align_of(ira, (Stage1ZirInstAlignOf *)instruction);
        case Stage1ZirInstIdOverflowOp:
            return ir_analyze_instruction_overflow_op(ira, (Stage1ZirInstOverflowOp *)instruction);
        case Stage1ZirInstIdTestErr:
            return ir_analyze_instruction_test_err(ira, (Stage1ZirInstTestErr *)instruction);
        case Stage1ZirInstIdUnwrapErrCode:
            return ir_analyze_instruction_unwrap_err_code(ira, (Stage1ZirInstUnwrapErrCode *)instruction);
        case Stage1ZirInstIdUnwrapErrPayload:
            return ir_analyze_instruction_unwrap_err_payload(ira, (Stage1ZirInstUnwrapErrPayload *)instruction);
        case Stage1ZirInstIdFnProto:
            return ir_analyze_instruction_fn_proto(ira, (Stage1ZirInstFnProto *)instruction);
        case Stage1ZirInstIdTestComptime:
            return ir_analyze_instruction_test_comptime(ira, (Stage1ZirInstTestComptime *)instruction);
        case Stage1ZirInstIdCheckSwitchProngsUnderNo:
            return ir_analyze_instruction_check_switch_prongs(ira, (Stage1ZirInstCheckSwitchProngs *)instruction, false);
        case Stage1ZirInstIdCheckSwitchProngsUnderYes:
            return ir_analyze_instruction_check_switch_prongs(ira, (Stage1ZirInstCheckSwitchProngs *)instruction, true);
        case Stage1ZirInstIdCheckStatementIsVoid:
            return ir_analyze_instruction_check_statement_is_void(ira, (Stage1ZirInstCheckStatementIsVoid *)instruction);
        case Stage1ZirInstIdDeclRef:
            return ir_analyze_instruction_decl_ref(ira, (Stage1ZirInstDeclRef *)instruction);
        case Stage1ZirInstIdPanic:
            return ir_analyze_instruction_panic(ira, (Stage1ZirInstPanic *)instruction);
        case Stage1ZirInstIdPtrCast:
            return ir_analyze_instruction_ptr_cast(ira, (Stage1ZirInstPtrCast *)instruction);
        case Stage1ZirInstIdIntToPtr:
            return ir_analyze_instruction_int_to_ptr(ira, (Stage1ZirInstIntToPtr *)instruction);
        case Stage1ZirInstIdPtrToInt:
            return ir_analyze_instruction_ptr_to_int(ira, (Stage1ZirInstPtrToInt *)instruction);
        case Stage1ZirInstIdTagName:
            return ir_analyze_instruction_enum_tag_name(ira, (Stage1ZirInstTagName *)instruction);
        case Stage1ZirInstIdFieldParentPtr:
            return ir_analyze_instruction_field_parent_ptr(ira, (Stage1ZirInstFieldParentPtr *)instruction);
        case Stage1ZirInstIdOffsetOf:
            return ir_analyze_instruction_offset_of(ira, (Stage1ZirInstOffsetOf *)instruction);
        case Stage1ZirInstIdBitOffsetOf:
            return ir_analyze_instruction_bit_offset_of(ira, (Stage1ZirInstBitOffsetOf *)instruction);
        case Stage1ZirInstIdTypeInfo:
            return ir_analyze_instruction_type_info(ira, (Stage1ZirInstTypeInfo *) instruction);
        case Stage1ZirInstIdType:
            return ir_analyze_instruction_type(ira, (Stage1ZirInstType *)instruction);
        case Stage1ZirInstIdHasField:
            return ir_analyze_instruction_has_field(ira, (Stage1ZirInstHasField *) instruction);
        case Stage1ZirInstIdSetEvalBranchQuota:
            return ir_analyze_instruction_set_eval_branch_quota(ira, (Stage1ZirInstSetEvalBranchQuota *)instruction);
        case Stage1ZirInstIdPtrType:
            return ir_analyze_instruction_ptr_type(ira, (Stage1ZirInstPtrType *)instruction);
        case Stage1ZirInstIdPtrTypeSimple:
            return ir_analyze_instruction_ptr_type_simple(ira, (Stage1ZirInstPtrTypeSimple *)instruction, false);
        case Stage1ZirInstIdPtrTypeSimpleConst:
            return ir_analyze_instruction_ptr_type_simple(ira, (Stage1ZirInstPtrTypeSimple *)instruction, true);
        case Stage1ZirInstIdAlignCast:
            return ir_analyze_instruction_align_cast(ira, (Stage1ZirInstAlignCast *)instruction);
        case Stage1ZirInstIdImplicitCast:
            return ir_analyze_instruction_implicit_cast(ira, (Stage1ZirInstImplicitCast *)instruction);
        case Stage1ZirInstIdResolveResult:
            return ir_analyze_instruction_resolve_result(ira, (Stage1ZirInstResolveResult *)instruction);
        case Stage1ZirInstIdResetResult:
            return ir_analyze_instruction_reset_result(ira, (Stage1ZirInstResetResult *)instruction);
        case Stage1ZirInstIdSetAlignStack:
            return ir_analyze_instruction_set_align_stack(ira, (Stage1ZirInstSetAlignStack *)instruction);
        case Stage1ZirInstIdArgTypeAllowVarFalse:
            return ir_analyze_instruction_arg_type(ira, (Stage1ZirInstArgType *)instruction, false);
        case Stage1ZirInstIdArgTypeAllowVarTrue:
            return ir_analyze_instruction_arg_type(ira, (Stage1ZirInstArgType *)instruction, true);
        case Stage1ZirInstIdExport:
            return ir_analyze_instruction_export(ira, (Stage1ZirInstExport *)instruction);
        case Stage1ZirInstIdExtern:
            return ir_analyze_instruction_extern(ira, (Stage1ZirInstExtern *)instruction);
        case Stage1ZirInstIdErrorReturnTrace:
            return ir_analyze_instruction_error_return_trace(ira, (Stage1ZirInstErrorReturnTrace *)instruction);
        case Stage1ZirInstIdErrorUnion:
            return ir_analyze_instruction_error_union(ira, (Stage1ZirInstErrorUnion *)instruction);
        case Stage1ZirInstIdAtomicRmw:
            return ir_analyze_instruction_atomic_rmw(ira, (Stage1ZirInstAtomicRmw *)instruction);
        case Stage1ZirInstIdAtomicLoad:
            return ir_analyze_instruction_atomic_load(ira, (Stage1ZirInstAtomicLoad *)instruction);
        case Stage1ZirInstIdAtomicStore:
            return ir_analyze_instruction_atomic_store(ira, (Stage1ZirInstAtomicStore *)instruction);
        case Stage1ZirInstIdSaveErrRetAddr:
            return ir_analyze_instruction_save_err_ret_addr(ira, (Stage1ZirInstSaveErrRetAddr *)instruction);
        case Stage1ZirInstIdAddImplicitReturnType:
            return ir_analyze_instruction_add_implicit_return_type(ira, (Stage1ZirInstAddImplicitReturnType *)instruction);
        case Stage1ZirInstIdFloatOp:
            return ir_analyze_instruction_float_op(ira, (Stage1ZirInstFloatOp *)instruction);
        case Stage1ZirInstIdMulAdd:
            return ir_analyze_instruction_mul_add(ira, (Stage1ZirInstMulAdd *)instruction);
        case Stage1ZirInstIdIntToErr:
            return ir_analyze_instruction_int_to_err(ira, (Stage1ZirInstIntToErr *)instruction);
        case Stage1ZirInstIdErrToInt:
            return ir_analyze_instruction_err_to_int(ira, (Stage1ZirInstErrToInt *)instruction);
        case Stage1ZirInstIdIntToEnum:
            return ir_analyze_instruction_int_to_enum(ira, (Stage1ZirInstIntToEnum *)instruction);
        case Stage1ZirInstIdEnumToInt:
            return ir_analyze_instruction_enum_to_int(ira, (Stage1ZirInstEnumToInt *)instruction);
        case Stage1ZirInstIdCheckRuntimeScope:
            return ir_analyze_instruction_check_runtime_scope(ira, (Stage1ZirInstCheckRuntimeScope *)instruction);
        case Stage1ZirInstIdHasDecl:
            return ir_analyze_instruction_has_decl(ira, (Stage1ZirInstHasDecl *)instruction);
        case Stage1ZirInstIdUndeclaredIdent:
            return ir_analyze_instruction_undeclared_ident(ira, (Stage1ZirInstUndeclaredIdent *)instruction);
        case Stage1ZirInstIdAlloca:
            return nullptr;
        case Stage1ZirInstIdEndExpr:
            return ir_analyze_instruction_end_expr(ira, (Stage1ZirInstEndExpr *)instruction);
        case Stage1ZirInstIdBitCast:
            return ir_analyze_instruction_bit_cast_src(ira, (Stage1ZirInstBitCast *)instruction);
        case Stage1ZirInstIdUnionInitNamedField:
            return ir_analyze_instruction_union_init_named_field(ira, (Stage1ZirInstUnionInitNamedField *)instruction);
        case Stage1ZirInstIdSuspendBegin:
            return ir_analyze_instruction_suspend_begin(ira, (Stage1ZirInstSuspendBegin *)instruction);
        case Stage1ZirInstIdSuspendFinish:
            return ir_analyze_instruction_suspend_finish(ira, (Stage1ZirInstSuspendFinish *)instruction);
        case Stage1ZirInstIdResume:
            return ir_analyze_instruction_resume(ira, (Stage1ZirInstResume *)instruction);
        case Stage1ZirInstIdAwait:
            return ir_analyze_instruction_await(ira, (Stage1ZirInstAwait *)instruction);
        case Stage1ZirInstIdSpillBegin:
            return ir_analyze_instruction_spill_begin(ira, (Stage1ZirInstSpillBegin *)instruction);
        case Stage1ZirInstIdSpillEnd:
            return ir_analyze_instruction_spill_end(ira, (Stage1ZirInstSpillEnd *)instruction);
        case Stage1ZirInstIdWasmMemorySize:
            return ir_analyze_instruction_wasm_memory_size(ira, (Stage1ZirInstWasmMemorySize *)instruction);
        case Stage1ZirInstIdWasmMemoryGrow:
            return ir_analyze_instruction_wasm_memory_grow(ira, (Stage1ZirInstWasmMemoryGrow *)instruction);
        case Stage1ZirInstIdSrc:
            return ir_analyze_instruction_src(ira, (Stage1ZirInstSrc *)instruction);
        case Stage1ZirInstIdPrefetch:
            return ir_analyze_instruction_prefetch(ira, (Stage1ZirInstPrefetch *)instruction);
    }
    zig_unreachable();
}

// This function attempts to evaluate stage1 ZIR code while doing type checking and other analysis.
// It emits to a new Stage1Air which is partially evaluated IR code.
ZigType *ir_analyze(CodeGen *codegen, Stage1Zir *stage1_zir, Stage1Air *stage1_air,
        size_t *backward_branch_count, size_t *backward_branch_quota,
        ZigType *expected_type, AstNode *expected_type_source_node, ZigValue *result_ptr, ZigFn *fn)
{
    assert(stage1_zir->first_err_trace_msg == nullptr);
    assert(expected_type == nullptr || !type_is_invalid(expected_type));

    IrAnalyze *ira = heap::c_allocator.create<IrAnalyze>();
    ira->fn = fn;
    ira->backward_branch_count = backward_branch_count;
    ira->backward_branch_quota = backward_branch_quota;
    ira->ref_count = 1;
    ira->codegen = codegen;

    ira->explicit_return_type = expected_type;
    ira->explicit_return_type_source_node = expected_type_source_node;

    ira->zir = stage1_zir;

    ira->new_irb.codegen = codegen;
    ira->new_irb.exec = stage1_air;

    Stage1ZirBasicBlock *old_entry_bb = ira->zir->basic_block_list.at(0);
    Stage1AirBasicBlock *new_entry_bb = ir_get_new_bb(ira, old_entry_bb, nullptr);
    ira->new_irb.current_basic_block = new_entry_bb;
    ira->old_bb_index = 0;

    ir_start_bb(ira, old_entry_bb, nullptr);

    if (result_ptr != nullptr) {
        assert(result_ptr->type->id == ZigTypeIdPointer);
        Stage1AirInstConst *const_inst = ir_create_inst_noval<Stage1AirInstConst>(
                &ira->new_irb, stage1_air->begin_scope, stage1_air->source_node);
        const_inst->base.value = result_ptr;
        ira->return_ptr = &const_inst->base;
    } else {
        assert(stage1_air->begin_scope != nullptr);
        assert(stage1_air->source_node != nullptr);
        ira->return_ptr = ir_build_return_ptr(ira, stage1_air->begin_scope, stage1_air->source_node,
                get_pointer_to_type(codegen, expected_type, false));
    }

    while (ira->old_bb_index < ira->zir->basic_block_list.length) {
        Stage1ZirInst *old_instruction = ira->zir_current_basic_block->instruction_list.at(ira->instruction_index);

        if (old_instruction->ref_count == 0 && !ir_inst_src_has_side_effects(old_instruction)) {
            ira->instruction_index += 1;
            continue;
        }

        if (ira->codegen->verbose_ir) {
            fprintf(stderr, "~ ");
            old_instruction->src();
            fprintf(stderr, "~ ");
            ir_print_inst_src(codegen, stderr, old_instruction, 0);
        }
        ira->suspend_source_instr = old_instruction;
        Stage1AirInst *new_instruction = ir_analyze_instruction_base(ira, old_instruction);
        if (new_instruction != nullptr) {
            src_assert(new_instruction->value->type != nullptr || new_instruction->value->type != nullptr, old_instruction->source_node);
            old_instruction->child = new_instruction;

            if (type_is_invalid(new_instruction->value->type)) {
                if (ira->codegen->verbose_ir) {
                    fprintf(stderr, "-> (invalid)");
                }

                if (stage1_air->first_err_trace_msg != nullptr) {
                    ira->codegen->trace_err = stage1_air->first_err_trace_msg;
                } else {
                    stage1_air->first_err_trace_msg = ira->codegen->trace_err;
                }
                return ira->codegen->builtin_types.entry_invalid;
            } else if (ira->codegen->verbose_ir) {
                fprintf(stderr, "-> ");
                if (new_instruction->value->type->id == ZigTypeIdUnreachable) {
                    fprintf(stderr, "(noreturn)\n");
                } else {
                    ir_print_inst_gen(codegen, stderr, new_instruction, 0);
                }
            }

            // unreachable instructions do their own control flow.
            if (new_instruction->value->type->id == ZigTypeIdUnreachable)
                continue;
        } else {
            if (ira->codegen->verbose_ir) {
                fprintf(stderr, "-> (null");
            }
        }

        ira->instruction_index += 1;
    }

    ZigType *res_type;
    if (stage1_air->first_err_trace_msg != nullptr) {
        codegen->trace_err = stage1_air->first_err_trace_msg;
        res_type = ira->codegen->builtin_types.entry_invalid;
    } else if (ira->src_implicit_return_type_list.length == 0) {
        res_type = codegen->builtin_types.entry_unreachable;
    } else {
        res_type = 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);
    }

    // It is now safe to free Pass 1 IR instructions.
    ira_deref(ira);

    return res_type;
}

bool ir_inst_gen_has_side_effects(Stage1AirInst *instruction) {
    switch (instruction->id) {
        case Stage1AirInstIdInvalid:
            zig_unreachable();
        case Stage1AirInstIdBr:
        case Stage1AirInstIdCondBr:
        case Stage1AirInstIdSwitchBr:
        case Stage1AirInstIdDeclVar:
        case Stage1AirInstIdStorePtr:
        case Stage1AirInstIdVectorStoreElem:
        case Stage1AirInstIdCall:
        case Stage1AirInstIdReturn:
        case Stage1AirInstIdUnreachable:
        case Stage1AirInstIdFence:
        case Stage1AirInstIdMemset:
        case Stage1AirInstIdMemcpy:
        case Stage1AirInstIdBreakpoint:
        case Stage1AirInstIdOverflowOp: // TODO when we support multiple returns this can be side effect free
        case Stage1AirInstIdPanic:
        case Stage1AirInstIdSaveErrRetAddr:
        case Stage1AirInstIdAtomicRmw:
        case Stage1AirInstIdAtomicStore:
        case Stage1AirInstIdCmpxchg:
        case Stage1AirInstIdAssertZero:
        case Stage1AirInstIdAssertNonNull:
        case Stage1AirInstIdPtrOfArrayToSlice:
        case Stage1AirInstIdSlice:
        case Stage1AirInstIdOptionalWrap:
        case Stage1AirInstIdVectorToArray:
        case Stage1AirInstIdSuspendBegin:
        case Stage1AirInstIdSuspendFinish:
        case Stage1AirInstIdResume:
        case Stage1AirInstIdAwait:
        case Stage1AirInstIdSpillBegin:
        case Stage1AirInstIdWasmMemoryGrow:
        case Stage1AirInstIdExtern:
        case Stage1AirInstIdPrefetch:
            return true;

        case Stage1AirInstIdPhi:
        case Stage1AirInstIdBinOp:
        case Stage1AirInstIdConst:
        case Stage1AirInstIdCast:
        case Stage1AirInstIdElemPtr:
        case Stage1AirInstIdVarPtr:
        case Stage1AirInstIdReturnPtr:
        case Stage1AirInstIdStructFieldPtr:
        case Stage1AirInstIdTestNonNull:
        case Stage1AirInstIdClz:
        case Stage1AirInstIdCtz:
        case Stage1AirInstIdPopCount:
        case Stage1AirInstIdBswap:
        case Stage1AirInstIdBitReverse:
        case Stage1AirInstIdUnionTag:
        case Stage1AirInstIdTruncate:
        case Stage1AirInstIdShuffleVector:
        case Stage1AirInstIdSelect:
        case Stage1AirInstIdSplat:
        case Stage1AirInstIdBoolNot:
        case Stage1AirInstIdReturnAddress:
        case Stage1AirInstIdFrameAddress:
        case Stage1AirInstIdFrameHandle:
        case Stage1AirInstIdFrameSize:
        case Stage1AirInstIdTestErr:
        case Stage1AirInstIdPtrCast:
        case Stage1AirInstIdBitCast:
        case Stage1AirInstIdWidenOrShorten:
        case Stage1AirInstIdPtrToInt:
        case Stage1AirInstIdIntToPtr:
        case Stage1AirInstIdIntToEnum:
        case Stage1AirInstIdIntToErr:
        case Stage1AirInstIdErrToInt:
        case Stage1AirInstIdErrName:
        case Stage1AirInstIdTagName:
        case Stage1AirInstIdFieldParentPtr:
        case Stage1AirInstIdAlignCast:
        case Stage1AirInstIdErrorReturnTrace:
        case Stage1AirInstIdFloatOp:
        case Stage1AirInstIdMulAdd:
        case Stage1AirInstIdAtomicLoad:
        case Stage1AirInstIdArrayToVector:
        case Stage1AirInstIdAlloca:
        case Stage1AirInstIdSpillEnd:
        case Stage1AirInstIdVectorExtractElem:
        case Stage1AirInstIdBinaryNot:
        case Stage1AirInstIdNegation:
        case Stage1AirInstIdWasmMemorySize:
        case Stage1AirInstIdReduce:
            return false;

        case Stage1AirInstIdAsm:
            {
                Stage1AirInstAsm *asm_instruction = (Stage1AirInstAsm *)instruction;
                return asm_instruction->has_side_effects;
            }
        case Stage1AirInstIdUnwrapErrPayload:
            {
                Stage1AirInstUnwrapErrPayload *unwrap_err_payload_instruction =
                    (Stage1AirInstUnwrapErrPayload *)instruction;
                return unwrap_err_payload_instruction->safety_check_on ||
                    unwrap_err_payload_instruction->initializing;
            }
        case Stage1AirInstIdUnwrapErrCode:
            return reinterpret_cast<Stage1AirInstUnwrapErrCode *>(instruction)->initializing;
        case Stage1AirInstIdUnionFieldPtr:
            return reinterpret_cast<Stage1AirInstUnionFieldPtr *>(instruction)->initializing;
        case Stage1AirInstIdOptionalUnwrapPtr:
            return reinterpret_cast<Stage1AirInstOptionalUnwrapPtr *>(instruction)->initializing;
        case Stage1AirInstIdErrWrapPayload:
            return reinterpret_cast<Stage1AirInstErrWrapPayload *>(instruction)->result_loc != nullptr;
        case Stage1AirInstIdErrWrapCode:
            return reinterpret_cast<Stage1AirInstErrWrapCode *>(instruction)->result_loc != nullptr;
        case Stage1AirInstIdLoadPtr:
            return reinterpret_cast<Stage1AirInstLoadPtr *>(instruction)->result_loc != nullptr;
        case Stage1AirInstIdRef:
            return reinterpret_cast<Stage1AirInstRef *>(instruction)->result_loc != nullptr;
    }
    zig_unreachable();
}

bool ir_inst_src_has_side_effects(Stage1ZirInst *instruction) {
    switch (instruction->id) {
        case Stage1ZirInstIdInvalid:
            zig_unreachable();
        case Stage1ZirInstIdBr:
        case Stage1ZirInstIdCondBr:
        case Stage1ZirInstIdSwitchBr:
        case Stage1ZirInstIdDeclVar:
        case Stage1ZirInstIdStorePtr:
        case Stage1ZirInstIdCallExtra:
        case Stage1ZirInstIdAsyncCallExtra:
        case Stage1ZirInstIdCall:
        case Stage1ZirInstIdCallArgs:
        case Stage1ZirInstIdReturn:
        case Stage1ZirInstIdUnreachable:
        case Stage1ZirInstIdSetCold:
        case Stage1ZirInstIdSetRuntimeSafety:
        case Stage1ZirInstIdSetFloatMode:
        case Stage1ZirInstIdImport:
        case Stage1ZirInstIdCompileErr:
        case Stage1ZirInstIdCompileLog:
        case Stage1ZirInstIdCImport:
        case Stage1ZirInstIdCInclude:
        case Stage1ZirInstIdCDefine:
        case Stage1ZirInstIdCUndef:
        case Stage1ZirInstIdFence:
        case Stage1ZirInstIdMemset:
        case Stage1ZirInstIdMemcpy:
        case Stage1ZirInstIdBreakpoint:
        case Stage1ZirInstIdOverflowOp: // TODO when we support multiple returns this can be side effect free
        case Stage1ZirInstIdCheckSwitchProngsUnderNo:
        case Stage1ZirInstIdCheckSwitchProngsUnderYes:
        case Stage1ZirInstIdCheckStatementIsVoid:
        case Stage1ZirInstIdCheckRuntimeScope:
        case Stage1ZirInstIdPanic:
        case Stage1ZirInstIdSetEvalBranchQuota:
        case Stage1ZirInstIdPtrType:
        case Stage1ZirInstIdPtrTypeSimple:
        case Stage1ZirInstIdPtrTypeSimpleConst:
        case Stage1ZirInstIdSetAlignStack:
        case Stage1ZirInstIdExport:
        case Stage1ZirInstIdExtern:
        case Stage1ZirInstIdSaveErrRetAddr:
        case Stage1ZirInstIdAddImplicitReturnType:
        case Stage1ZirInstIdAtomicRmw:
        case Stage1ZirInstIdAtomicStore:
        case Stage1ZirInstIdCmpxchg:
        case Stage1ZirInstIdUndeclaredIdent:
        case Stage1ZirInstIdEndExpr:
        case Stage1ZirInstIdResetResult:
        case Stage1ZirInstIdSuspendBegin:
        case Stage1ZirInstIdSuspendFinish:
        case Stage1ZirInstIdResume:
        case Stage1ZirInstIdAwait:
        case Stage1ZirInstIdSpillBegin:
        case Stage1ZirInstIdWasmMemoryGrow:
        case Stage1ZirInstIdPrefetch:
            return true;

        case Stage1ZirInstIdPhi:
        case Stage1ZirInstIdUnOp:
        case Stage1ZirInstIdBinOp:
        case Stage1ZirInstIdMergeErrSets:
        case Stage1ZirInstIdLoadPtr:
        case Stage1ZirInstIdConst:
        case Stage1ZirInstIdContainerInitList:
        case Stage1ZirInstIdContainerInitFields:
        case Stage1ZirInstIdUnionInitNamedField:
        case Stage1ZirInstIdFieldPtr:
        case Stage1ZirInstIdElemPtr:
        case Stage1ZirInstIdVarPtr:
        case Stage1ZirInstIdTypeOf:
        case Stage1ZirInstIdArrayType:
        case Stage1ZirInstIdSliceType:
        case Stage1ZirInstIdAnyFrameType:
        case Stage1ZirInstIdSizeOf:
        case Stage1ZirInstIdTestNonNull:
        case Stage1ZirInstIdOptionalUnwrapPtr:
        case Stage1ZirInstIdClz:
        case Stage1ZirInstIdCtz:
        case Stage1ZirInstIdPopCount:
        case Stage1ZirInstIdBswap:
        case Stage1ZirInstIdBitReverse:
        case Stage1ZirInstIdSwitchVar:
        case Stage1ZirInstIdSwitchElseVar:
        case Stage1ZirInstIdSwitchTarget:
        case Stage1ZirInstIdRef:
        case Stage1ZirInstIdEmbedFile:
        case Stage1ZirInstIdTruncate:
        case Stage1ZirInstIdVectorType:
        case Stage1ZirInstIdShuffleVector:
        case Stage1ZirInstIdSelect:
        case Stage1ZirInstIdSplat:
        case Stage1ZirInstIdBoolNot:
        case Stage1ZirInstIdSlice:
        case Stage1ZirInstIdAlignOf:
        case Stage1ZirInstIdReturnAddress:
        case Stage1ZirInstIdFrameAddress:
        case Stage1ZirInstIdFrameHandle:
        case Stage1ZirInstIdFrameType:
        case Stage1ZirInstIdFrameSize:
        case Stage1ZirInstIdTestErr:
        case Stage1ZirInstIdFnProto:
        case Stage1ZirInstIdTestComptime:
        case Stage1ZirInstIdPtrCast:
        case Stage1ZirInstIdBitCast:
        case Stage1ZirInstIdPtrToInt:
        case Stage1ZirInstIdIntToPtr:
        case Stage1ZirInstIdIntToEnum:
        case Stage1ZirInstIdIntToErr:
        case Stage1ZirInstIdErrToInt:
        case Stage1ZirInstIdDeclRef:
        case Stage1ZirInstIdErrName:
        case Stage1ZirInstIdTypeName:
        case Stage1ZirInstIdTagName:
        case Stage1ZirInstIdFieldParentPtr:
        case Stage1ZirInstIdOffsetOf:
        case Stage1ZirInstIdBitOffsetOf:
        case Stage1ZirInstIdTypeInfo:
        case Stage1ZirInstIdType:
        case Stage1ZirInstIdHasField:
        case Stage1ZirInstIdAlignCast:
        case Stage1ZirInstIdImplicitCast:
        case Stage1ZirInstIdResolveResult:
        case Stage1ZirInstIdArgTypeAllowVarFalse:
        case Stage1ZirInstIdArgTypeAllowVarTrue:
        case Stage1ZirInstIdErrorReturnTrace:
        case Stage1ZirInstIdErrorUnion:
        case Stage1ZirInstIdFloatOp:
        case Stage1ZirInstIdMulAdd:
        case Stage1ZirInstIdAtomicLoad:
        case Stage1ZirInstIdIntCast:
        case Stage1ZirInstIdFloatCast:
        case Stage1ZirInstIdErrSetCast:
        case Stage1ZirInstIdIntToFloat:
        case Stage1ZirInstIdFloatToInt:
        case Stage1ZirInstIdBoolToInt:
        case Stage1ZirInstIdEnumToInt:
        case Stage1ZirInstIdHasDecl:
        case Stage1ZirInstIdAlloca:
        case Stage1ZirInstIdSpillEnd:
        case Stage1ZirInstIdWasmMemorySize:
        case Stage1ZirInstIdSrc:
        case Stage1ZirInstIdReduce:
            return false;

        case Stage1ZirInstIdAsm:
            {
                Stage1ZirInstAsm *asm_instruction = (Stage1ZirInstAsm *)instruction;
                return asm_instruction->has_side_effects;
            }

        case Stage1ZirInstIdUnwrapErrPayload:
            {
                Stage1ZirInstUnwrapErrPayload *unwrap_err_payload_instruction =
                    (Stage1ZirInstUnwrapErrPayload *)instruction;
                return unwrap_err_payload_instruction->safety_check_on ||
                    unwrap_err_payload_instruction->initializing;
            }
        case Stage1ZirInstIdUnwrapErrCode:
            return reinterpret_cast<Stage1ZirInstUnwrapErrCode *>(instruction)->initializing;
    }
    zig_unreachable();
}

static ZigType *ir_resolve_lazy_fn_type(IrAnalyze *ira, AstNode *source_node, LazyValueFnType *lazy_fn_type) {
    Error err;
    AstNode *proto_node = lazy_fn_type->proto_node;

    FnTypeId fn_type_id = {0};
    init_fn_type_id(&fn_type_id, proto_node, lazy_fn_type->cc, proto_node->data.fn_proto.params.length);

    if (proto_node->data.fn_proto.callconv_expr != nullptr) {
        if ((err = emit_error_unless_callconv_allowed_for_target(ira->codegen, proto_node->data.fn_proto.callconv_expr, lazy_fn_type->cc)))
            return nullptr;
    }

    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;
                break;
            } else {
                ir_add_error_node(ira, param_node,
                        buf_sprintf("var args only allowed in functions with C calling convention"));
                return nullptr;
            }
        }
        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 (lazy_fn_type->param_types[fn_type_id.next_param_index] == nullptr) {
            param_info->type = nullptr;
            return get_generic_fn_type(ira->codegen, &fn_type_id);
        } else {
            Stage1AirInst *param_type_inst = lazy_fn_type->param_types[fn_type_id.next_param_index];
            ZigType *param_type = ir_resolve_type(ira, param_type_inst);
            if (type_is_invalid(param_type))
                return nullptr;

            if(!is_valid_param_type(param_type)){
                if(param_type->id == ZigTypeIdOpaque){
                    ir_add_error(ira, param_type_inst,
                        buf_sprintf("parameter of opaque type '%s' not allowed", buf_ptr(&param_type->name)));
                } else {
                    ir_add_error(ira, param_type_inst,
                        buf_sprintf("parameter of type '%s' not allowed", buf_ptr(&param_type->name)));
                }

                return nullptr;
            }

            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_inst,
                        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 nullptr;
                }
                param_info->type = param_type;
                fn_type_id.next_param_index += 1;
                return get_generic_fn_type(ira->codegen, &fn_type_id);
            case ReqCompTimeInvalid:
                return nullptr;
            case ReqCompTimeNo:
                break;
            }
            if (!calling_convention_allows_zig_types(fn_type_id.cc)) {
                bool has_bits;
                if ((err = type_has_bits2(ira->codegen, param_type, &has_bits)))
                    return nullptr;
                if (!has_bits) {
                    ir_add_error(ira, param_type_inst,
                        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 nullptr;
                }
            }
            param_info->type = param_type;
        }
    }

    if (lazy_fn_type->align_inst != nullptr) {
        if (!ir_resolve_align(ira, lazy_fn_type->align_inst, nullptr, &fn_type_id.alignment))
            return nullptr;
    }

    fn_type_id.return_type = ir_resolve_type(ira, lazy_fn_type->return_type);
    if (type_is_invalid(fn_type_id.return_type))
        return nullptr;
    if (fn_type_id.return_type->id == ZigTypeIdOpaque) {
        ir_add_error_node(ira, lazy_fn_type->return_type->source_node,
                buf_create_from_str("return type cannot be opaque"));
        return nullptr;
    }

    return get_fn_type(ira->codegen, &fn_type_id);
}

static Error ir_resolve_lazy_raw(AstNode *source_node, ZigValue *val) {
    Error err;
    if (val->special != ConstValSpecialLazy)
        return ErrorNone;
    switch (val->data.x_lazy->id) {
        case LazyValueIdInvalid:
            zig_unreachable();
        case LazyValueIdTypeInfoDecls: {
            LazyValueTypeInfoDecls *type_info_decls = reinterpret_cast<LazyValueTypeInfoDecls *>(val->data.x_lazy);
            IrAnalyze *ira = type_info_decls->ira;

            if ((err = ir_make_type_info_decls(ira, type_info_decls->source_node, val, type_info_decls->decls_scope, true)))
            {
                return err;
            };

            // We can't free the lazy value here, because multiple other ZigValues might be pointing to it.
            return ErrorNone;
        }
        case LazyValueIdAlignOf: {
            LazyValueAlignOf *lazy_align_of = reinterpret_cast<LazyValueAlignOf *>(val->data.x_lazy);
            IrAnalyze *ira = lazy_align_of->ira;

            if (lazy_align_of->target_type->value->special == ConstValSpecialStatic) {
                switch (lazy_align_of->target_type->value->data.x_type->id) {
                    case ZigTypeIdInvalid:
                        zig_unreachable();
                    case ZigTypeIdMetaType:
                    case ZigTypeIdUnreachable:
                    case ZigTypeIdComptimeFloat:
                    case ZigTypeIdComptimeInt:
                    case ZigTypeIdEnumLiteral:
                    case ZigTypeIdUndefined:
                    case ZigTypeIdNull:
                    case ZigTypeIdBoundFn:
                    case ZigTypeIdVoid:
                    case ZigTypeIdOpaque:
                        ir_add_error_node(ira, lazy_align_of->target_type->source_node,
                            buf_sprintf("no align available for type '%s'",
                                buf_ptr(&lazy_align_of->target_type->value->data.x_type->name)));
                        return ErrorSemanticAnalyzeFail;
                    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 ZigTypeIdVector:
                    case ZigTypeIdFnFrame:
                    case ZigTypeIdAnyFrame:
                        break;
                }
            }

            uint32_t align_in_bytes;
            if ((err = type_val_resolve_abi_align(ira->codegen, source_node,
                    lazy_align_of->target_type->value, &align_in_bytes)))
            {
                return err;
            }

            val->special = ConstValSpecialStatic;
            assert(val->type->id == ZigTypeIdComptimeInt || val->type->id == ZigTypeIdInt);
            bigint_init_unsigned(&val->data.x_bigint, align_in_bytes);

            // We can't free the lazy value here, because multiple other ZigValues might be pointing to it.
            return ErrorNone;
        }
        case LazyValueIdSizeOf: {
            LazyValueSizeOf *lazy_size_of = reinterpret_cast<LazyValueSizeOf *>(val->data.x_lazy);
            IrAnalyze *ira = lazy_size_of->ira;

            if (lazy_size_of->target_type->value->special == ConstValSpecialStatic) {
                switch (lazy_size_of->target_type->value->data.x_type->id) {
                    case ZigTypeIdInvalid: // handled above
                        zig_unreachable();
                    case ZigTypeIdUnreachable:
                    case ZigTypeIdUndefined:
                    case ZigTypeIdNull:
                    case ZigTypeIdBoundFn:
                    case ZigTypeIdOpaque:
                        ir_add_error_node(ira, lazy_size_of->target_type->source_node,
                            buf_sprintf("no size available for type '%s'",
                                buf_ptr(&lazy_size_of->target_type->value->data.x_type->name)));
                        return ErrorSemanticAnalyzeFail;
                    case ZigTypeIdMetaType:
                    case ZigTypeIdEnumLiteral:
                    case ZigTypeIdComptimeFloat:
                    case ZigTypeIdComptimeInt:
                    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 ZigTypeIdVector:
                    case ZigTypeIdFnFrame:
                    case ZigTypeIdAnyFrame:
                        break;
                }
            }

            size_t abi_size;
            size_t size_in_bits;
            if ((err = type_val_resolve_abi_size(ira->codegen, source_node, lazy_size_of->target_type->value,
                            &abi_size, &size_in_bits)))
            {
                return err;
            }

            val->special = ConstValSpecialStatic;
            assert(val->type->id == ZigTypeIdComptimeInt || val->type->id == ZigTypeIdInt);
            if (lazy_size_of->bit_size)
                bigint_init_unsigned(&val->data.x_bigint, size_in_bits);
            else
                bigint_init_unsigned(&val->data.x_bigint, abi_size);

            // We can't free the lazy value here, because multiple other ZigValues might be pointing to it.
            return ErrorNone;
        }
        case LazyValueIdSliceType: {
            LazyValueSliceType *lazy_slice_type = reinterpret_cast<LazyValueSliceType *>(val->data.x_lazy);
            IrAnalyze *ira = lazy_slice_type->ira;

            ZigType *elem_type = ir_resolve_type(ira, lazy_slice_type->elem_type);
            if (type_is_invalid(elem_type))
                return ErrorSemanticAnalyzeFail;

            ZigValue *sentinel_val;
            if (lazy_slice_type->sentinel != nullptr) {
                if (type_is_invalid(lazy_slice_type->sentinel->value->type))
                    return ErrorSemanticAnalyzeFail;
                Stage1AirInst *sentinel = ir_implicit_cast(ira, lazy_slice_type->sentinel, elem_type);
                if (type_is_invalid(sentinel->value->type))
                    return ErrorSemanticAnalyzeFail;
                sentinel_val = ir_resolve_const(ira, sentinel, UndefBad);
                if (sentinel_val == nullptr)
                    return ErrorSemanticAnalyzeFail;
            } else {
                sentinel_val = nullptr;
            }

            uint32_t align_bytes = 0;
            if (lazy_slice_type->align_inst != nullptr) {
                if (!ir_resolve_align(ira, lazy_slice_type->align_inst, elem_type, &align_bytes))
                    return ErrorSemanticAnalyzeFail;
            }

            switch (elem_type->id) {
                case ZigTypeIdInvalid: // handled above
                    zig_unreachable();
                case ZigTypeIdUnreachable:
                case ZigTypeIdUndefined:
                case ZigTypeIdNull:
                case ZigTypeIdOpaque:
                    ir_add_error_node(ira, lazy_slice_type->elem_type->source_node,
                        buf_sprintf("slice of type '%s' not allowed", buf_ptr(&elem_type->name)));
                    return ErrorSemanticAnalyzeFail;
                case ZigTypeIdMetaType:
                case ZigTypeIdVoid:
                case ZigTypeIdBool:
                case ZigTypeIdInt:
                case ZigTypeIdFloat:
                case ZigTypeIdPointer:
                case ZigTypeIdArray:
                case ZigTypeIdStruct:
                case ZigTypeIdComptimeFloat:
                case ZigTypeIdComptimeInt:
                case ZigTypeIdEnumLiteral:
                case ZigTypeIdOptional:
                case ZigTypeIdErrorUnion:
                case ZigTypeIdErrorSet:
                case ZigTypeIdEnum:
                case ZigTypeIdUnion:
                case ZigTypeIdFn:
                case ZigTypeIdBoundFn:
                case ZigTypeIdVector:
                case ZigTypeIdFnFrame:
                case ZigTypeIdAnyFrame:
                    break;
            }

            ResolveStatus needed_status = (align_bytes == 0) ?
                ResolveStatusZeroBitsKnown : ResolveStatusAlignmentKnown;
            if ((err = type_resolve(ira->codegen, elem_type, needed_status)))
                return err;
            ZigType *slice_ptr_type = get_pointer_to_type_extra2(ira->codegen, elem_type,
                    lazy_slice_type->is_const, lazy_slice_type->is_volatile,
                    PtrLenUnknown,
                    align_bytes,
                    0, 0, lazy_slice_type->is_allowzero,
                    VECTOR_INDEX_NONE, nullptr, sentinel_val);
            val->special = ConstValSpecialStatic;
            assert(val->type->id == ZigTypeIdMetaType);
            val->data.x_type = get_slice_type(ira->codegen, slice_ptr_type);

            // We can't free the lazy value here, because multiple other ZigValues might be pointing to it.
            return ErrorNone;
        }
        case LazyValueIdPtrType: {
            LazyValuePtrType *lazy_ptr_type = reinterpret_cast<LazyValuePtrType *>(val->data.x_lazy);
            IrAnalyze *ira = lazy_ptr_type->ira;

            ZigType *elem_type = ir_resolve_type(ira, lazy_ptr_type->elem_type);
            if (type_is_invalid(elem_type))
                return ErrorSemanticAnalyzeFail;

            ZigValue *sentinel_val;
            if (lazy_ptr_type->sentinel != nullptr) {
                if (type_is_invalid(lazy_ptr_type->sentinel->value->type))
                    return ErrorSemanticAnalyzeFail;
                Stage1AirInst *sentinel = ir_implicit_cast(ira, lazy_ptr_type->sentinel, elem_type);
                if (type_is_invalid(sentinel->value->type))
                    return ErrorSemanticAnalyzeFail;
                sentinel_val = ir_resolve_const(ira, sentinel, UndefBad);
                if (sentinel_val == nullptr)
                    return ErrorSemanticAnalyzeFail;
            } else {
                sentinel_val = nullptr;
            }

            uint32_t align_bytes = 0;
            if (lazy_ptr_type->align_inst != nullptr) {
                if (!ir_resolve_align(ira, lazy_ptr_type->align_inst, elem_type, &align_bytes))
                    return ErrorSemanticAnalyzeFail;
            }

            if (elem_type->id == ZigTypeIdUnreachable) {
                ir_add_error_node(ira, lazy_ptr_type->elem_type->source_node,
                        buf_create_from_str("pointer to noreturn not allowed"));
                return ErrorSemanticAnalyzeFail;
            } else if (elem_type->id == ZigTypeIdOpaque && lazy_ptr_type->ptr_len == PtrLenUnknown) {
                ir_add_error_node(ira, lazy_ptr_type->elem_type->source_node,
                        buf_create_from_str("unknown-length pointer to opaque"));
                return ErrorSemanticAnalyzeFail;
            } else if (lazy_ptr_type->ptr_len == PtrLenC) {
                bool ok_type;
                if ((err = type_allowed_in_extern(ira->codegen, elem_type, ExternPositionOther, &ok_type)))
                    return err;
                if (!ok_type) {
                    ir_add_error_node(ira, lazy_ptr_type->elem_type->source_node,
                        buf_sprintf("C pointers cannot point to non-C-ABI-compatible type '%s'",
                            buf_ptr(&elem_type->name)));
                    return ErrorSemanticAnalyzeFail;
                } else if (elem_type->id == ZigTypeIdOpaque) {
                    ir_add_error_node(ira, lazy_ptr_type->elem_type->source_node,
                            buf_sprintf("C pointers cannot point to opaque types"));
                    return ErrorSemanticAnalyzeFail;
                } else if (lazy_ptr_type->is_allowzero) {
                    ir_add_error_node(ira, lazy_ptr_type->elem_type->source_node,
                            buf_sprintf("C pointers always allow address zero"));
                    return ErrorSemanticAnalyzeFail;
                }
            }

            if (align_bytes != 0) {
                if ((err = type_resolve(ira->codegen, elem_type, ResolveStatusAlignmentKnown)))
                    return err;
                if (!type_has_bits(ira->codegen, elem_type))
                    align_bytes = 0;
            }
            bool allow_zero = lazy_ptr_type->is_allowzero || lazy_ptr_type->ptr_len == PtrLenC;
            assert(val->type->id == ZigTypeIdMetaType);
            val->data.x_type = get_pointer_to_type_extra2(ira->codegen, elem_type,
                    lazy_ptr_type->is_const, lazy_ptr_type->is_volatile, lazy_ptr_type->ptr_len, align_bytes,
                    lazy_ptr_type->bit_offset_in_host, lazy_ptr_type->host_int_bytes,
                    allow_zero, VECTOR_INDEX_NONE, nullptr, sentinel_val);
            val->special = ConstValSpecialStatic;

            // We can't free the lazy value here, because multiple other ZigValues might be pointing to it.
            return ErrorNone;
        }
        case LazyValueIdPtrTypeSimple: {
            LazyValuePtrTypeSimple *lazy_ptr_type = reinterpret_cast<LazyValuePtrTypeSimple *>(val->data.x_lazy);
            IrAnalyze *ira = lazy_ptr_type->ira;

            ZigType *elem_type = ir_resolve_type(ira, lazy_ptr_type->elem_type);
            if (type_is_invalid(elem_type))
                return ErrorSemanticAnalyzeFail;

            if (elem_type->id == ZigTypeIdUnreachable) {
                ir_add_error_node(ira, lazy_ptr_type->elem_type->source_node,
                        buf_create_from_str("pointer to noreturn not allowed"));
                return ErrorSemanticAnalyzeFail;
            }

            assert(val->type->id == ZigTypeIdMetaType);
            val->data.x_type = get_pointer_to_type_extra2(ira->codegen, elem_type,
                    false, false, PtrLenSingle, 0,
                    0, 0,
                    false, VECTOR_INDEX_NONE, nullptr, nullptr);
            val->special = ConstValSpecialStatic;

            // We can't free the lazy value here, because multiple other ZigValues might be pointing to it.
            return ErrorNone;
        }
        case LazyValueIdPtrTypeSimpleConst: {
            LazyValuePtrTypeSimple *lazy_ptr_type = reinterpret_cast<LazyValuePtrTypeSimple *>(val->data.x_lazy);
            IrAnalyze *ira = lazy_ptr_type->ira;

            ZigType *elem_type = ir_resolve_type(ira, lazy_ptr_type->elem_type);
            if (type_is_invalid(elem_type))
                return ErrorSemanticAnalyzeFail;

            if (elem_type->id == ZigTypeIdUnreachable) {
                ir_add_error_node(ira, lazy_ptr_type->elem_type->source_node,
                        buf_create_from_str("pointer to noreturn not allowed"));
                return ErrorSemanticAnalyzeFail;
            }

            assert(val->type->id == ZigTypeIdMetaType);
            val->data.x_type = get_pointer_to_type_extra2(ira->codegen, elem_type,
                    true, false, PtrLenSingle, 0,
                    0, 0,
                    false, VECTOR_INDEX_NONE, nullptr, nullptr);
            val->special = ConstValSpecialStatic;

            // We can't free the lazy value here, because multiple other ZigValues might be pointing to it.
            return ErrorNone;
        }
        case LazyValueIdArrayType: {
            LazyValueArrayType *lazy_array_type = reinterpret_cast<LazyValueArrayType *>(val->data.x_lazy);
            IrAnalyze *ira = lazy_array_type->ira;

            ZigType *elem_type = ir_resolve_type(ira, lazy_array_type->elem_type);
            if (type_is_invalid(elem_type))
                return ErrorSemanticAnalyzeFail;

            switch (elem_type->id) {
                case ZigTypeIdInvalid: // handled above
                    zig_unreachable();
                case ZigTypeIdUnreachable:
                case ZigTypeIdUndefined:
                case ZigTypeIdNull:
                case ZigTypeIdOpaque:
                    ir_add_error_node(ira, lazy_array_type->elem_type->source_node,
                                 buf_sprintf("array of type '%s' not allowed",
                                             buf_ptr(&elem_type->name)));
                    return ErrorSemanticAnalyzeFail;
                case ZigTypeIdMetaType:
                case ZigTypeIdVoid:
                case ZigTypeIdBool:
                case ZigTypeIdInt:
                case ZigTypeIdFloat:
                case ZigTypeIdPointer:
                case ZigTypeIdArray:
                case ZigTypeIdStruct:
                case ZigTypeIdComptimeFloat:
                case ZigTypeIdComptimeInt:
                case ZigTypeIdEnumLiteral:
                case ZigTypeIdOptional:
                case ZigTypeIdErrorUnion:
                case ZigTypeIdErrorSet:
                case ZigTypeIdEnum:
                case ZigTypeIdUnion:
                case ZigTypeIdFn:
                case ZigTypeIdBoundFn:
                case ZigTypeIdVector:
                case ZigTypeIdFnFrame:
                case ZigTypeIdAnyFrame:
                    break;
            }

            // Avoid resolving the type if the total length is zero.
            // Matches the logic in get_array_type and in the lazy alignment
            // resolution routine.
            if (lazy_array_type->length + (lazy_array_type->sentinel != nullptr) != 0) {
                if ((err = type_resolve(ira->codegen, elem_type, ResolveStatusSizeKnown)))
                    return err;
            }

            ZigValue *sentinel_val = nullptr;
            if (lazy_array_type->sentinel != nullptr) {
                if (type_is_invalid(lazy_array_type->sentinel->value->type))
                    return ErrorSemanticAnalyzeFail;
                Stage1AirInst *sentinel = ir_implicit_cast(ira, lazy_array_type->sentinel, elem_type);
                if (type_is_invalid(sentinel->value->type))
                    return ErrorSemanticAnalyzeFail;
                sentinel_val = ir_resolve_const(ira, sentinel, UndefBad);
                if (sentinel_val == nullptr)
                    return ErrorSemanticAnalyzeFail;
            }

            assert(val->type->id == ZigTypeIdMetaType);
            val->data.x_type = get_array_type(ira->codegen, elem_type, lazy_array_type->length, sentinel_val);
            val->special = ConstValSpecialStatic;

            // We can't free the lazy value here, because multiple other ZigValues might be pointing to it.
            return ErrorNone;
        }
        case LazyValueIdOptType: {
            LazyValueOptType *lazy_opt_type = reinterpret_cast<LazyValueOptType *>(val->data.x_lazy);
            IrAnalyze *ira = lazy_opt_type->ira;

            ZigType *payload_type = ir_resolve_type(ira, lazy_opt_type->payload_type);
            if (type_is_invalid(payload_type))
                return ErrorSemanticAnalyzeFail;

            if (payload_type->id == ZigTypeIdOpaque || payload_type->id == ZigTypeIdUnreachable) {
                ir_add_error_node(ira, lazy_opt_type->payload_type->source_node,
                        buf_sprintf("type '%s' cannot be optional", buf_ptr(&payload_type->name)));
                return ErrorSemanticAnalyzeFail;
            }

            if ((err = type_resolve(ira->codegen, payload_type, ResolveStatusSizeKnown)))
                return err;

            assert(val->type->id == ZigTypeIdMetaType);
            val->data.x_type = get_optional_type(ira->codegen, payload_type);
            val->special = ConstValSpecialStatic;

            // We can't free the lazy value here, because multiple other ZigValues might be pointing to it.
            return ErrorNone;
        }
        case LazyValueIdFnType: {
            LazyValueFnType *lazy_fn_type = reinterpret_cast<LazyValueFnType *>(val->data.x_lazy);
            IrAnalyze *ira = lazy_fn_type->ira;
            ZigType *fn_type = ir_resolve_lazy_fn_type(ira, source_node, lazy_fn_type);
            if (fn_type == nullptr)
                return ErrorSemanticAnalyzeFail;
            val->special = ConstValSpecialStatic;
            assert(val->type->id == ZigTypeIdMetaType);
            val->data.x_type = fn_type;

            // We can't free the lazy value here, because multiple other ZigValues might be pointing to it.
            return ErrorNone;
        }
        case LazyValueIdErrUnionType: {
            LazyValueErrUnionType *lazy_err_union_type =
                reinterpret_cast<LazyValueErrUnionType *>(val->data.x_lazy);
            IrAnalyze *ira = lazy_err_union_type->ira;

            ZigType *err_set_type = ir_resolve_type(ira, lazy_err_union_type->err_set_type);
            if (type_is_invalid(err_set_type))
                return ErrorSemanticAnalyzeFail;

            ZigType *payload_type = ir_resolve_type(ira, lazy_err_union_type->payload_type);
            if (type_is_invalid(payload_type))
                return ErrorSemanticAnalyzeFail;

            if (err_set_type->id != ZigTypeIdErrorSet) {
                ir_add_error_node(ira, lazy_err_union_type->err_set_type->source_node,
                    buf_sprintf("expected error set type, found type '%s'",
                        buf_ptr(&err_set_type->name)));
                return ErrorSemanticAnalyzeFail;
            }

            if ((err = type_resolve(ira->codegen, payload_type, ResolveStatusSizeKnown)))
                return ErrorSemanticAnalyzeFail;

            assert(val->type->id == ZigTypeIdMetaType);
            val->data.x_type = get_error_union_type(ira->codegen, err_set_type, payload_type);
            val->special = ConstValSpecialStatic;

            // We can't free the lazy value here, because multiple other ZigValues might be pointing to it.
            return ErrorNone;
        }
    }
    zig_unreachable();
}

static Error ir_resolve_lazy_recurse_array(AstNode *source_node, ZigValue *val, size_t len) {
    Error err;
    switch (val->data.x_array.special) {
        case ConstArraySpecialUndef:
        case ConstArraySpecialBuf:
            return ErrorNone;
        case ConstArraySpecialNone:
            break;
    }
    ZigValue *elems = val->data.x_array.data.s_none.elements;

    for (size_t i = 0; i < len; i += 1) {
        if ((err = ir_resolve_lazy_recurse(source_node, &elems[i])))
            return err;
    }

    return ErrorNone;
}

static Error ir_resolve_lazy_recurse(AstNode *source_node, ZigValue *val) {
    Error err;
    if ((err = ir_resolve_lazy_raw(source_node, val)))
        return err;
    assert(val->special != ConstValSpecialRuntime);
    assert(val->special != ConstValSpecialLazy);
    if (val->special != ConstValSpecialStatic)
        return ErrorNone;
    switch (val->type->id) {
        case ZigTypeIdOpaque:
        case ZigTypeIdEnum:
        case ZigTypeIdMetaType:
        case ZigTypeIdBool:
        case ZigTypeIdVoid:
        case ZigTypeIdComptimeFloat:
        case ZigTypeIdInt:
        case ZigTypeIdComptimeInt:
        case ZigTypeIdEnumLiteral:
        case ZigTypeIdErrorSet:
        case ZigTypeIdUndefined:
        case ZigTypeIdNull:
        case ZigTypeIdPointer:
        case ZigTypeIdFn:
        case ZigTypeIdAnyFrame:
        case ZigTypeIdBoundFn:
        case ZigTypeIdInvalid:
        case ZigTypeIdUnreachable:
        case ZigTypeIdFloat:
            return ErrorNone;
        case ZigTypeIdFnFrame:
            zig_panic("TODO: ir_resolve_lazy_recurse ZigTypeIdFnFrame");
        case ZigTypeIdUnion: {
            ConstUnionValue *union_val = &val->data.x_union;
            return ir_resolve_lazy_recurse(source_node, union_val->payload);
        }
        case ZigTypeIdVector:
            return ir_resolve_lazy_recurse_array(source_node, val, val->type->data.vector.len);
        case ZigTypeIdArray:
            return ir_resolve_lazy_recurse_array(source_node, val, val->type->data.array.len);
        case ZigTypeIdStruct:
            for (size_t i = 0; i < val->type->data.structure.src_field_count; i += 1) {
                ZigValue *field = val->data.x_struct.fields[i];
                if (val->type->data.structure.fields[i]->is_comptime) {
                    // comptime struct fields do not need to be resolved because
                    // they are not part of the value.
                    continue;
                }
                if ((err = ir_resolve_lazy_recurse(source_node, field)))
                    return err;
            }
            return ErrorNone;
        case ZigTypeIdOptional:
            if (get_src_ptr_type(val->type) != nullptr)
                return ErrorNone;
            if (val->data.x_optional == nullptr)
                return ErrorNone;

            return ir_resolve_lazy_recurse(source_node, val->data.x_optional);
        case ZigTypeIdErrorUnion: {
            bool is_err = val->data.x_err_union.error_set->data.x_err_set != nullptr;
            if (is_err) {
                return ir_resolve_lazy_recurse(source_node, val->data.x_err_union.error_set);
            } else {
                return ir_resolve_lazy_recurse(source_node, val->data.x_err_union.payload);
            }
        }
    }
    zig_unreachable();
}

Error ir_resolve_lazy(CodeGen *codegen, AstNode *source_node, ZigValue *val) {
    Error err;
    if ((err = ir_resolve_lazy_raw(source_node, val))) {
        return err;
    }
    if (type_is_invalid(val->type)) {
        return ErrorSemanticAnalyzeFail;
    }
    return ErrorNone;
}

void Stage1AirInst::src() {
    Stage1AirInst *inst = this;
    if (inst->source_node != nullptr) {
        inst->source_node->src();
    } else {
        fprintf(stderr, "(null source node)\n");
    }
}

void Stage1AirInst::dump() {
    Stage1AirInst *inst = this;
    inst->src();
    if (inst->scope == nullptr) {
        fprintf(stderr, "(null scope)\n");
    } else {
        ir_print_inst_gen(inst->scope->codegen, stderr, inst, 0);
    }
}

void IrAnalyze::dump() {
    ir_print_gen(this->codegen, stderr, this->new_irb.exec, 0);
    if (this->new_irb.current_basic_block != nullptr) {
        fprintf(stderr, "Current basic block:\n");
        ir_print_basic_block_gen(this->codegen, stderr, this->new_irb.current_basic_block, 1);
    }
}
