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

#include "analyze.hpp"
#include "ast_render.hpp"
#include "config.h"
#include "error.hpp"
#include "ir.hpp"
#include "ir_print.hpp"
#include "os.hpp"
#include "parser.hpp"
#include "softfloat.hpp"
#include "zig_llvm.h"


static const size_t default_backward_branch_quota = 1000;

static Error resolve_enum_type(CodeGen *g, ZigType *enum_type);
static Error resolve_struct_type(CodeGen *g, ZigType *struct_type);

static Error ATTRIBUTE_MUST_USE resolve_struct_zero_bits(CodeGen *g, ZigType *struct_type);
static Error ATTRIBUTE_MUST_USE resolve_struct_alignment(CodeGen *g, ZigType *struct_type);
static Error ATTRIBUTE_MUST_USE resolve_enum_zero_bits(CodeGen *g, ZigType *enum_type);
static Error ATTRIBUTE_MUST_USE resolve_union_zero_bits(CodeGen *g, ZigType *union_type);
static void analyze_fn_body(CodeGen *g, ZigFn *fn_table_entry);

static ErrorMsg *add_error_note_token(CodeGen *g, ErrorMsg *parent_msg, ImportTableEntry *owner, Token *token,
        Buf *msg)
{
    if (owner->c_import_node != nullptr) {
        // if this happens, then translate_c generated code that
        // failed semantic analysis, which isn't supposed to happen

        Buf *note_path = buf_create_from_str("?.c");
        Buf *note_source = buf_create_from_str("TODO: remember C source location to display here ");
        ZigList<size_t> note_line_offsets = {0};
        note_line_offsets.append(0);
        ErrorMsg *note = err_msg_create_with_line(note_path, 0, 0,
                note_source, &note_line_offsets, msg);

        err_msg_add_note(parent_msg, note);
        return note;
    }

    ErrorMsg *err = err_msg_create_with_line(owner->path, token->start_line, token->start_column,
            owner->source_code, owner->line_offsets, msg);

    err_msg_add_note(parent_msg, err);
    return err;
}

ErrorMsg *add_token_error(CodeGen *g, ImportTableEntry *owner, Token *token, Buf *msg) {
    if (owner->c_import_node != nullptr) {
        // if this happens, then translate_c generated code that
        // failed semantic analysis, which isn't supposed to happen
        ErrorMsg *err = add_node_error(g, owner->c_import_node,
            buf_sprintf("compiler bug: @cImport generated invalid zig code"));

        add_error_note_token(g, err, owner, token, msg);

        g->errors.append(err);
        return err;
    }
    ErrorMsg *err = err_msg_create_with_line(owner->path, token->start_line, token->start_column,
            owner->source_code, owner->line_offsets, msg);

    g->errors.append(err);
    return err;
}

ErrorMsg *add_node_error(CodeGen *g, AstNode *node, Buf *msg) {
    Token fake_token;
    fake_token.start_line = node->line;
    fake_token.start_column = node->column;
    return add_token_error(g, node->owner, &fake_token, msg);
}

ErrorMsg *add_error_note(CodeGen *g, ErrorMsg *parent_msg, AstNode *node, Buf *msg) {
    Token fake_token;
    fake_token.start_line = node->line;
    fake_token.start_column = node->column;
    return add_error_note_token(g, parent_msg, node->owner, &fake_token, msg);
}

ZigType *new_type_table_entry(ZigTypeId id) {
    ZigType *entry = allocate<ZigType>(1);
    entry->id = id;
    return entry;
}

static ScopeDecls **get_container_scope_ptr(ZigType *type_entry) {
    if (type_entry->id == ZigTypeIdStruct) {
        return &type_entry->data.structure.decls_scope;
    } else if (type_entry->id == ZigTypeIdEnum) {
        return &type_entry->data.enumeration.decls_scope;
    } else if (type_entry->id == ZigTypeIdUnion) {
        return &type_entry->data.unionation.decls_scope;
    }
    zig_unreachable();
}

ScopeDecls *get_container_scope(ZigType *type_entry) {
    return *get_container_scope_ptr(type_entry);
}

void init_scope(CodeGen *g, Scope *dest, ScopeId id, AstNode *source_node, Scope *parent) {
    dest->codegen = g;
    dest->id = id;
    dest->source_node = source_node;
    dest->parent = parent;
}

ScopeDecls *create_decls_scope(CodeGen *g, AstNode *node, Scope *parent, ZigType *container_type, ImportTableEntry *import) {
    assert(node == nullptr || node->type == NodeTypeContainerDecl || node->type == NodeTypeFnCallExpr);
    ScopeDecls *scope = allocate<ScopeDecls>(1);
    init_scope(g, &scope->base, ScopeIdDecls, node, parent);
    scope->decl_table.init(4);
    scope->container_type = container_type;
    scope->import = import;
    return scope;
}

ScopeBlock *create_block_scope(CodeGen *g, AstNode *node, Scope *parent) {
    assert(node->type == NodeTypeBlock);
    ScopeBlock *scope = allocate<ScopeBlock>(1);
    init_scope(g, &scope->base, ScopeIdBlock, node, parent);
    scope->name = node->data.block.name;
    return scope;
}

ScopeDefer *create_defer_scope(CodeGen *g, AstNode *node, Scope *parent) {
    assert(node->type == NodeTypeDefer);
    ScopeDefer *scope = allocate<ScopeDefer>(1);
    init_scope(g, &scope->base, ScopeIdDefer, node, parent);
    return scope;
}

ScopeDeferExpr *create_defer_expr_scope(CodeGen *g, AstNode *node, Scope *parent) {
    assert(node->type == NodeTypeDefer);
    ScopeDeferExpr *scope = allocate<ScopeDeferExpr>(1);
    init_scope(g, &scope->base, ScopeIdDeferExpr, node, parent);
    return scope;
}

Scope *create_var_scope(CodeGen *g, AstNode *node, Scope *parent, ZigVar *var) {
    ScopeVarDecl *scope = allocate<ScopeVarDecl>(1);
    init_scope(g, &scope->base, ScopeIdVarDecl, node, parent);
    scope->var = var;
    return &scope->base;
}

ScopeCImport *create_cimport_scope(CodeGen *g, AstNode *node, Scope *parent) {
    assert(node->type == NodeTypeFnCallExpr);
    ScopeCImport *scope = allocate<ScopeCImport>(1);
    init_scope(g, &scope->base, ScopeIdCImport, node, parent);
    buf_resize(&scope->buf, 0);
    return scope;
}

ScopeLoop *create_loop_scope(CodeGen *g, AstNode *node, Scope *parent) {
    ScopeLoop *scope = allocate<ScopeLoop>(1);
    init_scope(g, &scope->base, ScopeIdLoop, node, parent);
    if (node->type == NodeTypeWhileExpr) {
        scope->name = node->data.while_expr.name;
    } else if (node->type == NodeTypeForExpr) {
        scope->name = node->data.for_expr.name;
    } else {
        zig_unreachable();
    }
    return scope;
}

Scope *create_runtime_scope(CodeGen *g, AstNode *node, Scope *parent, IrInstruction *is_comptime) {
    ScopeRuntime *scope = allocate<ScopeRuntime>(1);
    scope->is_comptime = is_comptime;
    init_scope(g, &scope->base, ScopeIdRuntime, node, parent);
    return &scope->base;
}

ScopeSuspend *create_suspend_scope(CodeGen *g, AstNode *node, Scope *parent) {
    assert(node->type == NodeTypeSuspend);
    ScopeSuspend *scope = allocate<ScopeSuspend>(1);
    init_scope(g, &scope->base, ScopeIdSuspend, node, parent);
    return scope;
}

ScopeFnDef *create_fndef_scope(CodeGen *g, AstNode *node, Scope *parent, ZigFn *fn_entry) {
    ScopeFnDef *scope = allocate<ScopeFnDef>(1);
    init_scope(g, &scope->base, ScopeIdFnDef, node, parent);
    scope->fn_entry = fn_entry;
    return scope;
}

Scope *create_comptime_scope(CodeGen *g, AstNode *node, Scope *parent) {
    ScopeCompTime *scope = allocate<ScopeCompTime>(1);
    init_scope(g, &scope->base, ScopeIdCompTime, node, parent);
    return &scope->base;
}

Scope *create_coro_prelude_scope(CodeGen *g, AstNode *node, Scope *parent) {
    ScopeCoroPrelude *scope = allocate<ScopeCoroPrelude>(1);
    init_scope(g, &scope->base, ScopeIdCoroPrelude, node, parent);
    return &scope->base;
}

ImportTableEntry *get_scope_import(Scope *scope) {
    while (scope) {
        if (scope->id == ScopeIdDecls) {
            ScopeDecls *decls_scope = (ScopeDecls *)scope;
            assert(decls_scope->import);
            return decls_scope->import;
        }
        scope = scope->parent;
    }
    zig_unreachable();
}

static ZigType *new_container_type_entry(CodeGen *g, ZigTypeId id, AstNode *source_node, Scope *parent_scope) {
    ZigType *entry = new_type_table_entry(id);
    *get_container_scope_ptr(entry) = create_decls_scope(g, source_node, parent_scope, entry, get_scope_import(parent_scope));
    return entry;
}

static uint8_t bits_needed_for_unsigned(uint64_t x) {
    if (x == 0) {
        return 0;
    }
    uint8_t base = log2_u64(x);
    uint64_t upper = (((uint64_t)1) << base) - 1;
    return (upper >= x) ? base : (base + 1);
}

AstNode *type_decl_node(ZigType *type_entry) {
    switch (type_entry->id) {
        case ZigTypeIdInvalid:
            zig_unreachable();
        case ZigTypeIdStruct:
            return type_entry->data.structure.decl_node;
        case ZigTypeIdEnum:
            return type_entry->data.enumeration.decl_node;
        case ZigTypeIdUnion:
            return type_entry->data.unionation.decl_node;
        case ZigTypeIdOpaque:
        case ZigTypeIdMetaType:
        case ZigTypeIdVoid:
        case ZigTypeIdBool:
        case ZigTypeIdUnreachable:
        case ZigTypeIdInt:
        case ZigTypeIdFloat:
        case ZigTypeIdPointer:
        case ZigTypeIdArray:
        case ZigTypeIdComptimeFloat:
        case ZigTypeIdComptimeInt:
        case ZigTypeIdUndefined:
        case ZigTypeIdNull:
        case ZigTypeIdOptional:
        case ZigTypeIdErrorUnion:
        case ZigTypeIdErrorSet:
        case ZigTypeIdFn:
        case ZigTypeIdNamespace:
        case ZigTypeIdBoundFn:
        case ZigTypeIdArgTuple:
        case ZigTypeIdPromise:
        case ZigTypeIdVector:
            return nullptr;
    }
    zig_unreachable();
}

bool type_is_resolved(ZigType *type_entry, ResolveStatus status) {
    switch (type_entry->id) {
        case ZigTypeIdInvalid:
            zig_unreachable();
        case ZigTypeIdStruct:
            return type_entry->data.structure.resolve_status >= status;
        case ZigTypeIdEnum:
            switch (status) {
                case ResolveStatusUnstarted:
                    return true;
                case ResolveStatusInvalid:
                    zig_unreachable();
                case ResolveStatusZeroBitsKnown:
                    return type_entry->data.enumeration.zero_bits_known;
                case ResolveStatusAlignmentKnown:
                    return type_entry->data.enumeration.zero_bits_known;
                case ResolveStatusSizeKnown:
                    return type_entry->data.enumeration.complete;
            }
            zig_unreachable();
        case ZigTypeIdUnion:
            switch (status) {
                case ResolveStatusUnstarted:
                    return true;
                case ResolveStatusInvalid:
                    zig_unreachable();
                case ResolveStatusZeroBitsKnown:
                    return type_entry->data.unionation.zero_bits_known;
                case ResolveStatusAlignmentKnown:
                    return type_entry->data.unionation.zero_bits_known;
                case ResolveStatusSizeKnown:
                    return type_entry->data.unionation.complete;
            }
            zig_unreachable();
        case ZigTypeIdOpaque:
            return status < ResolveStatusSizeKnown;
        case ZigTypeIdMetaType:
        case ZigTypeIdVoid:
        case ZigTypeIdBool:
        case ZigTypeIdUnreachable:
        case ZigTypeIdInt:
        case ZigTypeIdFloat:
        case ZigTypeIdPointer:
        case ZigTypeIdArray:
        case ZigTypeIdComptimeFloat:
        case ZigTypeIdComptimeInt:
        case ZigTypeIdUndefined:
        case ZigTypeIdNull:
        case ZigTypeIdOptional:
        case ZigTypeIdErrorUnion:
        case ZigTypeIdErrorSet:
        case ZigTypeIdFn:
        case ZigTypeIdNamespace:
        case ZigTypeIdBoundFn:
        case ZigTypeIdArgTuple:
        case ZigTypeIdPromise:
        case ZigTypeIdVector:
            return true;
    }
    zig_unreachable();
}

bool type_is_complete(ZigType *type_entry) {
    return type_is_resolved(type_entry, ResolveStatusSizeKnown);
}

uint64_t type_size(CodeGen *g, ZigType *type_entry) {
    assert(type_is_complete(type_entry));

    if (!type_has_bits(type_entry))
        return 0;

    if (type_entry->id == ZigTypeIdStruct && type_entry->data.structure.layout == ContainerLayoutPacked) {
        uint64_t size_in_bits = type_size_bits(g, type_entry);
        return (size_in_bits + 7) / 8;
    } else if (type_entry->id == ZigTypeIdArray) {
        ZigType *child_type = type_entry->data.array.child_type;
        if (child_type->id == ZigTypeIdStruct &&
            child_type->data.structure.layout == ContainerLayoutPacked)
        {
            uint64_t size_in_bits = type_size_bits(g, type_entry);
            return (size_in_bits + 7) / 8;
        }
    }

    return LLVMABISizeOfType(g->target_data_ref, type_entry->type_ref);
}

uint64_t type_size_bits(CodeGen *g, ZigType *type_entry) {
    assert(type_is_complete(type_entry));

    if (!type_has_bits(type_entry))
        return 0;

    if (type_entry->id == ZigTypeIdStruct) {
        if (type_entry->data.structure.layout == ContainerLayoutPacked) {
            uint64_t result = 0;
            for (size_t i = 0; i < type_entry->data.structure.src_field_count; i += 1) {
                result += type_size_bits(g, type_entry->data.structure.fields[i].type_entry);
            }
            return result;
        } else if (type_entry->data.structure.layout == ContainerLayoutExtern) {
            return type_size(g, type_entry) * 8;
        }
    } else if (type_entry->id == ZigTypeIdArray) {
        ZigType *child_type = type_entry->data.array.child_type;
        return type_entry->data.array.len * type_size_bits(g, child_type);
    }

    return LLVMSizeOfTypeInBits(g->target_data_ref, type_entry->type_ref);
}

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

ZigType *get_smallest_unsigned_int_type(CodeGen *g, uint64_t x) {
    return get_int_type(g, false, bits_needed_for_unsigned(x));
}

ZigType *get_promise_type(CodeGen *g, ZigType *result_type) {
    if (result_type != nullptr && result_type->promise_parent != nullptr) {
        return result_type->promise_parent;
    } else if (result_type == nullptr && g->builtin_types.entry_promise != nullptr) {
        return g->builtin_types.entry_promise;
    }

    ZigType *u8_ptr_type = get_pointer_to_type(g, g->builtin_types.entry_u8, false);
    ZigType *entry = new_type_table_entry(ZigTypeIdPromise);
    entry->type_ref = u8_ptr_type->type_ref;
    entry->zero_bits = false;
    entry->data.promise.result_type = result_type;
    buf_init_from_str(&entry->name, "promise");
    if (result_type != nullptr) {
        buf_appendf(&entry->name, "->%s", buf_ptr(&result_type->name));
    }
    entry->di_type = u8_ptr_type->di_type;

    if (result_type != nullptr) {
        result_type->promise_parent = entry;
    } else if (result_type == nullptr) {
        g->builtin_types.entry_promise = entry;
    }
    return entry;
}

static const char *ptr_len_to_star_str(PtrLen ptr_len) {
    switch (ptr_len) {
        case PtrLenSingle:
            return "*";
        case PtrLenUnknown:
            return "[*]";
        case PtrLenC:
            return "[*c]";
    }
    zig_unreachable();
}

ZigType *get_pointer_to_type_extra(CodeGen *g, ZigType *child_type, bool is_const,
        bool is_volatile, PtrLen ptr_len, uint32_t byte_alignment,
        uint32_t bit_offset_in_host, uint32_t host_int_bytes)
{
    // TODO when implementing https://github.com/ziglang/zig/issues/1953
    // move this to a parameter
    bool allow_zero = (ptr_len == PtrLenC);
    assert(!type_is_invalid(child_type));
    assert(ptr_len == PtrLenSingle || child_type->id != ZigTypeIdOpaque);

    if (byte_alignment != 0) {
        uint32_t abi_alignment = get_abi_alignment(g, child_type);
        if (byte_alignment == abi_alignment)
            byte_alignment = 0;
    }

    if (host_int_bytes != 0) {
        uint32_t child_type_bits = type_size_bits(g, child_type);
        if (host_int_bytes * 8 == child_type_bits) {
            assert(bit_offset_in_host == 0);
            host_int_bytes = 0;
        }
    }

    TypeId type_id = {};
    ZigType **parent_pointer = nullptr;
    if (host_int_bytes != 0 || is_volatile || byte_alignment != 0 || ptr_len != PtrLenSingle || allow_zero) {
        type_id.id = ZigTypeIdPointer;
        type_id.data.pointer.child_type = child_type;
        type_id.data.pointer.is_const = is_const;
        type_id.data.pointer.is_volatile = is_volatile;
        type_id.data.pointer.alignment = byte_alignment;
        type_id.data.pointer.bit_offset_in_host = bit_offset_in_host;
        type_id.data.pointer.host_int_bytes = host_int_bytes;
        type_id.data.pointer.ptr_len = ptr_len;
        type_id.data.pointer.allow_zero = allow_zero;

        auto existing_entry = g->type_table.maybe_get(type_id);
        if (existing_entry)
            return existing_entry->value;
    } else {
        assert(bit_offset_in_host == 0);
        parent_pointer = &child_type->pointer_parent[(is_const ? 1 : 0)];
        if (*parent_pointer) {
            assert((*parent_pointer)->data.pointer.explicit_alignment == 0);
            return *parent_pointer;
        }
    }

    assert(type_is_resolved(child_type, ResolveStatusZeroBitsKnown));

    ZigType *entry = new_type_table_entry(ZigTypeIdPointer);

    const char *star_str = ptr_len_to_star_str(ptr_len);
    const char *const_str = is_const ? "const " : "";
    const char *volatile_str = is_volatile ? "volatile " : "";
    const char *allow_zero_str;
    if (ptr_len == PtrLenC) {
        assert(allow_zero);
        allow_zero_str = "";
    } else {
        allow_zero_str = allow_zero ? "allowzero " : "";
    }
    buf_resize(&entry->name, 0);
    if (host_int_bytes == 0 && byte_alignment == 0) {
        buf_appendf(&entry->name, "%s%s%s%s%s",
                star_str, const_str, volatile_str, allow_zero_str, buf_ptr(&child_type->name));
    } else if (host_int_bytes == 0) {
        buf_appendf(&entry->name, "%salign(%" PRIu32 ") %s%s%s%s", star_str, byte_alignment,
                const_str, volatile_str, allow_zero_str, buf_ptr(&child_type->name));
    } else if (byte_alignment == 0) {
        buf_appendf(&entry->name, "%salign(:%" PRIu32 ":%" PRIu32 ") %s%s%s%s", star_str,
                bit_offset_in_host, host_int_bytes, const_str, volatile_str, allow_zero_str,
                buf_ptr(&child_type->name));
    } else {
        buf_appendf(&entry->name, "%salign(%" PRIu32 ":%" PRIu32 ":%" PRIu32 ") %s%s%s%s", star_str, byte_alignment,
                bit_offset_in_host, host_int_bytes, const_str, volatile_str, allow_zero_str,
                buf_ptr(&child_type->name));
    }

    assert(child_type->id != ZigTypeIdInvalid);

    entry->zero_bits = !type_has_bits(child_type);

    if (!entry->zero_bits) {
        if (is_const || is_volatile || byte_alignment != 0 || ptr_len != PtrLenSingle ||
            bit_offset_in_host != 0 || allow_zero)
        {
            ZigType *peer_type = get_pointer_to_type_extra(g, child_type, false, false,
                    PtrLenSingle, 0, 0, host_int_bytes);
            entry->type_ref = peer_type->type_ref;
            entry->di_type = peer_type->di_type;
        } else {
            if (host_int_bytes == 0) {
                entry->type_ref = LLVMPointerType(child_type->type_ref, 0);
                uint64_t debug_size_in_bits = 8*LLVMStoreSizeOfType(g->target_data_ref, entry->type_ref);
                uint64_t debug_align_in_bits = 8*LLVMABIAlignmentOfType(g->target_data_ref, entry->type_ref);
                assert(child_type->di_type);
                entry->di_type = ZigLLVMCreateDebugPointerType(g->dbuilder, child_type->di_type,
                        debug_size_in_bits, debug_align_in_bits, buf_ptr(&entry->name));
            } else {
                ZigType *host_int_type = get_int_type(g, false, host_int_bytes * 8);
                entry->type_ref = LLVMPointerType(host_int_type->type_ref, 0);
                uint64_t debug_size_in_bits = 8*LLVMStoreSizeOfType(g->target_data_ref, host_int_type->type_ref);
                uint64_t debug_align_in_bits = 8*LLVMABIAlignmentOfType(g->target_data_ref, host_int_type->type_ref);
                entry->di_type = ZigLLVMCreateDebugPointerType(g->dbuilder, host_int_type->di_type,
                        debug_size_in_bits, debug_align_in_bits, buf_ptr(&entry->name));
            }
        }
    } else {
        assert(byte_alignment == 0);
        entry->di_type = g->builtin_types.entry_void->di_type;
    }

    entry->data.pointer.ptr_len = ptr_len;
    entry->data.pointer.child_type = child_type;
    entry->data.pointer.is_const = is_const;
    entry->data.pointer.is_volatile = is_volatile;
    entry->data.pointer.explicit_alignment = byte_alignment;
    entry->data.pointer.bit_offset_in_host = bit_offset_in_host;
    entry->data.pointer.host_int_bytes = host_int_bytes;
    entry->data.pointer.allow_zero = allow_zero;

    if (parent_pointer) {
        *parent_pointer = entry;
    } else {
        g->type_table.put(type_id, entry);
    }
    return entry;
}

ZigType *get_pointer_to_type(CodeGen *g, ZigType *child_type, bool is_const) {
    return get_pointer_to_type_extra(g, child_type, is_const, false, PtrLenSingle, 0, 0, 0);
}

ZigType *get_promise_frame_type(CodeGen *g, ZigType *return_type) {
    if (return_type->promise_frame_parent != nullptr) {
        return return_type->promise_frame_parent;
    }

    ZigType *atomic_state_type = g->builtin_types.entry_usize;
    ZigType *result_ptr_type = get_pointer_to_type(g, return_type, false);

    ZigList<const char *> field_names = {};
    field_names.append(ATOMIC_STATE_FIELD_NAME);
    field_names.append(RESULT_FIELD_NAME);
    field_names.append(RESULT_PTR_FIELD_NAME);
    if (g->have_err_ret_tracing) {
        field_names.append(ERR_RET_TRACE_PTR_FIELD_NAME);
        field_names.append(ERR_RET_TRACE_FIELD_NAME);
        field_names.append(RETURN_ADDRESSES_FIELD_NAME);
    }

    ZigList<ZigType *> field_types = {};
    field_types.append(atomic_state_type);
    field_types.append(return_type);
    field_types.append(result_ptr_type);
    if (g->have_err_ret_tracing) {
        field_types.append(get_ptr_to_stack_trace_type(g));
        field_types.append(g->stack_trace_type);
        field_types.append(get_array_type(g, g->builtin_types.entry_usize, stack_trace_ptr_count));
    }

    assert(field_names.length == field_types.length);
    Buf *name = buf_sprintf("AsyncFramePromise(%s)", buf_ptr(&return_type->name));
    ZigType *entry = get_struct_type(g, buf_ptr(name), field_names.items, field_types.items, field_names.length);

    return_type->promise_frame_parent = entry;
    return entry;
}

ZigType *get_optional_type(CodeGen *g, ZigType *child_type) {
    if (child_type->optional_parent) {
        ZigType *entry = child_type->optional_parent;
        return entry;
    } else {
        assertNoError(ensure_complete_type(g, child_type));

        ZigType *entry = new_type_table_entry(ZigTypeIdOptional);
        assert(child_type->type_ref || child_type->zero_bits);

        buf_resize(&entry->name, 0);
        buf_appendf(&entry->name, "?%s", buf_ptr(&child_type->name));

        if (child_type->zero_bits) {
            entry->type_ref = LLVMInt1Type();
            entry->di_type = g->builtin_types.entry_bool->di_type;
        } else if (type_is_codegen_pointer(child_type) || child_type->id == ZigTypeIdErrorSet) {
            assert(child_type->di_type);
            // this is an optimization but also is necessary for calling C
            // functions where all pointers are maybe pointers
            // function types are technically pointers
            entry->type_ref = child_type->type_ref;
            entry->di_type = child_type->di_type;
            if (entry->di_type == g->builtin_types.entry_global_error_set->di_type) {
                g->error_di_types.append(&entry->di_type);
            }
        } else {
            assert(child_type->di_type);
            // create a struct with a boolean whether this is the null value
            LLVMTypeRef elem_types[] = {
                child_type->type_ref,
                LLVMInt1Type(),
            };
            entry->type_ref = LLVMStructType(elem_types, 2, false);


            ZigLLVMDIScope *compile_unit_scope = ZigLLVMCompileUnitToScope(g->compile_unit);
            ZigLLVMDIFile *di_file = nullptr;
            unsigned line = 0;
            entry->di_type = ZigLLVMCreateReplaceableCompositeType(g->dbuilder,
                ZigLLVMTag_DW_structure_type(), buf_ptr(&entry->name),
                compile_unit_scope, di_file, line);

            uint64_t val_debug_size_in_bits = 8*LLVMStoreSizeOfType(g->target_data_ref, child_type->type_ref);
            uint64_t val_debug_align_in_bits = 8*LLVMABISizeOfType(g->target_data_ref, child_type->type_ref);
            uint64_t val_offset_in_bits = 8*LLVMOffsetOfElement(g->target_data_ref, entry->type_ref, 0);

            ZigType *bool_type = g->builtin_types.entry_bool;
            uint64_t maybe_debug_size_in_bits = 8*LLVMStoreSizeOfType(g->target_data_ref, bool_type->type_ref);
            uint64_t maybe_debug_align_in_bits = 8*LLVMABISizeOfType(g->target_data_ref, bool_type->type_ref);
            uint64_t maybe_offset_in_bits = 8*LLVMOffsetOfElement(g->target_data_ref, entry->type_ref, 1);

            uint64_t debug_size_in_bits = 8*LLVMStoreSizeOfType(g->target_data_ref, entry->type_ref);
            uint64_t debug_align_in_bits = 8*LLVMABISizeOfType(g->target_data_ref, entry->type_ref);

            ZigLLVMDIType *di_element_types[] = {
                ZigLLVMCreateDebugMemberType(g->dbuilder, ZigLLVMTypeToScope(entry->di_type),
                        "val", di_file, line,
                        val_debug_size_in_bits,
                        val_debug_align_in_bits,
                        val_offset_in_bits,
                        0, child_type->di_type),
                ZigLLVMCreateDebugMemberType(g->dbuilder, ZigLLVMTypeToScope(entry->di_type),
                        "maybe", di_file, line,
                        maybe_debug_size_in_bits,
                        maybe_debug_align_in_bits,
                        maybe_offset_in_bits,
                        0, bool_type->di_type),
            };
            ZigLLVMDIType *replacement_di_type = ZigLLVMCreateDebugStructType(g->dbuilder,
                    compile_unit_scope,
                    buf_ptr(&entry->name),
                    di_file, line, debug_size_in_bits, debug_align_in_bits, 0,
                    nullptr, di_element_types, 2, 0, nullptr, "");

            ZigLLVMReplaceTemporary(g->dbuilder, entry->di_type, replacement_di_type);
            entry->di_type = replacement_di_type;
        }

        entry->data.maybe.child_type = child_type;

        child_type->optional_parent = entry;
        return entry;
    }
}

ZigType *get_error_union_type(CodeGen *g, ZigType *err_set_type, ZigType *payload_type) {
    assert(err_set_type->id == ZigTypeIdErrorSet);
    assert(!type_is_invalid(payload_type));

    TypeId type_id = {};
    type_id.id = ZigTypeIdErrorUnion;
    type_id.data.error_union.err_set_type = err_set_type;
    type_id.data.error_union.payload_type = payload_type;

    auto existing_entry = g->type_table.maybe_get(type_id);
    if (existing_entry) {
        return existing_entry->value;
    }

    ZigType *entry = new_type_table_entry(ZigTypeIdErrorUnion);
    assert(payload_type->di_type);
    assert(type_is_complete(payload_type));

    buf_resize(&entry->name, 0);
    buf_appendf(&entry->name, "%s!%s", buf_ptr(&err_set_type->name), buf_ptr(&payload_type->name));

    entry->data.error_union.err_set_type = err_set_type;
    entry->data.error_union.payload_type = payload_type;

    if (!type_has_bits(payload_type)) {
        if (type_has_bits(err_set_type)) {
            entry->type_ref = err_set_type->type_ref;
            entry->di_type = err_set_type->di_type;
            g->error_di_types.append(&entry->di_type);
        } else {
            entry->zero_bits = true;
            entry->di_type = g->builtin_types.entry_void->di_type;
        }
    } else if (!type_has_bits(err_set_type)) {
        entry->type_ref = payload_type->type_ref;
        entry->di_type = payload_type->di_type;
    } else {
        LLVMTypeRef elem_types[] = {
            err_set_type->type_ref,
            payload_type->type_ref,
        };
        entry->type_ref = LLVMStructType(elem_types, 2, false);

        ZigLLVMDIScope *compile_unit_scope = ZigLLVMCompileUnitToScope(g->compile_unit);
        ZigLLVMDIFile *di_file = nullptr;
        unsigned line = 0;
        entry->di_type = ZigLLVMCreateReplaceableCompositeType(g->dbuilder,
            ZigLLVMTag_DW_structure_type(), buf_ptr(&entry->name),
            compile_unit_scope, di_file, line);

        uint64_t tag_debug_size_in_bits = 8*LLVMStoreSizeOfType(g->target_data_ref, err_set_type->type_ref);
        uint64_t tag_debug_align_in_bits = 8*LLVMABISizeOfType(g->target_data_ref, err_set_type->type_ref);
        uint64_t tag_offset_in_bits = 8*LLVMOffsetOfElement(g->target_data_ref, entry->type_ref, err_union_err_index);

        uint64_t value_debug_size_in_bits = 8*LLVMStoreSizeOfType(g->target_data_ref, payload_type->type_ref);
        uint64_t value_debug_align_in_bits = 8*LLVMABISizeOfType(g->target_data_ref, payload_type->type_ref);
        uint64_t value_offset_in_bits = 8*LLVMOffsetOfElement(g->target_data_ref, entry->type_ref,
                err_union_payload_index);

        uint64_t debug_size_in_bits = 8*LLVMStoreSizeOfType(g->target_data_ref, entry->type_ref);
        uint64_t debug_align_in_bits = 8*LLVMABISizeOfType(g->target_data_ref, entry->type_ref);

        ZigLLVMDIType *di_element_types[] = {
            ZigLLVMCreateDebugMemberType(g->dbuilder, ZigLLVMTypeToScope(entry->di_type),
                    "tag", di_file, line,
                    tag_debug_size_in_bits,
                    tag_debug_align_in_bits,
                    tag_offset_in_bits,
                    0, err_set_type->di_type),
            ZigLLVMCreateDebugMemberType(g->dbuilder, ZigLLVMTypeToScope(entry->di_type),
                    "value", di_file, line,
                    value_debug_size_in_bits,
                    value_debug_align_in_bits,
                    value_offset_in_bits,
                    0, payload_type->di_type),
        };

        ZigLLVMDIType *replacement_di_type = ZigLLVMCreateDebugStructType(g->dbuilder,
                compile_unit_scope,
                buf_ptr(&entry->name),
                di_file, line,
                debug_size_in_bits,
                debug_align_in_bits,
                0,
                nullptr, di_element_types, 2, 0, nullptr, "");

        ZigLLVMReplaceTemporary(g->dbuilder, entry->di_type, replacement_di_type);
        entry->di_type = replacement_di_type;
    }

    g->type_table.put(type_id, entry);
    return entry;
}

ZigType *get_array_type(CodeGen *g, ZigType *child_type, uint64_t array_size) {
    TypeId type_id = {};
    type_id.id = ZigTypeIdArray;
    type_id.data.array.child_type = child_type;
    type_id.data.array.size = array_size;
    auto existing_entry = g->type_table.maybe_get(type_id);
    if (existing_entry) {
        ZigType *entry = existing_entry->value;
        return entry;
    }

    assertNoError(ensure_complete_type(g, child_type));

    ZigType *entry = new_type_table_entry(ZigTypeIdArray);
    entry->zero_bits = (array_size == 0) || child_type->zero_bits;

    buf_resize(&entry->name, 0);
    buf_appendf(&entry->name, "[%" ZIG_PRI_u64 "]%s", array_size, buf_ptr(&child_type->name));

    if (entry->zero_bits) {
        entry->di_type = ZigLLVMCreateDebugArrayType(g->dbuilder, 0,
                0, child_type->di_type, 0);
    } else {
        entry->type_ref = child_type->type_ref ? LLVMArrayType(child_type->type_ref,
                (unsigned int)array_size) : nullptr;

        uint64_t debug_size_in_bits = 8*LLVMStoreSizeOfType(g->target_data_ref, entry->type_ref);
        uint64_t debug_align_in_bits = 8*LLVMABISizeOfType(g->target_data_ref, entry->type_ref);

        entry->di_type = ZigLLVMCreateDebugArrayType(g->dbuilder, debug_size_in_bits,
                debug_align_in_bits, child_type->di_type, (int)array_size);
    }
    entry->data.array.child_type = child_type;
    entry->data.array.len = array_size;

    g->type_table.put(type_id, entry);
    return entry;
}

static void slice_type_common_init(CodeGen *g, ZigType *pointer_type, ZigType *entry) {
    unsigned element_count = 2;
    Buf *ptr_field_name = buf_create_from_str("ptr");
    Buf *len_field_name = buf_create_from_str("len");

    entry->data.structure.layout = ContainerLayoutAuto;
    entry->data.structure.is_slice = true;
    entry->data.structure.src_field_count = element_count;
    entry->data.structure.gen_field_count = element_count;
    entry->data.structure.fields = allocate<TypeStructField>(element_count);
    entry->data.structure.fields_by_name.init(element_count);
    entry->data.structure.fields[slice_ptr_index].name = ptr_field_name;
    entry->data.structure.fields[slice_ptr_index].type_entry = pointer_type;
    entry->data.structure.fields[slice_ptr_index].src_index = slice_ptr_index;
    entry->data.structure.fields[slice_ptr_index].gen_index = 0;
    entry->data.structure.fields[slice_len_index].name = len_field_name;
    entry->data.structure.fields[slice_len_index].type_entry = g->builtin_types.entry_usize;
    entry->data.structure.fields[slice_len_index].src_index = slice_len_index;
    entry->data.structure.fields[slice_len_index].gen_index = 1;

    entry->data.structure.fields_by_name.put(ptr_field_name, &entry->data.structure.fields[slice_ptr_index]);
    entry->data.structure.fields_by_name.put(len_field_name, &entry->data.structure.fields[slice_len_index]);

    if (!type_has_bits(pointer_type->data.pointer.child_type)) {
        entry->data.structure.gen_field_count = 1;
        entry->data.structure.fields[slice_ptr_index].gen_index = SIZE_MAX;
        entry->data.structure.fields[slice_len_index].gen_index = 0;
    }
}

ZigType *get_slice_type(CodeGen *g, ZigType *ptr_type) {
    assert(ptr_type->id == ZigTypeIdPointer);
    assert(ptr_type->data.pointer.ptr_len == PtrLenUnknown);

    ZigType **parent_pointer = &ptr_type->data.pointer.slice_parent;
    if (*parent_pointer) {
        return *parent_pointer;
    }

    ZigType *entry = new_type_table_entry(ZigTypeIdStruct);

    // replace the & with [] to go from a ptr type name to a slice type name
    buf_resize(&entry->name, 0);
    size_t name_offset = (ptr_type->data.pointer.ptr_len == PtrLenSingle) ? 1 : 3;
    buf_appendf(&entry->name, "[]%s", buf_ptr(&ptr_type->name) + name_offset);

    ZigType *child_type = ptr_type->data.pointer.child_type;
    if (ptr_type->data.pointer.is_const || ptr_type->data.pointer.is_volatile ||
        ptr_type->data.pointer.explicit_alignment != 0 || ptr_type->data.pointer.allow_zero)
    {
        ZigType *peer_ptr_type = get_pointer_to_type_extra(g, child_type, false, false,
                PtrLenUnknown, 0, 0, 0);
        ZigType *peer_slice_type = get_slice_type(g, peer_ptr_type);

        slice_type_common_init(g, ptr_type, entry);

        entry->type_ref = peer_slice_type->type_ref;
        entry->di_type = peer_slice_type->di_type;
        entry->data.structure.resolve_status = ResolveStatusSizeKnown;
        entry->data.structure.abi_alignment = peer_slice_type->data.structure.abi_alignment;

        *parent_pointer = entry;
        return entry;
    }

    // If the child type is []const T then we need to make sure the type ref
    // and debug info is the same as if the child type were []T.
    if (is_slice(child_type)) {
        ZigType *child_ptr_type = child_type->data.structure.fields[slice_ptr_index].type_entry;
        assert(child_ptr_type->id == ZigTypeIdPointer);
        if (child_ptr_type->data.pointer.is_const || child_ptr_type->data.pointer.is_volatile ||
            child_ptr_type->data.pointer.explicit_alignment != 0 || child_ptr_type->data.pointer.allow_zero)
        {
            ZigType *grand_child_type = child_ptr_type->data.pointer.child_type;
            ZigType *bland_child_ptr_type = get_pointer_to_type_extra(g, grand_child_type, false, false,
                    PtrLenUnknown, 0, 0, 0);
            ZigType *bland_child_slice = get_slice_type(g, bland_child_ptr_type);
            ZigType *peer_ptr_type = get_pointer_to_type_extra(g, bland_child_slice, false, false,
                    PtrLenUnknown, 0, 0, 0);
            ZigType *peer_slice_type = get_slice_type(g, peer_ptr_type);

            entry->type_ref = peer_slice_type->type_ref;
            entry->di_type = peer_slice_type->di_type;
            entry->data.structure.abi_alignment = peer_slice_type->data.structure.abi_alignment;
        }
    }

    slice_type_common_init(g, ptr_type, entry);

    if (!entry->type_ref) {
        entry->type_ref = LLVMStructCreateNamed(LLVMGetGlobalContext(), buf_ptr(&entry->name));

        ZigLLVMDIScope *compile_unit_scope = ZigLLVMCompileUnitToScope(g->compile_unit);
        ZigLLVMDIFile *di_file = nullptr;
        unsigned line = 0;
        entry->di_type = ZigLLVMCreateReplaceableCompositeType(g->dbuilder,
            ZigLLVMTag_DW_structure_type(), buf_ptr(&entry->name),
            compile_unit_scope, di_file, line);

        if (child_type->zero_bits) {
            LLVMTypeRef element_types[] = {
                g->builtin_types.entry_usize->type_ref,
            };
            LLVMStructSetBody(entry->type_ref, element_types, 1, false);

            ZigType *usize_type = g->builtin_types.entry_usize;
            uint64_t len_debug_size_in_bits = 8*LLVMStoreSizeOfType(g->target_data_ref, usize_type->type_ref);
            uint64_t len_debug_align_in_bits = 8*LLVMABIAlignmentOfType(g->target_data_ref, usize_type->type_ref);
            uint64_t len_offset_in_bits = 8*LLVMOffsetOfElement(g->target_data_ref, entry->type_ref, 0);

            uint64_t debug_size_in_bits = 8*LLVMStoreSizeOfType(g->target_data_ref, entry->type_ref);
            uint64_t debug_align_in_bits = 8*LLVMABIAlignmentOfType(g->target_data_ref, entry->type_ref);

            ZigLLVMDIType *di_element_types[] = {
                ZigLLVMCreateDebugMemberType(g->dbuilder, ZigLLVMTypeToScope(entry->di_type),
                        "len", di_file, line,
                        len_debug_size_in_bits,
                        len_debug_align_in_bits,
                        len_offset_in_bits,
                        0, usize_type->di_type),
            };
            ZigLLVMDIType *replacement_di_type = ZigLLVMCreateDebugStructType(g->dbuilder,
                    compile_unit_scope,
                    buf_ptr(&entry->name),
                    di_file, line, debug_size_in_bits, debug_align_in_bits, 0,
                    nullptr, di_element_types, 1, 0, nullptr, "");

            ZigLLVMReplaceTemporary(g->dbuilder, entry->di_type, replacement_di_type);
            entry->di_type = replacement_di_type;

            entry->data.structure.abi_alignment = LLVMABIAlignmentOfType(g->target_data_ref, usize_type->type_ref);
        } else {
            unsigned element_count = 2;
            LLVMTypeRef element_types[] = {
                ptr_type->type_ref,
                g->builtin_types.entry_usize->type_ref,
            };
            LLVMStructSetBody(entry->type_ref, element_types, element_count, false);


            uint64_t ptr_debug_size_in_bits = 8*LLVMStoreSizeOfType(g->target_data_ref, ptr_type->type_ref);
            uint64_t ptr_debug_align_in_bits = 8*LLVMABIAlignmentOfType(g->target_data_ref, ptr_type->type_ref);
            uint64_t ptr_offset_in_bits = 8*LLVMOffsetOfElement(g->target_data_ref, entry->type_ref, 0);

            ZigType *usize_type = g->builtin_types.entry_usize;
            uint64_t len_debug_size_in_bits = 8*LLVMStoreSizeOfType(g->target_data_ref, usize_type->type_ref);
            uint64_t len_debug_align_in_bits = 8*LLVMABIAlignmentOfType(g->target_data_ref, usize_type->type_ref);
            uint64_t len_offset_in_bits = 8*LLVMOffsetOfElement(g->target_data_ref, entry->type_ref, 1);

            uint64_t debug_size_in_bits = 8*LLVMStoreSizeOfType(g->target_data_ref, entry->type_ref);
            uint64_t debug_align_in_bits = 8*LLVMABIAlignmentOfType(g->target_data_ref, entry->type_ref);

            ZigLLVMDIType *di_element_types[] = {
                ZigLLVMCreateDebugMemberType(g->dbuilder, ZigLLVMTypeToScope(entry->di_type),
                        "ptr", di_file, line,
                        ptr_debug_size_in_bits,
                        ptr_debug_align_in_bits,
                        ptr_offset_in_bits,
                        0, ptr_type->di_type),
                ZigLLVMCreateDebugMemberType(g->dbuilder, ZigLLVMTypeToScope(entry->di_type),
                        "len", di_file, line,
                        len_debug_size_in_bits,
                        len_debug_align_in_bits,
                        len_offset_in_bits,
                        0, usize_type->di_type),
            };
            ZigLLVMDIType *replacement_di_type = ZigLLVMCreateDebugStructType(g->dbuilder,
                    compile_unit_scope,
                    buf_ptr(&entry->name),
                    di_file, line, debug_size_in_bits, debug_align_in_bits, 0,
                    nullptr, di_element_types, 2, 0, nullptr, "");

            ZigLLVMReplaceTemporary(g->dbuilder, entry->di_type, replacement_di_type);
            entry->di_type = replacement_di_type;

            entry->data.structure.abi_alignment = LLVMABIAlignmentOfType(g->target_data_ref, entry->type_ref);
        }
    }


    entry->data.structure.resolve_status = ResolveStatusSizeKnown;

    *parent_pointer = entry;
    return entry;
}

ZigType *get_opaque_type(CodeGen *g, Scope *scope, AstNode *source_node, const char *name) {
    ZigType *entry = new_type_table_entry(ZigTypeIdOpaque);

    buf_init_from_str(&entry->name, name);

    ImportTableEntry *import = scope ? get_scope_import(scope) : nullptr;
    unsigned line = source_node ? (unsigned)(source_node->line + 1) : 0;

    entry->type_ref = LLVMInt8Type();
    entry->di_type = ZigLLVMCreateDebugForwardDeclType(g->dbuilder,
        ZigLLVMTag_DW_structure_type(), buf_ptr(&entry->name),
        import ? ZigLLVMFileToScope(import->di_file) : nullptr,
        import ? import->di_file : nullptr,
        line);
    entry->zero_bits = false;

    return entry;
}

ZigType *get_bound_fn_type(CodeGen *g, ZigFn *fn_entry) {
    ZigType *fn_type = fn_entry->type_entry;
    assert(fn_type->id == ZigTypeIdFn);
    if (fn_type->data.fn.bound_fn_parent)
        return fn_type->data.fn.bound_fn_parent;

    ZigType *bound_fn_type = new_type_table_entry(ZigTypeIdBoundFn);
    bound_fn_type->data.bound_fn.fn_type = fn_type;
    bound_fn_type->zero_bits = true;

    buf_resize(&bound_fn_type->name, 0);
    buf_appendf(&bound_fn_type->name, "(bound %s)", buf_ptr(&fn_type->name));

    fn_type->data.fn.bound_fn_parent = bound_fn_type;
    return bound_fn_type;
}

const char *calling_convention_name(CallingConvention cc) {
    switch (cc) {
        case CallingConventionUnspecified: return "undefined";
        case CallingConventionC: return "ccc";
        case CallingConventionCold: return "coldcc";
        case CallingConventionNaked: return "nakedcc";
        case CallingConventionStdcall: return "stdcallcc";
        case CallingConventionAsync: return "async";
    }
    zig_unreachable();
}

static const char *calling_convention_fn_type_str(CallingConvention cc) {
    switch (cc) {
        case CallingConventionUnspecified: return "";
        case CallingConventionC: return "extern ";
        case CallingConventionCold: return "coldcc ";
        case CallingConventionNaked: return "nakedcc ";
        case CallingConventionStdcall: return "stdcallcc ";
        case CallingConventionAsync: return "async ";
    }
    zig_unreachable();
}

bool calling_convention_allows_zig_types(CallingConvention cc) {
    switch (cc) {
        case CallingConventionUnspecified:
        case CallingConventionAsync:
            return true;
        case CallingConventionC:
        case CallingConventionCold:
        case CallingConventionNaked:
        case CallingConventionStdcall:
            return false;
    }
    zig_unreachable();
}

ZigType *get_ptr_to_stack_trace_type(CodeGen *g) {
    if (g->stack_trace_type == nullptr) {
        ConstExprValue *stack_trace_type_val = get_builtin_value(g, "StackTrace");
        assert(stack_trace_type_val->type->id == ZigTypeIdMetaType);

        g->stack_trace_type = stack_trace_type_val->data.x_type;
        assertNoError(type_resolve(g, g->stack_trace_type, ResolveStatusZeroBitsKnown));

        g->ptr_to_stack_trace_type = get_pointer_to_type(g, g->stack_trace_type, false);
    }
    return g->ptr_to_stack_trace_type;
}

bool want_first_arg_sret(CodeGen *g, FnTypeId *fn_type_id) {
    if (fn_type_id->cc == CallingConventionUnspecified) {
        return handle_is_ptr(fn_type_id->return_type);
    }
    if (fn_type_id->cc != CallingConventionC) {
        return false;
    }
    if (type_is_c_abi_int(g, fn_type_id->return_type)) {
        return false;
    }
    if (g->zig_target->arch == ZigLLVM_x86_64) {
        X64CABIClass abi_class = type_c_abi_x86_64_class(g, fn_type_id->return_type);
        return abi_class == X64CABIClass_MEMORY;
    } else if (target_is_arm(g->zig_target)) {
        return type_size(g, fn_type_id->return_type) > 16;
    }
    zig_panic("TODO implement C ABI for this architecture. See https://github.com/ziglang/zig/issues/1481");
}

ZigType *get_fn_type(CodeGen *g, FnTypeId *fn_type_id) {
    Error err;
    auto table_entry = g->fn_type_table.maybe_get(fn_type_id);
    if (table_entry) {
        return table_entry->value;
    }
    if (fn_type_id->return_type != nullptr) {
        if ((err = ensure_complete_type(g, fn_type_id->return_type)))
            return g->builtin_types.entry_invalid;
        assert(fn_type_id->return_type->id != ZigTypeIdOpaque);
    } else {
        zig_panic("TODO implement inferred return types https://github.com/ziglang/zig/issues/447");
    }

    ZigType *fn_type = new_type_table_entry(ZigTypeIdFn);
    fn_type->data.fn.fn_type_id = *fn_type_id;

    bool skip_debug_info = false;

    // populate the name of the type
    buf_resize(&fn_type->name, 0);
    if (fn_type->data.fn.fn_type_id.cc == CallingConventionAsync) {
        assert(fn_type_id->async_allocator_type != nullptr);
        buf_appendf(&fn_type->name, "async<%s> ", buf_ptr(&fn_type_id->async_allocator_type->name));
    } else {
        const char *cc_str = calling_convention_fn_type_str(fn_type->data.fn.fn_type_id.cc);
        buf_appendf(&fn_type->name, "%s", cc_str);
    }
    buf_appendf(&fn_type->name, "fn(");
    for (size_t i = 0; i < fn_type_id->param_count; i += 1) {
        FnTypeParamInfo *param_info = &fn_type_id->param_info[i];

        ZigType *param_type = param_info->type;
        const char *comma = (i == 0) ? "" : ", ";
        const char *noalias_str = param_info->is_noalias ? "noalias " : "";
        buf_appendf(&fn_type->name, "%s%s%s", comma, noalias_str, buf_ptr(&param_type->name));

        skip_debug_info = skip_debug_info || !param_type->di_type;
    }

    if (fn_type_id->is_var_args) {
        const char *comma = (fn_type_id->param_count == 0) ? "" : ", ";
        buf_appendf(&fn_type->name, "%s...", comma);
    }
    buf_appendf(&fn_type->name, ")");
    if (fn_type_id->alignment != 0) {
        buf_appendf(&fn_type->name, " align(%" PRIu32 ")", fn_type_id->alignment);
    }
    buf_appendf(&fn_type->name, " %s", buf_ptr(&fn_type_id->return_type->name));
    skip_debug_info = skip_debug_info || !fn_type_id->return_type->di_type;

    // next, loop over the parameters again and compute debug information
    // and codegen information
    if (!skip_debug_info) {
        bool first_arg_return = want_first_arg_sret(g, fn_type_id);
        bool is_async = fn_type_id->cc == CallingConventionAsync;
        bool is_c_abi = fn_type_id->cc == CallingConventionC;
        bool prefix_arg_error_return_trace = g->have_err_ret_tracing && fn_type_can_fail(fn_type_id);
        // +1 for maybe making the first argument the return value
        // +1 for maybe first argument the error return trace
        // +2 for maybe arguments async allocator and error code pointer
        ZigList<LLVMTypeRef> gen_param_types = {};
        // +1 because 0 is the return type and
        // +1 for maybe making first arg ret val and
        // +1 for maybe first argument the error return trace
        // +2 for maybe arguments async allocator and error code pointer
        ZigList<ZigLLVMDIType *> param_di_types = {};
        param_di_types.append(fn_type_id->return_type->di_type);
        ZigType *gen_return_type;
        if (is_async) {
            gen_return_type = get_pointer_to_type(g, g->builtin_types.entry_u8, false);
        } else if (!type_has_bits(fn_type_id->return_type)) {
            gen_return_type = g->builtin_types.entry_void;
        } else if (first_arg_return) {
            ZigType *gen_type = get_pointer_to_type(g, fn_type_id->return_type, false);
            gen_param_types.append(gen_type->type_ref);
            param_di_types.append(gen_type->di_type);
            gen_return_type = g->builtin_types.entry_void;
        } else {
            gen_return_type = fn_type_id->return_type;
        }
        fn_type->data.fn.gen_return_type = gen_return_type;

        if (prefix_arg_error_return_trace) {
            ZigType *gen_type = get_ptr_to_stack_trace_type(g);
            gen_param_types.append(gen_type->type_ref);
            param_di_types.append(gen_type->di_type);
        }
        if (is_async) {
            {
                // async allocator param
                ZigType *gen_type = fn_type_id->async_allocator_type;
                gen_param_types.append(gen_type->type_ref);
                param_di_types.append(gen_type->di_type);
            }

            {
                // error code pointer
                ZigType *gen_type = get_pointer_to_type(g, g->builtin_types.entry_global_error_set, false);
                gen_param_types.append(gen_type->type_ref);
                param_di_types.append(gen_type->di_type);
            }
        }

        fn_type->data.fn.gen_param_info = allocate<FnGenParamInfo>(fn_type_id->param_count);
        for (size_t i = 0; i < fn_type_id->param_count; i += 1) {
            FnTypeParamInfo *src_param_info = &fn_type->data.fn.fn_type_id.param_info[i];
            ZigType *type_entry = src_param_info->type;
            FnGenParamInfo *gen_param_info = &fn_type->data.fn.gen_param_info[i];

            gen_param_info->src_index = i;
            gen_param_info->gen_index = SIZE_MAX;

            if ((err = type_resolve(g, type_entry, ResolveStatusZeroBitsKnown)))
                return g->builtin_types.entry_invalid;

            if (is_c_abi) {
                if ((err = type_resolve(g, type_entry, ResolveStatusSizeKnown)))
                    return g->builtin_types.entry_invalid;
                continue;
            }

            if (type_has_bits(type_entry)) {
                ZigType *gen_type;
                if (handle_is_ptr(type_entry)) {
                    gen_type = get_pointer_to_type(g, type_entry, true);
                    gen_param_info->is_byval = true;
                } else {
                    gen_type = type_entry;
                }
                gen_param_info->gen_index = gen_param_types.length;
                gen_param_info->type = gen_type;
                gen_param_types.append(gen_type->type_ref);

                param_di_types.append(gen_type->di_type);
            }
        }

        if (is_c_abi) {
            FnWalk fn_walk = {};
            fn_walk.id = FnWalkIdTypes;
            fn_walk.data.types.param_di_types = &param_di_types;
            fn_walk.data.types.gen_param_types = &gen_param_types;
            walk_function_params(g, fn_type, &fn_walk);
        }

        fn_type->data.fn.gen_param_count = gen_param_types.length;

        for (size_t i = 0; i < gen_param_types.length; i += 1) {
            assert(gen_param_types.items[i] != nullptr);
        }
        fn_type->data.fn.raw_type_ref = LLVMFunctionType(gen_return_type->type_ref,
                gen_param_types.items, (unsigned int)gen_param_types.length, fn_type_id->is_var_args);
        fn_type->type_ref = LLVMPointerType(fn_type->data.fn.raw_type_ref, 0);
        fn_type->data.fn.raw_di_type = ZigLLVMCreateSubroutineType(g->dbuilder, param_di_types.items, (int)param_di_types.length, 0);
        fn_type->di_type = ZigLLVMCreateDebugPointerType(g->dbuilder, fn_type->data.fn.raw_di_type,
                LLVMStoreSizeOfType(g->target_data_ref, fn_type->type_ref),
                LLVMABIAlignmentOfType(g->target_data_ref, fn_type->type_ref), "");
    }

    g->fn_type_table.put(&fn_type->data.fn.fn_type_id, fn_type);

    return fn_type;
}

static ZigTypeId container_to_type(ContainerKind kind) {
    switch (kind) {
        case ContainerKindStruct:
            return ZigTypeIdStruct;
        case ContainerKindEnum:
            return ZigTypeIdEnum;
        case ContainerKindUnion:
            return ZigTypeIdUnion;
    }
    zig_unreachable();
}

ZigType *get_partial_container_type(CodeGen *g, Scope *scope, ContainerKind kind,
        AstNode *decl_node, const char *name, ContainerLayout layout)
{
    ZigTypeId type_id = container_to_type(kind);
    ZigType *entry = new_container_type_entry(g, type_id, decl_node, scope);

    switch (kind) {
        case ContainerKindStruct:
            entry->data.structure.decl_node = decl_node;
            entry->data.structure.layout = layout;
            break;
        case ContainerKindEnum:
            entry->data.enumeration.decl_node = decl_node;
            entry->data.enumeration.layout = layout;
            break;
        case ContainerKindUnion:
            entry->data.unionation.decl_node = decl_node;
            entry->data.unionation.layout = layout;
            break;
    }

    size_t line = decl_node ? decl_node->line : 0;
    unsigned dwarf_kind = ZigLLVMTag_DW_structure_type();

    ImportTableEntry *import = get_scope_import(scope);
    entry->type_ref = LLVMStructCreateNamed(LLVMGetGlobalContext(), name);
    entry->di_type = ZigLLVMCreateReplaceableCompositeType(g->dbuilder,
        dwarf_kind, name,
        ZigLLVMFileToScope(import->di_file), import->di_file, (unsigned)(line + 1));

    buf_init_from_str(&entry->name, name);

    return entry;
}

static ConstExprValue *analyze_const_value(CodeGen *g, Scope *scope, AstNode *node, ZigType *type_entry,
        Buf *type_name)
{
    size_t backward_branch_count = 0;
    return ir_eval_const_value(g, scope, node, type_entry,
            &backward_branch_count, default_backward_branch_quota,
            nullptr, nullptr, node, type_name, nullptr, nullptr);
}

ZigType *analyze_type_expr(CodeGen *g, Scope *scope, AstNode *node) {
    ConstExprValue *result = analyze_const_value(g, scope, node, g->builtin_types.entry_type, nullptr);
    if (type_is_invalid(result->type))
        return g->builtin_types.entry_invalid;

    assert(result->special != ConstValSpecialRuntime);
    return result->data.x_type;
}

ZigType *get_generic_fn_type(CodeGen *g, FnTypeId *fn_type_id) {
    ZigType *fn_type = new_type_table_entry(ZigTypeIdFn);
    buf_resize(&fn_type->name, 0);
    if (fn_type->data.fn.fn_type_id.cc == CallingConventionAsync) {
        const char *async_allocator_type_str = (fn_type->data.fn.fn_type_id.async_allocator_type == nullptr) ?
            "var" : buf_ptr(&fn_type_id->async_allocator_type->name);
        buf_appendf(&fn_type->name, "async(%s) ", async_allocator_type_str);
    } else {
        const char *cc_str = calling_convention_fn_type_str(fn_type->data.fn.fn_type_id.cc);
        buf_appendf(&fn_type->name, "%s", cc_str);
    }
    buf_appendf(&fn_type->name, "fn(");
    size_t i = 0;
    for (; i < fn_type_id->next_param_index; i += 1) {
        const char *comma_str = (i == 0) ? "" : ",";
        buf_appendf(&fn_type->name, "%s%s", comma_str,
            buf_ptr(&fn_type_id->param_info[i].type->name));
    }
    for (; i < fn_type_id->param_count; i += 1) {
        const char *comma_str = (i == 0) ? "" : ",";
        buf_appendf(&fn_type->name, "%svar", comma_str);
    }
    buf_appendf(&fn_type->name, ")var");

    fn_type->data.fn.fn_type_id = *fn_type_id;
    fn_type->data.fn.is_generic = true;
    fn_type->zero_bits = true;
    return fn_type;
}

void init_fn_type_id(FnTypeId *fn_type_id, AstNode *proto_node, size_t param_count_alloc) {
    assert(proto_node->type == NodeTypeFnProto);
    AstNodeFnProto *fn_proto = &proto_node->data.fn_proto;

    if (fn_proto->cc == CallingConventionUnspecified) {
        bool extern_abi = fn_proto->is_extern || fn_proto->is_export;
        fn_type_id->cc = extern_abi ? CallingConventionC : CallingConventionUnspecified;
    } else {
        fn_type_id->cc = fn_proto->cc;
    }

    fn_type_id->param_count = fn_proto->params.length;
    fn_type_id->param_info = allocate<FnTypeParamInfo>(param_count_alloc);
    fn_type_id->next_param_index = 0;
    fn_type_id->is_var_args = fn_proto->is_var_args;
}

static bool analyze_const_align(CodeGen *g, Scope *scope, AstNode *node, uint32_t *result) {
    ConstExprValue *align_result = analyze_const_value(g, scope, node, get_align_amt_type(g), nullptr);
    if (type_is_invalid(align_result->type))
        return false;

    uint32_t align_bytes = bigint_as_unsigned(&align_result->data.x_bigint);
    if (align_bytes == 0) {
        add_node_error(g, node, buf_sprintf("alignment must be >= 1"));
        return false;
    }
    if (!is_power_of_2(align_bytes)) {
        add_node_error(g, node, buf_sprintf("alignment value %" PRIu32 " is not a power of 2", align_bytes));
        return false;
    }

    *result = align_bytes;
    return true;
}

static bool analyze_const_string(CodeGen *g, Scope *scope, AstNode *node, Buf **out_buffer) {
    ZigType *ptr_type = get_pointer_to_type_extra(g, g->builtin_types.entry_u8, true, false,
            PtrLenUnknown, 0, 0, 0);
    ZigType *str_type = get_slice_type(g, ptr_type);
    ConstExprValue *result_val = analyze_const_value(g, scope, node, str_type, nullptr);
    if (type_is_invalid(result_val->type))
        return false;

    ConstExprValue *ptr_field = &result_val->data.x_struct.fields[slice_ptr_index];
    ConstExprValue *len_field = &result_val->data.x_struct.fields[slice_len_index];

    assert(ptr_field->data.x_ptr.special == ConstPtrSpecialBaseArray);
    ConstExprValue *array_val = ptr_field->data.x_ptr.data.base_array.array_val;
    if (array_val->data.x_array.special == ConstArraySpecialBuf) {
        *out_buffer = array_val->data.x_array.data.s_buf;
        return true;
    }
    expand_undef_array(g, array_val);
    size_t len = bigint_as_unsigned(&len_field->data.x_bigint);
    Buf *result = buf_alloc();
    buf_resize(result, len);
    for (size_t i = 0; i < len; i += 1) {
        size_t new_index = ptr_field->data.x_ptr.data.base_array.elem_index + i;
        ConstExprValue *char_val = &array_val->data.x_array.data.s_none.elements[new_index];
        if (char_val->special == ConstValSpecialUndef) {
            add_node_error(g, node, buf_sprintf("use of undefined value"));
            return false;
        }
        uint64_t big_c = bigint_as_unsigned(&char_val->data.x_bigint);
        assert(big_c <= UINT8_MAX);
        uint8_t c = (uint8_t)big_c;
        buf_ptr(result)[i] = c;
    }
    *out_buffer = result;
    return true;
}

static Error emit_error_unless_type_allowed_in_packed_struct(CodeGen *g, ZigType *type_entry,
        AstNode *source_node)
{
    Error err;
    switch (type_entry->id) {
        case ZigTypeIdInvalid:
            zig_unreachable();
        case ZigTypeIdMetaType:
        case ZigTypeIdUnreachable:
        case ZigTypeIdComptimeFloat:
        case ZigTypeIdComptimeInt:
        case ZigTypeIdUndefined:
        case ZigTypeIdNull:
        case ZigTypeIdErrorUnion:
        case ZigTypeIdErrorSet:
        case ZigTypeIdNamespace:
        case ZigTypeIdBoundFn:
        case ZigTypeIdArgTuple:
        case ZigTypeIdOpaque:
        case ZigTypeIdPromise:
            add_node_error(g, source_node,
                    buf_sprintf("type '%s' not allowed in packed struct; no guaranteed in-memory representation",
                        buf_ptr(&type_entry->name)));
            return ErrorSemanticAnalyzeFail;
        case ZigTypeIdVoid:
        case ZigTypeIdBool:
        case ZigTypeIdInt:
        case ZigTypeIdFloat:
        case ZigTypeIdPointer:
        case ZigTypeIdFn:
        case ZigTypeIdVector:
            return ErrorNone;
        case ZigTypeIdArray: {
            ZigType *elem_type = type_entry->data.array.child_type;
            if ((err = emit_error_unless_type_allowed_in_packed_struct(g, elem_type, source_node)))
                return err;
            if (type_size(g, type_entry) * 8 == type_size_bits(g, type_entry))
                return ErrorNone;
            add_node_error(g, source_node,
                buf_sprintf("array of '%s' not allowed in packed struct due to padding bits",
                    buf_ptr(&elem_type->name)));
            return ErrorSemanticAnalyzeFail;
        }
        case ZigTypeIdStruct:
            switch (type_entry->data.structure.layout) {
                case ContainerLayoutPacked:
                case ContainerLayoutExtern:
                    return ErrorNone;
                case ContainerLayoutAuto:
                    add_node_error(g, source_node,
                        buf_sprintf("non-packed, non-extern struct '%s' not allowed in packed struct; no guaranteed in-memory representation",
                            buf_ptr(&type_entry->name)));
                    return ErrorSemanticAnalyzeFail;
            }
            zig_unreachable();
        case ZigTypeIdUnion:
            switch (type_entry->data.unionation.layout) {
                case ContainerLayoutPacked:
                case ContainerLayoutExtern:
                    return ErrorNone;
                case ContainerLayoutAuto:
                    add_node_error(g, source_node,
                        buf_sprintf("non-packed, non-extern union '%s' not allowed in packed struct; no guaranteed in-memory representation",
                            buf_ptr(&type_entry->name)));
                    return ErrorSemanticAnalyzeFail;
            }
            zig_unreachable();
        case ZigTypeIdOptional:
            if (get_codegen_ptr_type(type_entry) != nullptr) {
                return ErrorNone;
            } else {
                add_node_error(g, source_node,
                    buf_sprintf("type '%s' not allowed in packed struct; no guaranteed in-memory representation",
                        buf_ptr(&type_entry->name)));
                return ErrorSemanticAnalyzeFail;
            }
        case ZigTypeIdEnum: {
            AstNode *decl_node = type_entry->data.enumeration.decl_node;
            if (decl_node->data.container_decl.init_arg_expr != nullptr) {
                return ErrorNone;
            }
            ErrorMsg *msg = add_node_error(g, source_node,
                buf_sprintf("type '%s' not allowed in packed struct; no guaranteed in-memory representation",
                    buf_ptr(&type_entry->name)));
            add_error_note(g, msg, decl_node,
                    buf_sprintf("enum declaration does not specify an integer tag type"));
            return ErrorSemanticAnalyzeFail;
        }
    }
    zig_unreachable();
}

bool type_allowed_in_extern(CodeGen *g, ZigType *type_entry) {
    switch (type_entry->id) {
        case ZigTypeIdInvalid:
            zig_unreachable();
        case ZigTypeIdMetaType:
        case ZigTypeIdComptimeFloat:
        case ZigTypeIdComptimeInt:
        case ZigTypeIdUndefined:
        case ZigTypeIdNull:
        case ZigTypeIdErrorUnion:
        case ZigTypeIdErrorSet:
        case ZigTypeIdNamespace:
        case ZigTypeIdBoundFn:
        case ZigTypeIdArgTuple:
        case ZigTypeIdPromise:
        case ZigTypeIdVoid:
            return false;
        case ZigTypeIdOpaque:
        case ZigTypeIdUnreachable:
        case ZigTypeIdBool:
            return true;
        case ZigTypeIdInt:
            switch (type_entry->data.integral.bit_count) {
                case 8:
                case 16:
                case 32:
                case 64:
                case 128:
                    return true;
                default:
                    return false;
            }
        case ZigTypeIdVector:
            return type_allowed_in_extern(g, type_entry->data.vector.elem_type);
        case ZigTypeIdFloat:
            return true;
        case ZigTypeIdArray:
            return type_allowed_in_extern(g, type_entry->data.array.child_type);
        case ZigTypeIdFn:
            return type_entry->data.fn.fn_type_id.cc == CallingConventionC ||
                 type_entry->data.fn.fn_type_id.cc == CallingConventionStdcall;
        case ZigTypeIdPointer:
            if (type_size(g, type_entry) == 0)
                return false;
            return true;
        case ZigTypeIdStruct:
            return type_entry->data.structure.layout == ContainerLayoutExtern || type_entry->data.structure.layout == ContainerLayoutPacked;
        case ZigTypeIdOptional:
            {
                ZigType *child_type = type_entry->data.maybe.child_type;
                if (child_type->id != ZigTypeIdPointer && child_type->id != ZigTypeIdFn) {
                    return false;
                }
                return type_allowed_in_extern(g, child_type);
            }
        case ZigTypeIdEnum:
            return type_entry->data.enumeration.layout == ContainerLayoutExtern || type_entry->data.enumeration.layout == ContainerLayoutPacked;
        case ZigTypeIdUnion:
            return type_entry->data.unionation.layout == ContainerLayoutExtern || type_entry->data.unionation.layout == ContainerLayoutPacked;
    }
    zig_unreachable();
}

ZigType *get_auto_err_set_type(CodeGen *g, ZigFn *fn_entry) {
    ZigType *err_set_type = new_type_table_entry(ZigTypeIdErrorSet);
    buf_resize(&err_set_type->name, 0);
    buf_appendf(&err_set_type->name, "@typeOf(%s).ReturnType.ErrorSet", buf_ptr(&fn_entry->symbol_name));
    err_set_type->type_ref = g->builtin_types.entry_global_error_set->type_ref;
    err_set_type->di_type = g->builtin_types.entry_global_error_set->di_type;
    err_set_type->data.error_set.err_count = 0;
    err_set_type->data.error_set.errors = nullptr;
    err_set_type->data.error_set.infer_fn = fn_entry;

    g->error_di_types.append(&err_set_type->di_type);

    return err_set_type;
}

static ZigType *analyze_fn_type(CodeGen *g, AstNode *proto_node, Scope *child_scope, ZigFn *fn_entry) {
    assert(proto_node->type == NodeTypeFnProto);
    AstNodeFnProto *fn_proto = &proto_node->data.fn_proto;
    Error err;

    FnTypeId fn_type_id = {0};
    init_fn_type_id(&fn_type_id, proto_node, proto_node->data.fn_proto.params.length);

    for (; fn_type_id.next_param_index < fn_type_id.param_count; fn_type_id.next_param_index += 1) {
        AstNode *param_node = fn_proto->params.at(fn_type_id.next_param_index);
        assert(param_node->type == NodeTypeParamDecl);

        bool param_is_comptime = param_node->data.param_decl.is_inline;
        bool param_is_var_args = param_node->data.param_decl.is_var_args;

        if (param_is_comptime) {
            if (!calling_convention_allows_zig_types(fn_type_id.cc)) {
                add_node_error(g, param_node,
                        buf_sprintf("comptime parameter not allowed in function with calling convention '%s'",
                            calling_convention_name(fn_type_id.cc)));
                return g->builtin_types.entry_invalid;
            }
            if (param_node->data.param_decl.type != nullptr) {
                ZigType *type_entry = analyze_type_expr(g, child_scope, param_node->data.param_decl.type);
                if (type_is_invalid(type_entry)) {
                    return g->builtin_types.entry_invalid;
                }
                FnTypeParamInfo *param_info = &fn_type_id.param_info[fn_type_id.next_param_index];
                param_info->type = type_entry;
                param_info->is_noalias = param_node->data.param_decl.is_noalias;
                fn_type_id.next_param_index += 1;
            }

            return get_generic_fn_type(g, &fn_type_id);
        } else if (param_is_var_args) {
            if (fn_type_id.cc == CallingConventionC) {
                fn_type_id.param_count = fn_type_id.next_param_index;
                continue;
            } else if (calling_convention_allows_zig_types(fn_type_id.cc)) {
                return get_generic_fn_type(g, &fn_type_id);
            } else {
                add_node_error(g, param_node,
                        buf_sprintf("var args not allowed in function with calling convention '%s'",
                            calling_convention_name(fn_type_id.cc)));
                return g->builtin_types.entry_invalid;
            }
        } else if (param_node->data.param_decl.var_token != nullptr) {
            if (!calling_convention_allows_zig_types(fn_type_id.cc)) {
                add_node_error(g, param_node,
                        buf_sprintf("parameter of type 'var' not allowed in function with calling convention '%s'",
                            calling_convention_name(fn_type_id.cc)));
                return g->builtin_types.entry_invalid;
            }
            return get_generic_fn_type(g, &fn_type_id);
        }

        ZigType *type_entry = analyze_type_expr(g, child_scope, param_node->data.param_decl.type);
        if (type_is_invalid(type_entry)) {
            return g->builtin_types.entry_invalid;
        }
        if (!calling_convention_allows_zig_types(fn_type_id.cc)) {
            if ((err = type_resolve(g, type_entry, ResolveStatusZeroBitsKnown)))
                return g->builtin_types.entry_invalid;
            if (!type_has_bits(type_entry)) {
                add_node_error(g, param_node->data.param_decl.type,
                    buf_sprintf("parameter of type '%s' has 0 bits; not allowed in function with calling convention '%s'",
                        buf_ptr(&type_entry->name), calling_convention_name(fn_type_id.cc)));
                return g->builtin_types.entry_invalid;
            }
        }

        if (!calling_convention_allows_zig_types(fn_type_id.cc) && !type_allowed_in_extern(g, type_entry)) {
            add_node_error(g, param_node->data.param_decl.type,
                    buf_sprintf("parameter of type '%s' not allowed in function with calling convention '%s'",
                        buf_ptr(&type_entry->name),
                        calling_convention_name(fn_type_id.cc)));
            return g->builtin_types.entry_invalid;
        }

        switch (type_entry->id) {
            case ZigTypeIdInvalid:
                zig_unreachable();
            case ZigTypeIdUnreachable:
            case ZigTypeIdUndefined:
            case ZigTypeIdNull:
            case ZigTypeIdArgTuple:
            case ZigTypeIdOpaque:
                add_node_error(g, param_node->data.param_decl.type,
                    buf_sprintf("parameter of type '%s' not allowed", buf_ptr(&type_entry->name)));
                return g->builtin_types.entry_invalid;
            case ZigTypeIdComptimeFloat:
            case ZigTypeIdComptimeInt:
            case ZigTypeIdNamespace:
            case ZigTypeIdBoundFn:
            case ZigTypeIdMetaType:
            case ZigTypeIdVoid:
            case ZigTypeIdBool:
            case ZigTypeIdInt:
            case ZigTypeIdFloat:
            case ZigTypeIdPointer:
            case ZigTypeIdArray:
            case ZigTypeIdStruct:
            case ZigTypeIdOptional:
            case ZigTypeIdErrorUnion:
            case ZigTypeIdErrorSet:
            case ZigTypeIdEnum:
            case ZigTypeIdUnion:
            case ZigTypeIdFn:
            case ZigTypeIdPromise:
            case ZigTypeIdVector:
                switch (type_requires_comptime(g, type_entry)) {
                    case ReqCompTimeNo:
                        break;
                    case ReqCompTimeYes:
                        add_node_error(g, param_node->data.param_decl.type,
                            buf_sprintf("parameter of type '%s' must be declared comptime",
                            buf_ptr(&type_entry->name)));
                        return g->builtin_types.entry_invalid;
                    case ReqCompTimeInvalid:
                        return g->builtin_types.entry_invalid;
                }
                break;
        }
        FnTypeParamInfo *param_info = &fn_type_id.param_info[fn_type_id.next_param_index];
        param_info->type = type_entry;
        param_info->is_noalias = param_node->data.param_decl.is_noalias;
    }

    if (fn_proto->align_expr != nullptr) {
        if (!analyze_const_align(g, child_scope, fn_proto->align_expr, &fn_type_id.alignment)) {
            return g->builtin_types.entry_invalid;
        }
    }

    if (fn_proto->return_var_token != nullptr) {
        if (!calling_convention_allows_zig_types(fn_type_id.cc)) {
            add_node_error(g, fn_proto->return_type,
                buf_sprintf("return type 'var' not allowed in function with calling convention '%s'",
                calling_convention_name(fn_type_id.cc)));
            return g->builtin_types.entry_invalid;
        }
        add_node_error(g, proto_node,
            buf_sprintf("TODO implement inferred return types https://github.com/ziglang/zig/issues/447"));
        return g->builtin_types.entry_invalid;
        //return get_generic_fn_type(g, &fn_type_id);
    }

    ZigType *specified_return_type = analyze_type_expr(g, child_scope, fn_proto->return_type);
    if (type_is_invalid(specified_return_type)) {
        fn_type_id.return_type = g->builtin_types.entry_invalid;
        return g->builtin_types.entry_invalid;
    }

    if (fn_proto->auto_err_set) {
        ZigType *inferred_err_set_type = get_auto_err_set_type(g, fn_entry);
        if ((err = type_resolve(g, specified_return_type, ResolveStatusSizeKnown)))
            return g->builtin_types.entry_invalid;
        fn_type_id.return_type = get_error_union_type(g, inferred_err_set_type, specified_return_type);
    } else {
        fn_type_id.return_type = specified_return_type;
    }

    if (!calling_convention_allows_zig_types(fn_type_id.cc) &&
        fn_type_id.return_type->id != ZigTypeIdVoid &&
        !type_allowed_in_extern(g, fn_type_id.return_type))
    {
        add_node_error(g, fn_proto->return_type,
                buf_sprintf("return type '%s' not allowed in function with calling convention '%s'",
                    buf_ptr(&fn_type_id.return_type->name),
                    calling_convention_name(fn_type_id.cc)));
        return g->builtin_types.entry_invalid;
    }

    switch (fn_type_id.return_type->id) {
        case ZigTypeIdInvalid:
            zig_unreachable();

        case ZigTypeIdUndefined:
        case ZigTypeIdNull:
        case ZigTypeIdArgTuple:
        case ZigTypeIdOpaque:
            add_node_error(g, fn_proto->return_type,
                buf_sprintf("return type '%s' not allowed", buf_ptr(&fn_type_id.return_type->name)));
            return g->builtin_types.entry_invalid;

        case ZigTypeIdComptimeFloat:
        case ZigTypeIdComptimeInt:
        case ZigTypeIdNamespace:
        case ZigTypeIdBoundFn:
        case ZigTypeIdMetaType:
        case ZigTypeIdUnreachable:
        case ZigTypeIdVoid:
        case ZigTypeIdBool:
        case ZigTypeIdInt:
        case ZigTypeIdFloat:
        case ZigTypeIdPointer:
        case ZigTypeIdArray:
        case ZigTypeIdStruct:
        case ZigTypeIdOptional:
        case ZigTypeIdErrorUnion:
        case ZigTypeIdErrorSet:
        case ZigTypeIdEnum:
        case ZigTypeIdUnion:
        case ZigTypeIdFn:
        case ZigTypeIdPromise:
        case ZigTypeIdVector:
            switch (type_requires_comptime(g, fn_type_id.return_type)) {
                case ReqCompTimeInvalid:
                    return g->builtin_types.entry_invalid;
                case ReqCompTimeYes:
                    return get_generic_fn_type(g, &fn_type_id);
                case ReqCompTimeNo:
                    break;
            }
            break;
    }

    if (fn_type_id.cc == CallingConventionAsync) {
        if (fn_proto->async_allocator_type == nullptr) {
            return get_generic_fn_type(g, &fn_type_id);
        }
        fn_type_id.async_allocator_type = analyze_type_expr(g, child_scope, fn_proto->async_allocator_type);
        if (type_is_invalid(fn_type_id.async_allocator_type)) {
            return g->builtin_types.entry_invalid;
        }
    }

    return get_fn_type(g, &fn_type_id);
}

bool type_is_invalid(ZigType *type_entry) {
    switch (type_entry->id) {
        case ZigTypeIdInvalid:
            return true;
        case ZigTypeIdStruct:
            return type_entry->data.structure.resolve_status == ResolveStatusInvalid;
        case ZigTypeIdEnum:
            return type_entry->data.enumeration.is_invalid;
        case ZigTypeIdUnion:
            return type_entry->data.unionation.is_invalid;
        default:
            return false;
    }
    zig_unreachable();
}


static Error resolve_enum_type(CodeGen *g, ZigType *enum_type) {
    assert(enum_type->id == ZigTypeIdEnum);

    if (enum_type->data.enumeration.is_invalid)
        return ErrorSemanticAnalyzeFail;

    if (enum_type->data.enumeration.complete)
        return ErrorNone;

    Error err;
    if ((err = resolve_enum_zero_bits(g, enum_type)))
        return err;

    AstNode *decl_node = enum_type->data.enumeration.decl_node;

    if (enum_type->data.enumeration.embedded_in_current) {
        if (!enum_type->data.enumeration.reported_infinite_err) {
            enum_type->data.enumeration.is_invalid = true;
            enum_type->data.enumeration.reported_infinite_err = true;
            add_node_error(g, decl_node, buf_sprintf("enum '%s' contains itself", buf_ptr(&enum_type->name)));
        }
        return ErrorSemanticAnalyzeFail;
    }

    assert(!enum_type->data.enumeration.zero_bits_loop_flag);
    assert(decl_node->type == NodeTypeContainerDecl);
    assert(enum_type->di_type);

    uint32_t field_count = enum_type->data.enumeration.src_field_count;

    assert(enum_type->data.enumeration.fields);
    ZigLLVMDIEnumerator **di_enumerators = allocate<ZigLLVMDIEnumerator*>(field_count);

    Scope *scope = &enum_type->data.enumeration.decls_scope->base;
    ImportTableEntry *import = get_scope_import(scope);

    // set temporary flag
    enum_type->data.enumeration.embedded_in_current = true;

    for (uint32_t i = 0; i < field_count; i += 1) {
        TypeEnumField *enum_field = &enum_type->data.enumeration.fields[i];

        // TODO send patch to LLVM to support APInt in createEnumerator instead of int64_t
        // http://lists.llvm.org/pipermail/llvm-dev/2017-December/119456.html
        di_enumerators[i] = ZigLLVMCreateDebugEnumerator(g->dbuilder, buf_ptr(enum_field->name),
                bigint_as_signed(&enum_field->value));
    }

    // unset temporary flag
    enum_type->data.enumeration.embedded_in_current = false;
    enum_type->data.enumeration.complete = true;

    if (enum_type->data.enumeration.is_invalid)
        return ErrorSemanticAnalyzeFail;

    if (enum_type->zero_bits) {
        enum_type->type_ref = LLVMVoidType();

        uint64_t debug_size_in_bits = 0;
        uint64_t debug_align_in_bits = 0;
        ZigLLVMDIType **di_root_members = nullptr;
        size_t debug_member_count = 0;
        ZigLLVMDIType *replacement_di_type = ZigLLVMCreateDebugStructType(g->dbuilder,
                ZigLLVMFileToScope(import->di_file),
                buf_ptr(&enum_type->name),
                import->di_file, (unsigned)(decl_node->line + 1),
                debug_size_in_bits,
                debug_align_in_bits,
                0, nullptr, di_root_members, (int)debug_member_count, 0, nullptr, "");

        ZigLLVMReplaceTemporary(g->dbuilder, enum_type->di_type, replacement_di_type);
        enum_type->di_type = replacement_di_type;
        return ErrorNone;
    }

    ZigType *tag_int_type = enum_type->data.enumeration.tag_int_type;

    // create debug type for tag
    uint64_t tag_debug_size_in_bits = 8*LLVMStoreSizeOfType(g->target_data_ref, tag_int_type->type_ref);
    uint64_t tag_debug_align_in_bits = 8*LLVMABIAlignmentOfType(g->target_data_ref, tag_int_type->type_ref);
    ZigLLVMDIType *tag_di_type = ZigLLVMCreateDebugEnumerationType(g->dbuilder,
            ZigLLVMFileToScope(import->di_file), buf_ptr(&enum_type->name),
            import->di_file, (unsigned)(decl_node->line + 1),
            tag_debug_size_in_bits,
            tag_debug_align_in_bits,
            di_enumerators, field_count,
            tag_int_type->di_type, "");

    ZigLLVMReplaceTemporary(g->dbuilder, enum_type->di_type, tag_di_type);
    enum_type->di_type = tag_di_type;
    return ErrorNone;
}


ZigType *get_struct_type(CodeGen *g, const char *type_name, const char *field_names[],
        ZigType *field_types[], size_t field_count)
{
    ZigType *struct_type = new_type_table_entry(ZigTypeIdStruct);

    buf_init_from_str(&struct_type->name, type_name);

    struct_type->data.structure.src_field_count = field_count;
    struct_type->data.structure.gen_field_count = 0;
    struct_type->data.structure.resolve_status = ResolveStatusSizeKnown;
    struct_type->data.structure.fields = allocate<TypeStructField>(field_count);
    struct_type->data.structure.fields_by_name.init(field_count);

    ZigLLVMDIType **di_element_types = allocate<ZigLLVMDIType*>(field_count);
    LLVMTypeRef *element_types = allocate<LLVMTypeRef>(field_count);
    for (size_t i = 0; i < field_count; i += 1) {
        element_types[struct_type->data.structure.gen_field_count] = field_types[i]->type_ref;

        TypeStructField *field = &struct_type->data.structure.fields[i];
        field->name = buf_create_from_str(field_names[i]);
        field->type_entry = field_types[i];
        field->src_index = i;

        if (type_has_bits(field->type_entry)) {
            field->gen_index = struct_type->data.structure.gen_field_count;
            struct_type->data.structure.gen_field_count += 1;
        } else {
            field->gen_index = SIZE_MAX;
        }

        auto prev_entry = struct_type->data.structure.fields_by_name.put_unique(field->name, field);
        assert(prev_entry == nullptr);
    }

    struct_type->type_ref = LLVMStructCreateNamed(LLVMGetGlobalContext(), type_name);
    LLVMStructSetBody(struct_type->type_ref, element_types, struct_type->data.structure.gen_field_count, false);

    struct_type->di_type = ZigLLVMCreateReplaceableCompositeType(g->dbuilder,
        ZigLLVMTag_DW_structure_type(), type_name,
        ZigLLVMCompileUnitToScope(g->compile_unit), nullptr, 0);

    for (size_t i = 0; i < field_count; i += 1) {
        TypeStructField *type_struct_field = &struct_type->data.structure.fields[i];
        if (type_struct_field->gen_index == SIZE_MAX) {
            continue;
        }
        ZigType *field_type = type_struct_field->type_entry;
        uint64_t debug_size_in_bits = 8*LLVMStoreSizeOfType(g->target_data_ref, field_type->type_ref);
        uint64_t debug_align_in_bits = 8*LLVMABIAlignmentOfType(g->target_data_ref, field_type->type_ref);
        uint64_t debug_offset_in_bits = 8*LLVMOffsetOfElement(g->target_data_ref, struct_type->type_ref, type_struct_field->gen_index);
        di_element_types[type_struct_field->gen_index] = ZigLLVMCreateDebugMemberType(g->dbuilder,
                ZigLLVMTypeToScope(struct_type->di_type), buf_ptr(type_struct_field->name),
                nullptr, 0,
                debug_size_in_bits,
                debug_align_in_bits,
                debug_offset_in_bits,
                0, field_type->di_type);

        assert(di_element_types[type_struct_field->gen_index]);
    }

    uint64_t debug_size_in_bits = 8*LLVMStoreSizeOfType(g->target_data_ref, struct_type->type_ref);
    uint64_t debug_align_in_bits = 8*LLVMABISizeOfType(g->target_data_ref, struct_type->type_ref);
    ZigLLVMDIType *replacement_di_type = ZigLLVMCreateDebugStructType(g->dbuilder,
            ZigLLVMCompileUnitToScope(g->compile_unit),
            type_name, nullptr, 0,
            debug_size_in_bits,
            debug_align_in_bits,
            0,
            nullptr, di_element_types, struct_type->data.structure.gen_field_count, 0, nullptr, "");

    ZigLLVMReplaceTemporary(g->dbuilder, struct_type->di_type, replacement_di_type);
    struct_type->di_type = replacement_di_type;
    struct_type->data.structure.abi_alignment = LLVMABIAlignmentOfType(g->target_data_ref, struct_type->type_ref);

    return struct_type;
}

static Error resolve_struct_type(CodeGen *g, ZigType *struct_type) {
    assert(struct_type->id == ZigTypeIdStruct);

    Error err;

    if (struct_type->data.structure.resolve_status == ResolveStatusInvalid)
        return ErrorSemanticAnalyzeFail;
    if (struct_type->data.structure.resolve_status >= ResolveStatusSizeKnown)
        return ErrorNone;

    if ((err = resolve_struct_alignment(g, struct_type)))
        return err;

    AstNode *decl_node = struct_type->data.structure.decl_node;

    if (struct_type->data.structure.resolve_loop_flag) {
        if (struct_type->data.structure.resolve_status != ResolveStatusInvalid) {
            struct_type->data.structure.resolve_status = ResolveStatusInvalid;
            add_node_error(g, decl_node,
                buf_sprintf("struct '%s' contains itself", buf_ptr(&struct_type->name)));
        }
        return ErrorSemanticAnalyzeFail;
    }

    struct_type->data.structure.resolve_loop_flag = true;

    assert(struct_type->data.structure.fields || struct_type->data.structure.src_field_count == 0);
    assert(decl_node->type == NodeTypeContainerDecl);

    size_t field_count = struct_type->data.structure.src_field_count;

    size_t gen_field_count = struct_type->data.structure.gen_field_count;
    LLVMTypeRef *element_types = allocate<LLVMTypeRef>(gen_field_count);

    Scope *scope = &struct_type->data.structure.decls_scope->base;

    size_t gen_field_index = 0;
    bool packed = (struct_type->data.structure.layout == ContainerLayoutPacked);
    size_t packed_bits_offset = 0;
    size_t first_packed_bits_offset_misalign = SIZE_MAX;
    size_t debug_field_count = 0;

    for (size_t i = 0; i < field_count; i += 1) {
        TypeStructField *type_struct_field = &struct_type->data.structure.fields[i];
        ZigType *field_type = type_struct_field->type_entry;

        if ((err = ensure_complete_type(g, field_type))) {
            struct_type->data.structure.resolve_status = ResolveStatusInvalid;
            break;
        }

        if (struct_type->data.structure.layout == ContainerLayoutExtern) {
            if (!type_allowed_in_extern(g, field_type)) {
                AstNode *field_source_node = decl_node->data.container_decl.fields.at(i);
                add_node_error(g, field_source_node,
                        buf_sprintf("extern structs cannot contain fields of type '%s'",
                            buf_ptr(&field_type->name)));
                struct_type->data.structure.resolve_status = ResolveStatusInvalid;
                break;
            }
        }

        if (!type_has_bits(field_type))
            continue;

        type_struct_field->gen_index = gen_field_index;

        if (packed) {
            AstNode *field_source_node = decl_node->data.container_decl.fields.at(i);
            if ((err = emit_error_unless_type_allowed_in_packed_struct(g, field_type, field_source_node))) {
                struct_type->data.structure.resolve_status = ResolveStatusInvalid;
                break;
            }

            size_t field_size_in_bits = type_size_bits(g, field_type);
            size_t next_packed_bits_offset = packed_bits_offset + field_size_in_bits;

            if (first_packed_bits_offset_misalign != SIZE_MAX) {
                // this field is not byte-aligned; it is part of the previous field with a bit offset
                type_struct_field->bit_offset_in_host = packed_bits_offset - first_packed_bits_offset_misalign;

                size_t full_bit_count = next_packed_bits_offset - first_packed_bits_offset_misalign;
                LLVMTypeRef int_type_ref = LLVMIntType((unsigned)(full_bit_count));
                if (8 * LLVMStoreSizeOfType(g->target_data_ref, int_type_ref) == full_bit_count) {
                    // next field recovers store alignment
                    element_types[gen_field_index] = int_type_ref;
                    gen_field_index += 1;

                    first_packed_bits_offset_misalign = SIZE_MAX;
                }
            } else if (8 * LLVMStoreSizeOfType(g->target_data_ref, field_type->type_ref) != field_size_in_bits) {
                first_packed_bits_offset_misalign = packed_bits_offset;
                type_struct_field->bit_offset_in_host = 0;
            } else {
                // This is a byte-aligned field (both start and end) in a packed struct.
                element_types[gen_field_index] = field_type->type_ref;
                type_struct_field->bit_offset_in_host = 0;
                gen_field_index += 1;
            }
            packed_bits_offset = next_packed_bits_offset;
        } else {
            element_types[gen_field_index] = field_type->type_ref;
            assert(element_types[gen_field_index]);

            gen_field_index += 1;
        }
        debug_field_count += 1;
    }
    if (first_packed_bits_offset_misalign != SIZE_MAX) {
        size_t full_bit_count = packed_bits_offset - first_packed_bits_offset_misalign;
        LLVMTypeRef int_type_ref = LLVMIntType((unsigned)full_bit_count);
        size_t store_bit_count = 8 * LLVMStoreSizeOfType(g->target_data_ref, int_type_ref);
        element_types[gen_field_index] = LLVMIntType((unsigned)store_bit_count);
        gen_field_index += 1;
    }

    struct_type->data.structure.resolve_loop_flag = false;

    if (struct_type->data.structure.resolve_status == ResolveStatusInvalid)
        return ErrorSemanticAnalyzeFail;

    struct_type->data.structure.resolve_status = ResolveStatusSizeKnown;

    if (struct_type->zero_bits) {
        struct_type->type_ref = LLVMVoidType();

        ImportTableEntry *import = get_scope_import(scope);
        uint64_t debug_size_in_bits = 0;
        uint64_t debug_align_in_bits = 0;
        ZigLLVMDIType **di_element_types = nullptr;
        size_t debug_field_count = 0;
        ZigLLVMDIType *replacement_di_type = ZigLLVMCreateDebugStructType(g->dbuilder,
                ZigLLVMFileToScope(import->di_file),
                buf_ptr(&struct_type->name),
                import->di_file, (unsigned)(decl_node->line + 1),
                debug_size_in_bits,
                debug_align_in_bits,
                0, nullptr, di_element_types, (int)debug_field_count, 0, nullptr, "");
        ZigLLVMReplaceTemporary(g->dbuilder, struct_type->di_type, replacement_di_type);
        struct_type->di_type = replacement_di_type;
        return ErrorNone;
    }
    assert(struct_type->di_type);


    // the count may have been adjusting from packing bit fields
    gen_field_count = gen_field_index;
    struct_type->data.structure.gen_field_count = (uint32_t)gen_field_count;

    LLVMStructSetBody(struct_type->type_ref, element_types, (unsigned)gen_field_count, packed);

    // if you hit this assert then probably this type or a related type didn't
    // get ensure_complete_type called on it before using it with something that
    // requires a complete type
    assert(LLVMStoreSizeOfType(g->target_data_ref, struct_type->type_ref) > 0);

    ZigLLVMDIType **di_element_types = allocate<ZigLLVMDIType*>(debug_field_count);

    ImportTableEntry *import = get_scope_import(scope);
    size_t debug_field_index = 0;
    for (size_t i = 0; i < field_count; i += 1) {
        AstNode *field_node = decl_node->data.container_decl.fields.at(i);
        TypeStructField *type_struct_field = &struct_type->data.structure.fields[i];
        size_t gen_field_index = type_struct_field->gen_index;
        if (gen_field_index == SIZE_MAX) {
            continue;
        }

        ZigType *field_type = type_struct_field->type_entry;

        // if the field is a function, actually the debug info should be a pointer.
        ZigLLVMDIType *field_di_type;
        if (field_type->id == ZigTypeIdFn) {
            ZigType *field_ptr_type = get_pointer_to_type(g, field_type, true);
            uint64_t debug_size_in_bits = 8*LLVMStoreSizeOfType(g->target_data_ref, field_ptr_type->type_ref);
            uint64_t debug_align_in_bits = 8*LLVMABISizeOfType(g->target_data_ref, field_ptr_type->type_ref);
            field_di_type = ZigLLVMCreateDebugPointerType(g->dbuilder, field_type->di_type,
                    debug_size_in_bits, debug_align_in_bits, buf_ptr(&field_ptr_type->name));
        } else {
            field_di_type = field_type->di_type;
        }

        assert(field_type->type_ref);
        assert(struct_type->type_ref);
        assert(struct_type->data.structure.resolve_status == ResolveStatusSizeKnown);
        uint64_t debug_size_in_bits;
        uint64_t debug_align_in_bits;
        uint64_t debug_offset_in_bits;
        if (packed) {
            debug_size_in_bits = type_size_bits(g, type_struct_field->type_entry);
            debug_align_in_bits = 1;
            debug_offset_in_bits = 8*LLVMOffsetOfElement(g->target_data_ref, struct_type->type_ref,
                    (unsigned)gen_field_index) + type_struct_field->bit_offset_in_host;
        } else {
            debug_size_in_bits = 8*LLVMStoreSizeOfType(g->target_data_ref, field_type->type_ref);
            debug_align_in_bits = 8*LLVMABISizeOfType(g->target_data_ref, field_type->type_ref);
            debug_offset_in_bits = 8*LLVMOffsetOfElement(g->target_data_ref, struct_type->type_ref,
                    (unsigned)gen_field_index);
        }
        di_element_types[debug_field_index] = ZigLLVMCreateDebugMemberType(g->dbuilder,
                ZigLLVMTypeToScope(struct_type->di_type), buf_ptr(type_struct_field->name),
                import->di_file, (unsigned)(field_node->line + 1),
                debug_size_in_bits,
                debug_align_in_bits,
                debug_offset_in_bits,
                0, field_di_type);
        assert(di_element_types[debug_field_index]);
        debug_field_index += 1;
    }


    uint64_t debug_size_in_bits = 8*LLVMStoreSizeOfType(g->target_data_ref, struct_type->type_ref);
    uint64_t debug_align_in_bits = 8*LLVMABISizeOfType(g->target_data_ref, struct_type->type_ref);
    ZigLLVMDIType *replacement_di_type = ZigLLVMCreateDebugStructType(g->dbuilder,
            ZigLLVMFileToScope(import->di_file),
            buf_ptr(&struct_type->name),
            import->di_file, (unsigned)(decl_node->line + 1),
            debug_size_in_bits,
            debug_align_in_bits,
            0, nullptr, di_element_types, (int)debug_field_count, 0, nullptr, "");

    ZigLLVMReplaceTemporary(g->dbuilder, struct_type->di_type, replacement_di_type);
    struct_type->di_type = replacement_di_type;

    return ErrorNone;
}

static Error resolve_union_type(CodeGen *g, ZigType *union_type) {
    assert(union_type->id == ZigTypeIdUnion);

    if (union_type->data.unionation.complete)
        return ErrorNone;

    Error err;
    if ((err = resolve_union_zero_bits(g, union_type)))
        return err;

    AstNode *decl_node = union_type->data.unionation.decl_node;

    if (union_type->data.unionation.embedded_in_current) {
        if (!union_type->data.unionation.reported_infinite_err) {
            union_type->data.unionation.reported_infinite_err = true;
            union_type->data.unionation.is_invalid = true;
            add_node_error(g, decl_node, buf_sprintf("union '%s' contains itself", buf_ptr(&union_type->name)));
        }
        return ErrorSemanticAnalyzeFail;
    }

    assert(!union_type->data.unionation.zero_bits_loop_flag);
    assert(decl_node->type == NodeTypeContainerDecl);
    assert(union_type->di_type);

    uint32_t field_count = union_type->data.unionation.src_field_count;

    assert(union_type->data.unionation.fields);

    uint32_t gen_field_count = union_type->data.unionation.gen_field_count;
    ZigLLVMDIType **union_inner_di_types = allocate<ZigLLVMDIType*>(gen_field_count);

    ZigType *most_aligned_union_member = nullptr;
    uint64_t size_of_most_aligned_member_in_bits = 0;
    uint64_t biggest_align_in_bits = 0;
    uint64_t biggest_size_in_bits = 0;

    Scope *scope = &union_type->data.unionation.decls_scope->base;
    ImportTableEntry *import = get_scope_import(scope);

    // set temporary flag
    union_type->data.unionation.embedded_in_current = true;


    for (uint32_t i = 0; i < field_count; i += 1) {
        AstNode *field_node = decl_node->data.container_decl.fields.at(i);
        TypeUnionField *union_field = &union_type->data.unionation.fields[i];
        ZigType *field_type = union_field->type_entry;

        if ((err = ensure_complete_type(g, field_type))) {
            union_type->data.unionation.is_invalid = true;
            continue;
        }

        if (!type_has_bits(field_type))
            continue;

        uint64_t store_size_in_bits = 8*LLVMStoreSizeOfType(g->target_data_ref, field_type->type_ref);
        uint64_t abi_align_in_bits = 8*LLVMABIAlignmentOfType(g->target_data_ref, field_type->type_ref);

        assert(store_size_in_bits > 0);
        assert(abi_align_in_bits > 0);

        union_inner_di_types[union_field->gen_index] = ZigLLVMCreateDebugMemberType(g->dbuilder,
                ZigLLVMTypeToScope(union_type->di_type), buf_ptr(union_field->enum_field->name),
                import->di_file, (unsigned)(field_node->line + 1),
                store_size_in_bits,
                abi_align_in_bits,
                0,
                0, field_type->di_type);

        biggest_size_in_bits = max(biggest_size_in_bits, store_size_in_bits);

        if (!most_aligned_union_member || abi_align_in_bits > biggest_align_in_bits) {
            most_aligned_union_member = field_type;
            biggest_align_in_bits = abi_align_in_bits;
            size_of_most_aligned_member_in_bits = store_size_in_bits;
        }
    }


    // unset temporary flag
    union_type->data.unionation.embedded_in_current = false;
    union_type->data.unionation.complete = true;
    union_type->data.unionation.union_size_bytes = biggest_size_in_bits / 8;
    union_type->data.unionation.most_aligned_union_member = most_aligned_union_member;

    if (union_type->data.unionation.is_invalid)
        return ErrorSemanticAnalyzeFail;

    if (union_type->zero_bits) {
        union_type->type_ref = LLVMVoidType();

        uint64_t debug_size_in_bits = 0;
        uint64_t debug_align_in_bits = 0;
        ZigLLVMDIType **di_root_members = nullptr;
        size_t debug_member_count = 0;
        ZigLLVMDIType *replacement_di_type = ZigLLVMCreateDebugUnionType(g->dbuilder,
                ZigLLVMFileToScope(import->di_file),
                buf_ptr(&union_type->name),
                import->di_file, (unsigned)(decl_node->line + 1),
                debug_size_in_bits,
                debug_align_in_bits,
                0, di_root_members, (int)debug_member_count, 0, "");

        ZigLLVMReplaceTemporary(g->dbuilder, union_type->di_type, replacement_di_type);
        union_type->di_type = replacement_di_type;
        return ErrorNone;
    }

    uint64_t padding_in_bits = biggest_size_in_bits - size_of_most_aligned_member_in_bits;

    ZigType *tag_type = union_type->data.unionation.tag_type;
    if (tag_type == nullptr || tag_type->zero_bits) {
        assert(most_aligned_union_member != nullptr);

        if (padding_in_bits > 0) {
            ZigType *u8_type = get_int_type(g, false, 8);
            ZigType *padding_array = get_array_type(g, u8_type, padding_in_bits / 8);
            LLVMTypeRef union_element_types[] = {
                most_aligned_union_member->type_ref,
                padding_array->type_ref,
            };
            LLVMStructSetBody(union_type->type_ref, union_element_types, 2, false);
        } else {
            LLVMStructSetBody(union_type->type_ref, &most_aligned_union_member->type_ref, 1, false);
        }
        union_type->data.unionation.union_type_ref = union_type->type_ref;
        union_type->data.unionation.gen_tag_index = SIZE_MAX;
        union_type->data.unionation.gen_union_index = SIZE_MAX;

        assert(8*LLVMABIAlignmentOfType(g->target_data_ref, union_type->type_ref) >= biggest_align_in_bits);
        assert(8*LLVMStoreSizeOfType(g->target_data_ref, union_type->type_ref) >= biggest_size_in_bits);

        // create debug type for union
        ZigLLVMDIType *replacement_di_type = ZigLLVMCreateDebugUnionType(g->dbuilder,
            ZigLLVMFileToScope(import->di_file), buf_ptr(&union_type->name),
            import->di_file, (unsigned)(decl_node->line + 1),
            biggest_size_in_bits, biggest_align_in_bits, 0, union_inner_di_types,
            gen_field_count, 0, "");

        ZigLLVMReplaceTemporary(g->dbuilder, union_type->di_type, replacement_di_type);
        union_type->di_type = replacement_di_type;
        return ErrorNone;
    }

    LLVMTypeRef union_type_ref;
    if (padding_in_bits > 0) {
        ZigType *u8_type = get_int_type(g, false, 8);
        ZigType *padding_array = get_array_type(g, u8_type, padding_in_bits / 8);
        LLVMTypeRef union_element_types[] = {
            most_aligned_union_member->type_ref,
            padding_array->type_ref,
        };
        union_type_ref = LLVMStructType(union_element_types, 2, false);
    } else if (most_aligned_union_member == nullptr) {
        union_type->data.unionation.gen_tag_index = SIZE_MAX;
        union_type->data.unionation.gen_union_index = SIZE_MAX;
        union_type->type_ref = tag_type->type_ref;

        ZigLLVMReplaceTemporary(g->dbuilder, union_type->di_type, tag_type->di_type);
        union_type->di_type = tag_type->di_type;
        return ErrorNone;
    } else {
        union_type_ref = most_aligned_union_member->type_ref;
    }
    union_type->data.unionation.union_type_ref = union_type_ref;

    assert(8*LLVMABIAlignmentOfType(g->target_data_ref, union_type_ref) >= biggest_align_in_bits);
    assert(8*LLVMStoreSizeOfType(g->target_data_ref, union_type_ref) >= biggest_size_in_bits);

    // create llvm type for root struct
    ZigType *tag_int_type = tag_type->data.enumeration.tag_int_type;
    uint64_t align_of_tag_in_bits = 8*LLVMABIAlignmentOfType(g->target_data_ref, tag_int_type->type_ref);

    if (align_of_tag_in_bits >= biggest_align_in_bits) {
        union_type->data.unionation.gen_tag_index = 0;
        union_type->data.unionation.gen_union_index = 1;
    } else {
        union_type->data.unionation.gen_union_index = 0;
        union_type->data.unionation.gen_tag_index = 1;
    }

    LLVMTypeRef root_struct_element_types[2];
    root_struct_element_types[union_type->data.unionation.gen_tag_index] = tag_type->type_ref;
    root_struct_element_types[union_type->data.unionation.gen_union_index] = union_type_ref;
    LLVMStructSetBody(union_type->type_ref, root_struct_element_types, 2, false);


    // create debug type for union
    ZigLLVMDIType *union_di_type = ZigLLVMCreateDebugUnionType(g->dbuilder,
            ZigLLVMTypeToScope(union_type->di_type), "AnonUnion",
            import->di_file, (unsigned)(decl_node->line + 1),
            biggest_size_in_bits, biggest_align_in_bits, 0, union_inner_di_types,
            gen_field_count, 0, "");

    uint64_t union_offset_in_bits = 8*LLVMOffsetOfElement(g->target_data_ref, union_type->type_ref,
            union_type->data.unionation.gen_union_index);
    uint64_t tag_offset_in_bits = 8*LLVMOffsetOfElement(g->target_data_ref, union_type->type_ref,
            union_type->data.unionation.gen_tag_index);

    ZigLLVMDIType *union_member_di_type = ZigLLVMCreateDebugMemberType(g->dbuilder,
            ZigLLVMTypeToScope(union_type->di_type), "payload",
            import->di_file, (unsigned)(decl_node->line + 1),
            biggest_size_in_bits,
            biggest_align_in_bits,
            union_offset_in_bits,
            0, union_di_type);

    uint64_t tag_debug_size_in_bits = 8*LLVMStoreSizeOfType(g->target_data_ref, tag_type->type_ref);
    uint64_t tag_debug_align_in_bits = 8*LLVMABIAlignmentOfType(g->target_data_ref, tag_type->type_ref);

    ZigLLVMDIType *tag_member_di_type = ZigLLVMCreateDebugMemberType(g->dbuilder,
            ZigLLVMTypeToScope(union_type->di_type), "tag",
            import->di_file, (unsigned)(decl_node->line + 1),
            tag_debug_size_in_bits,
            tag_debug_align_in_bits,
            tag_offset_in_bits,
            0, tag_type->di_type);

    ZigLLVMDIType *di_root_members[2];
    di_root_members[union_type->data.unionation.gen_tag_index] = tag_member_di_type;
    di_root_members[union_type->data.unionation.gen_union_index] = union_member_di_type;

    uint64_t debug_size_in_bits = 8*LLVMStoreSizeOfType(g->target_data_ref, union_type->type_ref);
    uint64_t debug_align_in_bits = 8*LLVMABISizeOfType(g->target_data_ref, union_type->type_ref);
    ZigLLVMDIType *replacement_di_type = ZigLLVMCreateDebugStructType(g->dbuilder,
            ZigLLVMFileToScope(import->di_file),
            buf_ptr(&union_type->name),
            import->di_file, (unsigned)(decl_node->line + 1),
            debug_size_in_bits,
            debug_align_in_bits,
            0, nullptr, di_root_members, 2, 0, nullptr, "");

    ZigLLVMReplaceTemporary(g->dbuilder, union_type->di_type, replacement_di_type);
    union_type->di_type = replacement_di_type;

    return ErrorNone;
}

static Error resolve_enum_zero_bits(CodeGen *g, ZigType *enum_type) {
    assert(enum_type->id == ZigTypeIdEnum);

    if (enum_type->data.enumeration.zero_bits_known)
        return ErrorNone;

    if (enum_type->data.enumeration.zero_bits_loop_flag) {
        add_node_error(g, enum_type->data.enumeration.decl_node,
            buf_sprintf("'%s' depends on itself", buf_ptr(&enum_type->name)));
        enum_type->data.enumeration.is_invalid = true;
        return ErrorSemanticAnalyzeFail;
    }

    enum_type->data.enumeration.zero_bits_loop_flag = true;

    AstNode *decl_node = enum_type->data.enumeration.decl_node;
    assert(decl_node->type == NodeTypeContainerDecl);
    assert(enum_type->di_type);

    assert(!enum_type->data.enumeration.fields);
    uint32_t field_count = (uint32_t)decl_node->data.container_decl.fields.length;
    if (field_count == 0) {
        add_node_error(g, decl_node, buf_sprintf("enums must have 1 or more fields"));

        enum_type->data.enumeration.src_field_count = field_count;
        enum_type->data.enumeration.fields = nullptr;
        enum_type->data.enumeration.is_invalid = true;
        enum_type->data.enumeration.zero_bits_loop_flag = false;
        enum_type->data.enumeration.zero_bits_known = true;
        return ErrorSemanticAnalyzeFail;
    }

    enum_type->data.enumeration.src_field_count = field_count;
    enum_type->data.enumeration.fields = allocate<TypeEnumField>(field_count);
    enum_type->data.enumeration.fields_by_name.init(field_count);

    Scope *scope = &enum_type->data.enumeration.decls_scope->base;

    HashMap<BigInt, AstNode *, bigint_hash, bigint_eql> occupied_tag_values = {};
    occupied_tag_values.init(field_count);

    ZigType *tag_int_type;
    if (enum_type->data.enumeration.layout == ContainerLayoutExtern) {
        tag_int_type = get_c_int_type(g, CIntTypeInt);
    } else if (enum_type->data.enumeration.layout == ContainerLayoutAuto && field_count == 1) {
        tag_int_type = g->builtin_types.entry_num_lit_int;
    } else {
        tag_int_type = get_smallest_unsigned_int_type(g, field_count - 1);
    }

    // TODO: Are extern enums allowed to have an init_arg_expr?
    if (decl_node->data.container_decl.init_arg_expr != nullptr) {
        ZigType *wanted_tag_int_type = analyze_type_expr(g, scope, decl_node->data.container_decl.init_arg_expr);
        if (type_is_invalid(wanted_tag_int_type)) {
            enum_type->data.enumeration.is_invalid = true;
        } else if (wanted_tag_int_type->id != ZigTypeIdInt) {
            enum_type->data.enumeration.is_invalid = true;
            add_node_error(g, decl_node->data.container_decl.init_arg_expr,
                buf_sprintf("expected integer, found '%s'", buf_ptr(&wanted_tag_int_type->name)));
        } else if (wanted_tag_int_type->data.integral.is_signed) {
            enum_type->data.enumeration.is_invalid = true;
            add_node_error(g, decl_node->data.container_decl.init_arg_expr,
                buf_sprintf("expected unsigned integer, found '%s'", buf_ptr(&wanted_tag_int_type->name)));
        } else if (wanted_tag_int_type->data.integral.bit_count < tag_int_type->data.integral.bit_count) {
            enum_type->data.enumeration.is_invalid = true;
            add_node_error(g, decl_node->data.container_decl.init_arg_expr,
                buf_sprintf("'%s' too small to hold all bits; must be at least '%s'",
                    buf_ptr(&wanted_tag_int_type->name), buf_ptr(&tag_int_type->name)));
        } else {
            tag_int_type = wanted_tag_int_type;
        }
    }
    enum_type->data.enumeration.tag_int_type = tag_int_type;
    enum_type->type_ref = tag_int_type->type_ref;

    for (uint32_t field_i = 0; field_i < field_count; field_i += 1) {
        AstNode *field_node = decl_node->data.container_decl.fields.at(field_i);
        TypeEnumField *type_enum_field = &enum_type->data.enumeration.fields[field_i];
        type_enum_field->name = field_node->data.struct_field.name;
        type_enum_field->decl_index = field_i;
        type_enum_field->decl_node = field_node;

        if (field_node->data.struct_field.type != nullptr) {
            ErrorMsg *msg = add_node_error(g, field_node->data.struct_field.type,
                buf_sprintf("structs and unions, not enums, support field types"));
            add_error_note(g, msg, decl_node,
                    buf_sprintf("consider 'union(enum)' here"));
        }

        auto field_entry = enum_type->data.enumeration.fields_by_name.put_unique(type_enum_field->name, type_enum_field);
        if (field_entry != nullptr) {
            ErrorMsg *msg = add_node_error(g, field_node,
                buf_sprintf("duplicate enum field: '%s'", buf_ptr(type_enum_field->name)));
            add_error_note(g, msg, field_entry->value->decl_node, buf_sprintf("other field here"));
            enum_type->data.enumeration.is_invalid = true;
            continue;
        }

        AstNode *tag_value = field_node->data.struct_field.value;

        // In this first pass we resolve explicit tag values.
        // In a second pass we will fill in the unspecified ones.
        if (tag_value != nullptr) {
            ConstExprValue *result = analyze_const_value(g, scope, tag_value, tag_int_type, nullptr);
            if (type_is_invalid(result->type)) {
                enum_type->data.enumeration.is_invalid = true;
                continue;
            }
            assert(result->special != ConstValSpecialRuntime);
            assert(result->type->id == ZigTypeIdInt ||
                   result->type->id == ZigTypeIdComptimeInt);
            auto entry = occupied_tag_values.put_unique(result->data.x_bigint, tag_value);
            if (entry == nullptr) {
                bigint_init_bigint(&type_enum_field->value, &result->data.x_bigint);
            } else {
                Buf *val_buf = buf_alloc();
                bigint_append_buf(val_buf, &result->data.x_bigint, 10);

                ErrorMsg *msg = add_node_error(g, tag_value,
                        buf_sprintf("enum tag value %s already taken", buf_ptr(val_buf)));
                add_error_note(g, msg, entry->value,
                        buf_sprintf("other occurrence here"));
                enum_type->data.enumeration.is_invalid = true;
                continue;
            }
        }
    }

    // Now iterate again and populate the unspecified tag values
    uint32_t next_maybe_unoccupied_index = 0;

    for (uint32_t field_i = 0; field_i < field_count; field_i += 1) {
        AstNode *field_node = decl_node->data.container_decl.fields.at(field_i);
        TypeEnumField *type_enum_field = &enum_type->data.enumeration.fields[field_i];
        AstNode *tag_value = field_node->data.struct_field.value;

        if (tag_value == nullptr) {
            if (occupied_tag_values.size() == 0) {
                bigint_init_unsigned(&type_enum_field->value, next_maybe_unoccupied_index);
                next_maybe_unoccupied_index += 1;
            } else {
                BigInt proposed_value;
                for (;;) {
                    bigint_init_unsigned(&proposed_value, next_maybe_unoccupied_index);
                    next_maybe_unoccupied_index += 1;
                    auto entry = occupied_tag_values.put_unique(proposed_value, field_node);
                    if (entry != nullptr) {
                        continue;
                    }
                    break;
                }
                bigint_init_bigint(&type_enum_field->value, &proposed_value);
            }
        }
    }

    enum_type->data.enumeration.zero_bits_loop_flag = false;
    enum_type->zero_bits = !type_has_bits(tag_int_type);
    enum_type->data.enumeration.zero_bits_known = true;

    if (enum_type->data.enumeration.is_invalid)
        return ErrorSemanticAnalyzeFail;

    return ErrorNone;
}

static Error resolve_struct_zero_bits(CodeGen *g, ZigType *struct_type) {
    assert(struct_type->id == ZigTypeIdStruct);

    if (struct_type->data.structure.resolve_status == ResolveStatusInvalid)
        return ErrorSemanticAnalyzeFail;
    if (struct_type->data.structure.resolve_status >= ResolveStatusZeroBitsKnown)
        return ErrorNone;

    if (struct_type->data.structure.resolve_loop_flag) {
        struct_type->data.structure.resolve_status = ResolveStatusZeroBitsKnown;
        struct_type->data.structure.resolve_loop_flag = false;
        return ErrorNone;
    }

    struct_type->data.structure.resolve_loop_flag = true;

    AstNode *decl_node = struct_type->data.structure.decl_node;
    assert(decl_node->type == NodeTypeContainerDecl);
    assert(struct_type->di_type);

    assert(!struct_type->data.structure.fields);
    size_t field_count = decl_node->data.container_decl.fields.length;
    struct_type->data.structure.src_field_count = (uint32_t)field_count;
    struct_type->data.structure.fields = allocate<TypeStructField>(field_count);
    struct_type->data.structure.fields_by_name.init(field_count);

    Scope *scope = &struct_type->data.structure.decls_scope->base;

    size_t gen_field_index = 0;
    for (size_t i = 0; i < field_count; i += 1) {
        AstNode *field_node = decl_node->data.container_decl.fields.at(i);
        TypeStructField *type_struct_field = &struct_type->data.structure.fields[i];
        type_struct_field->name = field_node->data.struct_field.name;
        type_struct_field->decl_node = field_node;

        if (field_node->data.struct_field.type == nullptr) {
            add_node_error(g, field_node, buf_sprintf("struct field missing type"));
            struct_type->data.structure.resolve_status = ResolveStatusInvalid;
            continue;
        }

        auto field_entry = struct_type->data.structure.fields_by_name.put_unique(type_struct_field->name, type_struct_field);
        if (field_entry != nullptr) {
            ErrorMsg *msg = add_node_error(g, field_node,
                buf_sprintf("duplicate struct field: '%s'", buf_ptr(type_struct_field->name)));
            add_error_note(g, msg, field_entry->value->decl_node, buf_sprintf("other field here"));
            struct_type->data.structure.resolve_status = ResolveStatusInvalid;
            continue;
        }

        ZigType *field_type = analyze_type_expr(g, scope, field_node->data.struct_field.type);
        type_struct_field->type_entry = field_type;
        type_struct_field->src_index = i;
        type_struct_field->gen_index = SIZE_MAX;

        if (field_node->data.struct_field.value != nullptr) {
            add_node_error(g, field_node->data.struct_field.value,
                    buf_sprintf("enums, not structs, support field assignment"));
        }

        if (field_type->id == ZigTypeIdOpaque) {
            add_node_error(g, field_node->data.struct_field.type,
                buf_sprintf("opaque types have unknown size and therefore cannot be directly embedded in structs"));
            struct_type->data.structure.resolve_status = ResolveStatusInvalid;
            continue;
        }

        switch (type_requires_comptime(g, field_type)) {
            case ReqCompTimeYes:
                struct_type->data.structure.requires_comptime = true;
                break;
            case ReqCompTimeInvalid:
                struct_type->data.structure.resolve_status = ResolveStatusInvalid;
                continue;
            case ReqCompTimeNo:
                break;
        }

        if (!type_has_bits(field_type))
            continue;

        type_struct_field->gen_index = gen_field_index;
        gen_field_index += 1;
    }

    struct_type->data.structure.resolve_loop_flag = false;
    struct_type->data.structure.gen_field_count = (uint32_t)gen_field_index;
    struct_type->zero_bits = (gen_field_index == 0);

    if (struct_type->data.structure.resolve_status == ResolveStatusInvalid)
        return ErrorSemanticAnalyzeFail;

    struct_type->data.structure.resolve_status = ResolveStatusZeroBitsKnown;
    return ErrorNone;
}

static Error resolve_struct_alignment(CodeGen *g, ZigType *struct_type) {
    assert(struct_type->id == ZigTypeIdStruct);

    Error err;

    if (struct_type->data.structure.resolve_status == ResolveStatusInvalid)
        return ErrorSemanticAnalyzeFail;
    if (struct_type->data.structure.resolve_status >= ResolveStatusAlignmentKnown)
        return ErrorNone;

    if ((err = resolve_struct_zero_bits(g, struct_type)))
        return err;

    AstNode *decl_node = struct_type->data.structure.decl_node;

    if (struct_type->data.structure.resolve_loop_flag) {
        if (struct_type->data.structure.resolve_status != ResolveStatusInvalid) {
            struct_type->data.structure.resolve_status = ResolveStatusInvalid;
            add_node_error(g, decl_node,
                buf_sprintf("struct '%s' contains itself", buf_ptr(&struct_type->name)));
        }
        return ErrorSemanticAnalyzeFail;
    }

    struct_type->data.structure.resolve_loop_flag = true;
    assert(decl_node->type == NodeTypeContainerDecl);
    assert(struct_type->di_type);

    size_t field_count = struct_type->data.structure.src_field_count;
    if (struct_type->data.structure.layout == ContainerLayoutPacked) {
        struct_type->data.structure.abi_alignment = 1;
        for (size_t i = 0; i < field_count; i += 1) {
            TypeStructField *field = &struct_type->data.structure.fields[i];
            if (field->type_entry != nullptr && type_is_invalid(field->type_entry)) {
                struct_type->data.structure.resolve_status = ResolveStatusInvalid;
                break;
            }
        }
    } else for (size_t i = 0; i < field_count; i += 1) {
        TypeStructField *field = &struct_type->data.structure.fields[i];
        uint32_t this_field_align;

        // TODO If we have no type_entry for the field, we've already failed to
        // compile the program correctly. This stage1 compiler needs a deeper
        // reworking to make this correct, or we can ignore the problem
        // and make sure it is fixed in stage2. This workaround is for when
        // there is a false positive of a dependency loop, of alignment depending
        // on itself. When this false positive happens we assume a pointer-aligned
        // field, which is usually fine but could be incorrectly over-aligned or
        // even under-aligned. See https://github.com/ziglang/zig/issues/1512
        if (field->type_entry == nullptr) {
            this_field_align = LLVMABIAlignmentOfType(g->target_data_ref, LLVMPointerType(LLVMInt8Type(), 0));
        } else {
            if (type_is_invalid(field->type_entry)) {
                struct_type->data.structure.resolve_status = ResolveStatusInvalid;
                break;
            }

            if (!type_has_bits(field->type_entry))
                continue;

            if ((err = type_resolve(g, field->type_entry, ResolveStatusAlignmentKnown))) {
                struct_type->data.structure.resolve_status = ResolveStatusInvalid;
                break;
            }

            this_field_align = get_abi_alignment(g, field->type_entry);
            assert(this_field_align != 0);
        }
        // alignment of structs is the alignment of the most-aligned field
        if (this_field_align > struct_type->data.structure.abi_alignment) {
            struct_type->data.structure.abi_alignment = this_field_align;
        }
    }

    struct_type->data.structure.resolve_loop_flag = false;

    if (struct_type->data.structure.resolve_status == ResolveStatusInvalid) {
        return ErrorSemanticAnalyzeFail;
    }

    struct_type->data.structure.resolve_status = ResolveStatusAlignmentKnown;
    return ErrorNone;
}

static Error resolve_union_zero_bits(CodeGen *g, ZigType *union_type) {
    assert(union_type->id == ZigTypeIdUnion);

    Error err;

    if (union_type->data.unionation.is_invalid)
        return ErrorSemanticAnalyzeFail;

    if (union_type->data.unionation.zero_bits_known)
        return ErrorNone;

    if (type_is_invalid(union_type))
        return ErrorSemanticAnalyzeFail;

    if (union_type->data.unionation.zero_bits_loop_flag) {
        // If we get here it's due to recursion. From this we conclude that the struct is
        // not zero bits, and if abi_alignment == 0 we further conclude that the first field
        // is a pointer to this very struct, or a function pointer with parameters that
        // reference such a type.
        union_type->data.unionation.zero_bits_known = true;
        union_type->data.unionation.zero_bits_loop_flag = false;
        if (union_type->data.unionation.abi_alignment == 0) {
            if (union_type->data.unionation.layout == ContainerLayoutPacked) {
                union_type->data.unionation.abi_alignment = 1;
            } else {
                union_type->data.unionation.abi_alignment = LLVMABIAlignmentOfType(g->target_data_ref,
                        LLVMPointerType(LLVMInt8Type(), 0));
            }
        }
        return ErrorNone;
    }

    union_type->data.unionation.zero_bits_loop_flag = true;

    AstNode *decl_node = union_type->data.unionation.decl_node;
    assert(decl_node->type == NodeTypeContainerDecl);
    assert(union_type->di_type);

    assert(!union_type->data.unionation.fields);
    uint32_t field_count = (uint32_t)decl_node->data.container_decl.fields.length;
    if (field_count == 0) {
        add_node_error(g, decl_node, buf_sprintf("unions must have 1 or more fields"));

        union_type->data.unionation.src_field_count = field_count;
        union_type->data.unionation.fields = nullptr;
        union_type->data.unionation.is_invalid = true;
        union_type->data.unionation.zero_bits_loop_flag = false;
        union_type->data.unionation.zero_bits_known = true;
        return ErrorSemanticAnalyzeFail;
    }
    union_type->data.unionation.src_field_count = field_count;
    union_type->data.unionation.fields = allocate<TypeUnionField>(field_count);
    union_type->data.unionation.fields_by_name.init(field_count);

    uint32_t biggest_align_bytes = 0;

    Scope *scope = &union_type->data.unionation.decls_scope->base;

    HashMap<BigInt, AstNode *, bigint_hash, bigint_eql> occupied_tag_values = {};

    AstNode *enum_type_node = decl_node->data.container_decl.init_arg_expr;
    union_type->data.unionation.have_explicit_tag_type = decl_node->data.container_decl.auto_enum ||
        enum_type_node != nullptr;
    bool auto_layout = (union_type->data.unionation.layout == ContainerLayoutAuto);
    bool want_safety = (field_count >= 2) && (auto_layout || enum_type_node != nullptr) && !(g->build_mode == BuildModeFastRelease || g->build_mode == BuildModeSmallRelease);
    ZigType *tag_type;
    bool create_enum_type = decl_node->data.container_decl.auto_enum || (enum_type_node == nullptr && want_safety);
    bool *covered_enum_fields;
    ZigLLVMDIEnumerator **di_enumerators;
    uint32_t abi_alignment_so_far;
    if (create_enum_type) {
        occupied_tag_values.init(field_count);

        di_enumerators = allocate<ZigLLVMDIEnumerator*>(field_count);

        ZigType *tag_int_type;
        if (enum_type_node != nullptr) {
            tag_int_type = analyze_type_expr(g, scope, enum_type_node);
            if (type_is_invalid(tag_int_type)) {
                union_type->data.unionation.is_invalid = true;
                return ErrorSemanticAnalyzeFail;
            }
            if (tag_int_type->id != ZigTypeIdInt) {
                add_node_error(g, enum_type_node,
                    buf_sprintf("expected integer tag type, found '%s'", buf_ptr(&tag_int_type->name)));
                union_type->data.unionation.is_invalid = true;
                return ErrorSemanticAnalyzeFail;
            }
        } else if (auto_layout && field_count == 1) {
            tag_int_type = g->builtin_types.entry_num_lit_int;
        } else {
            tag_int_type = get_smallest_unsigned_int_type(g, field_count - 1);
        }
        abi_alignment_so_far = get_abi_alignment(g, tag_int_type);

        tag_type = new_type_table_entry(ZigTypeIdEnum);
        buf_resize(&tag_type->name, 0);
        buf_appendf(&tag_type->name, "@TagType(%s)", buf_ptr(&union_type->name));
        tag_type->type_ref = tag_int_type->type_ref;
        tag_type->zero_bits = tag_int_type->zero_bits;

        tag_type->data.enumeration.tag_int_type = tag_int_type;
        tag_type->data.enumeration.zero_bits_known = true;
        tag_type->data.enumeration.decl_node = decl_node;
        tag_type->data.enumeration.layout = ContainerLayoutAuto;
        tag_type->data.enumeration.src_field_count = field_count;
        tag_type->data.enumeration.fields = allocate<TypeEnumField>(field_count);
        tag_type->data.enumeration.fields_by_name.init(field_count);
        tag_type->data.enumeration.decls_scope = union_type->data.unionation.decls_scope;
        tag_type->data.enumeration.complete = true;
    } else if (enum_type_node != nullptr) {
        ZigType *enum_type = analyze_type_expr(g, scope, enum_type_node);
        if (type_is_invalid(enum_type)) {
            union_type->data.unionation.is_invalid = true;
            return ErrorSemanticAnalyzeFail;
        }
        if (enum_type->id != ZigTypeIdEnum) {
            union_type->data.unionation.is_invalid = true;
            add_node_error(g, enum_type_node,
                buf_sprintf("expected enum tag type, found '%s'", buf_ptr(&enum_type->name)));
            return ErrorSemanticAnalyzeFail;
        }
        if ((err = type_resolve(g, enum_type, ResolveStatusAlignmentKnown))) {
            assert(g->errors.length != 0);
            return err;
        }
        tag_type = enum_type;
        abi_alignment_so_far = get_abi_alignment(g, enum_type); // this populates src_field_count
        covered_enum_fields = allocate<bool>(enum_type->data.enumeration.src_field_count);
    } else {
        tag_type = nullptr;
        abi_alignment_so_far = 0;
    }
    union_type->data.unionation.tag_type = tag_type;

    uint32_t gen_field_index = 0;
    for (uint32_t i = 0; i < field_count; i += 1) {
        AstNode *field_node = decl_node->data.container_decl.fields.at(i);
        Buf *field_name = field_node->data.struct_field.name;
        TypeUnionField *union_field = &union_type->data.unionation.fields[i];
        union_field->name = field_node->data.struct_field.name;
        union_field->decl_node = field_node;

        auto field_entry = union_type->data.unionation.fields_by_name.put_unique(union_field->name, union_field);
        if (field_entry != nullptr) {
            ErrorMsg *msg = add_node_error(g, field_node,
                buf_sprintf("duplicate union field: '%s'", buf_ptr(union_field->name)));
            add_error_note(g, msg, field_entry->value->decl_node, buf_sprintf("other field here"));
            union_type->data.unionation.is_invalid = true;
            continue;
        }

        ZigType *field_type;
        if (field_node->data.struct_field.type == nullptr) {
            if (decl_node->data.container_decl.auto_enum || decl_node->data.container_decl.init_arg_expr != nullptr) {
                field_type = g->builtin_types.entry_void;
            } else {
                add_node_error(g, field_node, buf_sprintf("union field missing type"));
                union_type->data.unionation.is_invalid = true;
                continue;
            }
        } else {
            field_type = analyze_type_expr(g, scope, field_node->data.struct_field.type);
            if ((err = type_resolve(g, field_type, ResolveStatusAlignmentKnown))) {
                union_type->data.unionation.is_invalid = true;
                continue;
            }
        }
        union_field->type_entry = field_type;

        if (field_type->id == ZigTypeIdOpaque) {
            add_node_error(g, field_node->data.struct_field.type,
                buf_sprintf("opaque types have unknown size and therefore cannot be directly embedded in unions"));
            union_type->data.unionation.is_invalid = true;
            continue;
        }

        switch (type_requires_comptime(g, field_type)) {
            case ReqCompTimeInvalid:
                union_type->data.unionation.is_invalid = true;
                continue;
            case ReqCompTimeYes:
                union_type->data.unionation.requires_comptime = true;
                break;
            case ReqCompTimeNo:
                break;
        }

        if (field_node->data.struct_field.value != nullptr && !decl_node->data.container_decl.auto_enum) {
            ErrorMsg *msg = add_node_error(g, field_node->data.struct_field.value,
                    buf_sprintf("non-enum union field assignment"));
            add_error_note(g, msg, decl_node,
                    buf_sprintf("consider 'union(enum)' here"));
        }

        if (create_enum_type) {
            di_enumerators[i] = ZigLLVMCreateDebugEnumerator(g->dbuilder, buf_ptr(field_name), i);
            union_field->enum_field = &tag_type->data.enumeration.fields[i];
            union_field->enum_field->name = field_name;
            union_field->enum_field->decl_index = i;
            union_field->enum_field->decl_node = field_node;

            auto prev_entry = tag_type->data.enumeration.fields_by_name.put_unique(union_field->enum_field->name, union_field->enum_field);
            assert(prev_entry == nullptr); // caught by union de-duplicator above

            AstNode *tag_value = field_node->data.struct_field.value;
            // In this first pass we resolve explicit tag values.
            // In a second pass we will fill in the unspecified ones.
            if (tag_value != nullptr) {
                ZigType *tag_int_type = tag_type->data.enumeration.tag_int_type;
                ConstExprValue *result = analyze_const_value(g, scope, tag_value, tag_int_type, nullptr);
                if (type_is_invalid(result->type)) {
                    union_type->data.unionation.is_invalid = true;
                    continue;
                }
                assert(result->special != ConstValSpecialRuntime);
                assert(result->type->id == ZigTypeIdInt);
                auto entry = occupied_tag_values.put_unique(result->data.x_bigint, tag_value);
                if (entry == nullptr) {
                    bigint_init_bigint(&union_field->enum_field->value, &result->data.x_bigint);
                } else {
                    Buf *val_buf = buf_alloc();
                    bigint_append_buf(val_buf, &result->data.x_bigint, 10);

                    ErrorMsg *msg = add_node_error(g, tag_value,
                            buf_sprintf("enum tag value %s already taken", buf_ptr(val_buf)));
                    add_error_note(g, msg, entry->value,
                            buf_sprintf("other occurrence here"));
                    union_type->data.unionation.is_invalid = true;
                    continue;
                }
            }
        } else if (enum_type_node != nullptr) {
            union_field->enum_field = find_enum_type_field(tag_type, field_name);
            if (union_field->enum_field == nullptr) {
                ErrorMsg *msg = add_node_error(g, field_node,
                    buf_sprintf("enum field not found: '%s'", buf_ptr(field_name)));
                add_error_note(g, msg, tag_type->data.enumeration.decl_node,
                        buf_sprintf("enum declared here"));
                union_type->data.unionation.is_invalid = true;
                continue;
            }
            covered_enum_fields[union_field->enum_field->decl_index] = true;
        } else {
            union_field->enum_field = allocate<TypeEnumField>(1);
            union_field->enum_field->name = field_name;
            union_field->enum_field->decl_index = i;
            bigint_init_unsigned(&union_field->enum_field->value, i);
        }
        assert(union_field->enum_field != nullptr);

        if (!type_has_bits(field_type))
            continue;

        union_field->gen_index = gen_field_index;
        gen_field_index += 1;

        uint32_t field_align_bytes = get_abi_alignment(g, field_type);
        if (field_align_bytes > biggest_align_bytes) {
            biggest_align_bytes = field_align_bytes;
            if (biggest_align_bytes > abi_alignment_so_far) {
                abi_alignment_so_far = biggest_align_bytes;
            }
        }
    }

    union_type->data.unionation.abi_alignment = abi_alignment_so_far;

    if (union_type->data.unionation.is_invalid)
        return ErrorSemanticAnalyzeFail;

    bool src_have_tag = decl_node->data.container_decl.auto_enum ||
        decl_node->data.container_decl.init_arg_expr != nullptr;

    if (src_have_tag && union_type->data.unionation.layout != ContainerLayoutAuto) {
        const char *qual_str;
        switch (union_type->data.unionation.layout) {
            case ContainerLayoutAuto:
                zig_unreachable();
            case ContainerLayoutPacked:
                qual_str = "packed";
                break;
            case ContainerLayoutExtern:
                qual_str = "extern";
                break;
        }
        AstNode *source_node = (decl_node->data.container_decl.init_arg_expr != nullptr) ?
            decl_node->data.container_decl.init_arg_expr : decl_node;
        add_node_error(g, source_node,
            buf_sprintf("%s union does not support enum tag type", qual_str));
        union_type->data.unionation.is_invalid = true;
        return ErrorSemanticAnalyzeFail;
    }

    if (create_enum_type) {
        // Now iterate again and populate the unspecified tag values
        uint32_t next_maybe_unoccupied_index = 0;

        for (uint32_t field_i = 0; field_i < field_count; field_i += 1) {
            AstNode *field_node = decl_node->data.container_decl.fields.at(field_i);
            TypeUnionField *union_field = &union_type->data.unionation.fields[field_i];
            AstNode *tag_value = field_node->data.struct_field.value;

            if (tag_value == nullptr) {
                if (occupied_tag_values.size() == 0) {
                    bigint_init_unsigned(&union_field->enum_field->value, next_maybe_unoccupied_index);
                    next_maybe_unoccupied_index += 1;
                } else {
                    BigInt proposed_value;
                    for (;;) {
                        bigint_init_unsigned(&proposed_value, next_maybe_unoccupied_index);
                        next_maybe_unoccupied_index += 1;
                        auto entry = occupied_tag_values.put_unique(proposed_value, field_node);
                        if (entry != nullptr) {
                            continue;
                        }
                        break;
                    }
                    bigint_init_bigint(&union_field->enum_field->value, &proposed_value);
                }
            }
        }
    } else if (enum_type_node != nullptr) {
        for (uint32_t i = 0; i < tag_type->data.enumeration.src_field_count; i += 1) {
            TypeEnumField *enum_field = &tag_type->data.enumeration.fields[i];
            if (!covered_enum_fields[i]) {
                AstNode *enum_decl_node = tag_type->data.enumeration.decl_node;
                AstNode *field_node = enum_decl_node->data.container_decl.fields.at(i);
                ErrorMsg *msg = add_node_error(g, decl_node,
                    buf_sprintf("enum field missing: '%s'", buf_ptr(enum_field->name)));
                add_error_note(g, msg, field_node,
                        buf_sprintf("declared here"));
                union_type->data.unionation.is_invalid = true;
            }
        }
    }

    if (create_enum_type) {
        ImportTableEntry *import = get_scope_import(scope);
        uint64_t tag_debug_size_in_bits = tag_type->zero_bits ? 0 :
            8*LLVMStoreSizeOfType(g->target_data_ref, tag_type->type_ref);
        uint64_t tag_debug_align_in_bits = tag_type->zero_bits ? 0 :
            8*LLVMABIAlignmentOfType(g->target_data_ref, tag_type->type_ref);
        // TODO get a more accurate debug scope
        ZigLLVMDIType *tag_di_type = ZigLLVMCreateDebugEnumerationType(g->dbuilder,
                ZigLLVMFileToScope(import->di_file), buf_ptr(&tag_type->name),
                import->di_file, (unsigned)(decl_node->line + 1),
                tag_debug_size_in_bits, tag_debug_align_in_bits, di_enumerators, field_count,
                tag_type->di_type, "");
        tag_type->di_type = tag_di_type;
    }

    union_type->data.unionation.zero_bits_loop_flag = false;
    union_type->data.unionation.gen_field_count = gen_field_index;
    union_type->zero_bits = (gen_field_index == 0 && (field_count < 2 || !src_have_tag));
    union_type->data.unionation.zero_bits_known = true;

    if (union_type->data.unionation.is_invalid)
        return ErrorSemanticAnalyzeFail;

    return ErrorNone;
}

static void get_fully_qualified_decl_name_internal(Buf *buf, Scope *scope, uint8_t sep) {
    if (!scope)
        return;

    if (scope->id == ScopeIdDecls) {
        get_fully_qualified_decl_name_internal(buf, scope->parent, sep);

        ScopeDecls *scope_decls = (ScopeDecls *)scope;
        if (scope_decls->container_type) {
            buf_append_buf(buf, &scope_decls->container_type->name);
            buf_append_char(buf, sep);
        }
        return;
    }

    get_fully_qualified_decl_name_internal(buf, scope->parent, sep);
}

static void get_fully_qualified_decl_name(Buf *buf, Tld *tld, uint8_t sep) {
    buf_resize(buf, 0);
    get_fully_qualified_decl_name_internal(buf, tld->parent_scope, sep);
    buf_append_buf(buf, tld->name);
}

ZigFn *create_fn_raw(CodeGen *g, FnInline inline_value) {
    ZigFn *fn_entry = allocate<ZigFn>(1);

    fn_entry->codegen = g;
    fn_entry->analyzed_executable.backward_branch_count = &fn_entry->prealloc_bbc;
    fn_entry->analyzed_executable.backward_branch_quota = default_backward_branch_quota;
    fn_entry->analyzed_executable.fn_entry = fn_entry;
    fn_entry->ir_executable.fn_entry = fn_entry;
    fn_entry->fn_inline = inline_value;

    return fn_entry;
}

ZigFn *create_fn(CodeGen *g, AstNode *proto_node) {
    assert(proto_node->type == NodeTypeFnProto);
    AstNodeFnProto *fn_proto = &proto_node->data.fn_proto;

    FnInline inline_value = fn_proto->is_inline ? FnInlineAlways : FnInlineAuto;
    ZigFn *fn_entry = create_fn_raw(g, inline_value);

    fn_entry->proto_node = proto_node;
    fn_entry->body_node = (proto_node->data.fn_proto.fn_def_node == nullptr) ? nullptr :
        proto_node->data.fn_proto.fn_def_node->data.fn_def.body;

    return fn_entry;
}

static bool scope_is_root_decls(Scope *scope) {
    while (scope) {
        if (scope->id == ScopeIdDecls) {
            ScopeDecls *scope_decls = (ScopeDecls *)scope;
            return (scope_decls->container_type == nullptr);
        }
        scope = scope->parent;
    }
    zig_unreachable();
}

void typecheck_panic_fn(CodeGen *g, TldFn *tld_fn, ZigFn *panic_fn) {
    ConstExprValue *panic_fn_type_val = get_builtin_value(g, "PanicFn");
    assert(panic_fn_type_val != nullptr);
    assert(panic_fn_type_val->type->id == ZigTypeIdMetaType);
    ZigType *panic_fn_type = panic_fn_type_val->data.x_type;

    AstNode *fake_decl = allocate<AstNode>(1);
    *fake_decl = *panic_fn->proto_node;
    fake_decl->type = NodeTypeSymbol;
    fake_decl->data.symbol_expr.symbol = &panic_fn->symbol_name;

    // call this for the side effects of casting to panic_fn_type
    analyze_const_value(g, tld_fn->base.parent_scope, fake_decl, panic_fn_type, nullptr);
}

ZigType *get_test_fn_type(CodeGen *g) {
    if (g->test_fn_type)
        return g->test_fn_type;

    FnTypeId fn_type_id = {0};
    fn_type_id.return_type = get_error_union_type(g, g->builtin_types.entry_global_error_set,
            g->builtin_types.entry_void);
    g->test_fn_type = get_fn_type(g, &fn_type_id);
    return g->test_fn_type;
}

void add_fn_export(CodeGen *g, ZigFn *fn_table_entry, Buf *symbol_name, GlobalLinkageId linkage, bool ccc) {
    if (ccc) {
        if (buf_eql_str(symbol_name, "main") && g->libc_link_lib != nullptr) {
            g->have_c_main = true;
            g->subsystem = TargetSubsystemConsole;
        } else if (buf_eql_str(symbol_name, "WinMain") &&
            g->zig_target->os == OsWindows)
        {
            g->have_winmain = true;
            g->subsystem = TargetSubsystemWindows;
        } else if (buf_eql_str(symbol_name, "WinMainCRTStartup") &&
            g->zig_target->os == OsWindows)
        {
            g->have_winmain_crt_startup = true;
        } else if (buf_eql_str(symbol_name, "DllMainCRTStartup") &&
            g->zig_target->os == OsWindows)
        {
            g->have_dllmain_crt_startup = true;
        }
    }

    FnExport *fn_export = fn_table_entry->export_list.add_one();
    memset(fn_export, 0, sizeof(FnExport));
    buf_init_from_buf(&fn_export->name, symbol_name);
    fn_export->linkage = linkage;
}

static void resolve_decl_fn(CodeGen *g, TldFn *tld_fn) {
    ImportTableEntry *import = tld_fn->base.import;
    AstNode *source_node = tld_fn->base.source_node;
    if (source_node->type == NodeTypeFnProto) {
        AstNodeFnProto *fn_proto = &source_node->data.fn_proto;

        AstNode *fn_def_node = fn_proto->fn_def_node;

        ZigFn *fn_table_entry = create_fn(g, source_node);
        get_fully_qualified_decl_name(&fn_table_entry->symbol_name, &tld_fn->base, '_');

        if (fn_proto->is_export) {
            bool ccc = (fn_proto->cc == CallingConventionUnspecified || fn_proto->cc == CallingConventionC);
            add_fn_export(g, fn_table_entry, &fn_table_entry->symbol_name, GlobalLinkageIdStrong, ccc);
        }

        tld_fn->fn_entry = fn_table_entry;

        if (fn_table_entry->body_node) {
            fn_table_entry->fndef_scope = create_fndef_scope(g,
                fn_table_entry->body_node, tld_fn->base.parent_scope, fn_table_entry);

            for (size_t i = 0; i < fn_proto->params.length; i += 1) {
                AstNode *param_node = fn_proto->params.at(i);
                assert(param_node->type == NodeTypeParamDecl);
                if (param_node->data.param_decl.name == nullptr) {
                    add_node_error(g, param_node, buf_sprintf("missing parameter name"));
                }
            }
        } else {
            g->external_prototypes.put_unique(tld_fn->base.name, &tld_fn->base);
        }

        Scope *child_scope = fn_table_entry->fndef_scope ? &fn_table_entry->fndef_scope->base : tld_fn->base.parent_scope;

        fn_table_entry->type_entry = analyze_fn_type(g, source_node, child_scope, fn_table_entry);

        if (fn_proto->section_expr != nullptr) {
            if (fn_table_entry->body_node == nullptr) {
                add_node_error(g, fn_proto->section_expr,
                    buf_sprintf("cannot set section of external function '%s'", buf_ptr(&fn_table_entry->symbol_name)));
            } else {
                analyze_const_string(g, child_scope, fn_proto->section_expr, &fn_table_entry->section_name);
            }
        }

        if (fn_table_entry->type_entry->id == ZigTypeIdInvalid) {
            tld_fn->base.resolution = TldResolutionInvalid;
            return;
        }

        if (!fn_table_entry->type_entry->data.fn.is_generic) {
            if (fn_def_node)
                g->fn_defs.append(fn_table_entry);
        }

        if (scope_is_root_decls(tld_fn->base.parent_scope) &&
            (import == g->root_import || import->package == g->panic_package))
        {
            if (g->have_pub_main && buf_eql_str(&fn_table_entry->symbol_name, "main")) {
                g->main_fn = fn_table_entry;
            } else if ((import->package == g->panic_package || g->have_pub_panic) &&
                    buf_eql_str(&fn_table_entry->symbol_name, "panic"))
            {
                g->panic_fn = fn_table_entry;
                g->panic_tld_fn = tld_fn;
            }
        }
    } else if (source_node->type == NodeTypeTestDecl) {
        ZigFn *fn_table_entry = create_fn_raw(g, FnInlineAuto);

        get_fully_qualified_decl_name(&fn_table_entry->symbol_name, &tld_fn->base, '_');

        tld_fn->fn_entry = fn_table_entry;

        fn_table_entry->proto_node = source_node;
        fn_table_entry->fndef_scope = create_fndef_scope(g, source_node, tld_fn->base.parent_scope, fn_table_entry);
        fn_table_entry->type_entry = get_test_fn_type(g);
        fn_table_entry->body_node = source_node->data.test_decl.body;
        fn_table_entry->is_test = true;

        g->fn_defs.append(fn_table_entry);
        g->test_fns.append(fn_table_entry);

    } else {
        zig_unreachable();
    }
}

static void resolve_decl_comptime(CodeGen *g, TldCompTime *tld_comptime) {
    assert(tld_comptime->base.source_node->type == NodeTypeCompTime);
    AstNode *expr_node = tld_comptime->base.source_node->data.comptime_expr.expr;
    analyze_const_value(g, tld_comptime->base.parent_scope, expr_node, g->builtin_types.entry_void, nullptr);
}

static void add_top_level_decl(CodeGen *g, ScopeDecls *decls_scope, Tld *tld) {
    bool is_export = false;
    if (tld->id == TldIdVar) {
        assert(tld->source_node->type == NodeTypeVariableDeclaration);
        is_export = tld->source_node->data.variable_declaration.is_export;
    } else if (tld->id == TldIdFn) {
        assert(tld->source_node->type == NodeTypeFnProto);
        is_export = tld->source_node->data.fn_proto.is_export;

        if (!is_export && !tld->source_node->data.fn_proto.is_extern &&
            tld->source_node->data.fn_proto.fn_def_node == nullptr)
        {
            add_node_error(g, tld->source_node, buf_sprintf("non-extern function has no body"));
            return;
        }
    }
    if (is_export) {
        g->resolve_queue.append(tld);

        auto entry = g->exported_symbol_names.put_unique(tld->name, tld);
        if (entry) {
            AstNode *other_source_node = entry->value->source_node;
            ErrorMsg *msg = add_node_error(g, tld->source_node,
                    buf_sprintf("exported symbol collision: '%s'", buf_ptr(tld->name)));
            add_error_note(g, msg, other_source_node, buf_sprintf("other symbol here"));
        }
    }

    {
        auto entry = decls_scope->decl_table.put_unique(tld->name, tld);
        if (entry) {
            Tld *other_tld = entry->value;
            ErrorMsg *msg = add_node_error(g, tld->source_node, buf_sprintf("redefinition of '%s'", buf_ptr(tld->name)));
            add_error_note(g, msg, other_tld->source_node, buf_sprintf("previous definition is here"));
            return;
        }
    }

    {
        ZigType *type;
        if (get_primitive_type(g, tld->name, &type) != ErrorPrimitiveTypeNotFound) {
            add_node_error(g, tld->source_node,
                buf_sprintf("declaration shadows primitive type '%s'", buf_ptr(tld->name)));
        }
    }
}

static void preview_test_decl(CodeGen *g, AstNode *node, ScopeDecls *decls_scope) {
    assert(node->type == NodeTypeTestDecl);

    if (!g->is_test_build)
        return;

    ImportTableEntry *import = get_scope_import(&decls_scope->base);
    if (import->package != g->root_package)
        return;

    Buf *decl_name_buf = node->data.test_decl.name;

    Buf *test_name = g->test_name_prefix ?
        buf_sprintf("%s%s", buf_ptr(g->test_name_prefix), buf_ptr(decl_name_buf)) : decl_name_buf;

    if (g->test_filter != nullptr && strstr(buf_ptr(test_name), buf_ptr(g->test_filter)) == nullptr) {
        return;
    }

    TldFn *tld_fn = allocate<TldFn>(1);
    init_tld(&tld_fn->base, TldIdFn, test_name, VisibModPrivate, node, &decls_scope->base);
    g->resolve_queue.append(&tld_fn->base);
}

static void preview_comptime_decl(CodeGen *g, AstNode *node, ScopeDecls *decls_scope) {
    assert(node->type == NodeTypeCompTime);

    TldCompTime *tld_comptime = allocate<TldCompTime>(1);
    init_tld(&tld_comptime->base, TldIdCompTime, nullptr, VisibModPrivate, node, &decls_scope->base);
    g->resolve_queue.append(&tld_comptime->base);
}

void init_tld(Tld *tld, TldId id, Buf *name, VisibMod visib_mod, AstNode *source_node,
    Scope *parent_scope)
{
    tld->id = id;
    tld->name = name;
    tld->visib_mod = visib_mod;
    tld->source_node = source_node;
    tld->import = source_node ? source_node->owner : nullptr;
    tld->parent_scope = parent_scope;
}

void update_compile_var(CodeGen *g, Buf *name, ConstExprValue *value) {
    Tld *tld = g->compile_var_import->decls_scope->decl_table.get(name);
    resolve_top_level_decl(g, tld, false, tld->source_node);
    assert(tld->id == TldIdVar);
    TldVar *tld_var = (TldVar *)tld;
    tld_var->var->const_value = value;
    tld_var->var->var_type = value->type;
    tld_var->var->align_bytes = get_abi_alignment(g, value->type);
}

void scan_decls(CodeGen *g, ScopeDecls *decls_scope, AstNode *node) {
    switch (node->type) {
        case NodeTypeContainerDecl:
            for (size_t i = 0; i < node->data.container_decl.decls.length; i += 1) {
                AstNode *child = node->data.container_decl.decls.at(i);
                scan_decls(g, decls_scope, child);
            }
            break;
        case NodeTypeFnDef:
            scan_decls(g, decls_scope, node->data.fn_def.fn_proto);
            break;
        case NodeTypeVariableDeclaration:
            {
                Buf *name = node->data.variable_declaration.symbol;
                VisibMod visib_mod = node->data.variable_declaration.visib_mod;
                TldVar *tld_var = allocate<TldVar>(1);
                init_tld(&tld_var->base, TldIdVar, name, visib_mod, node, &decls_scope->base);
                tld_var->extern_lib_name = node->data.variable_declaration.lib_name;
                add_top_level_decl(g, decls_scope, &tld_var->base);
                break;
            }
        case NodeTypeFnProto:
            {
                // if the name is missing, we immediately announce an error
                Buf *fn_name = node->data.fn_proto.name;
                if (fn_name == nullptr) {
                    add_node_error(g, node, buf_sprintf("missing function name"));
                    break;
                }

                VisibMod visib_mod = node->data.fn_proto.visib_mod;
                TldFn *tld_fn = allocate<TldFn>(1);
                init_tld(&tld_fn->base, TldIdFn, fn_name, visib_mod, node, &decls_scope->base);
                tld_fn->extern_lib_name = node->data.fn_proto.lib_name;
                add_top_level_decl(g, decls_scope, &tld_fn->base);

                break;
            }
        case NodeTypeUse:
            {
                g->use_queue.append(node);
                ImportTableEntry *import = get_scope_import(&decls_scope->base);
                import->use_decls.append(node);
                break;
            }
        case NodeTypeTestDecl:
            preview_test_decl(g, node, decls_scope);
            break;
        case NodeTypeCompTime:
            preview_comptime_decl(g, node, decls_scope);
            break;
        case NodeTypeParamDecl:
        case NodeTypeReturnExpr:
        case NodeTypeDefer:
        case NodeTypeBlock:
        case NodeTypeGroupedExpr:
        case NodeTypeBinOpExpr:
        case NodeTypeUnwrapErrorExpr:
        case NodeTypeFnCallExpr:
        case NodeTypeArrayAccessExpr:
        case NodeTypeSliceExpr:
        case NodeTypeFloatLiteral:
        case NodeTypeIntLiteral:
        case NodeTypeStringLiteral:
        case NodeTypeCharLiteral:
        case NodeTypeBoolLiteral:
        case NodeTypeNullLiteral:
        case NodeTypeUndefinedLiteral:
        case NodeTypeSymbol:
        case NodeTypePrefixOpExpr:
        case NodeTypePointerType:
        case NodeTypeIfBoolExpr:
        case NodeTypeWhileExpr:
        case NodeTypeForExpr:
        case NodeTypeSwitchExpr:
        case NodeTypeSwitchProng:
        case NodeTypeSwitchRange:
        case NodeTypeBreak:
        case NodeTypeContinue:
        case NodeTypeUnreachable:
        case NodeTypeAsmExpr:
        case NodeTypeFieldAccessExpr:
        case NodeTypePtrDeref:
        case NodeTypeUnwrapOptional:
        case NodeTypeStructField:
        case NodeTypeContainerInitExpr:
        case NodeTypeStructValueField:
        case NodeTypeArrayType:
        case NodeTypeErrorType:
        case NodeTypeIfErrorExpr:
        case NodeTypeIfOptional:
        case NodeTypeErrorSetDecl:
        case NodeTypeCancel:
        case NodeTypeResume:
        case NodeTypeAwaitExpr:
        case NodeTypeSuspend:
        case NodeTypePromiseType:
            zig_unreachable();
    }
}

static void resolve_decl_container(CodeGen *g, TldContainer *tld_container) {
    ZigType *type_entry = tld_container->type_entry;
    assert(type_entry);

    switch (type_entry->id) {
        case ZigTypeIdStruct:
            resolve_struct_type(g, tld_container->type_entry);
            return;
        case ZigTypeIdEnum:
            resolve_enum_type(g, tld_container->type_entry);
            return;
        case ZigTypeIdUnion:
            resolve_union_type(g, tld_container->type_entry);
            return;
        default:
            zig_unreachable();
    }
}

ZigType *validate_var_type(CodeGen *g, AstNode *source_node, ZigType *type_entry) {
    switch (type_entry->id) {
        case ZigTypeIdInvalid:
            return g->builtin_types.entry_invalid;
        case ZigTypeIdUnreachable:
        case ZigTypeIdUndefined:
        case ZigTypeIdNull:
        case ZigTypeIdArgTuple:
        case ZigTypeIdOpaque:
            add_node_error(g, source_node, buf_sprintf("variable of type '%s' not allowed",
                buf_ptr(&type_entry->name)));
            return g->builtin_types.entry_invalid;
        case ZigTypeIdComptimeFloat:
        case ZigTypeIdComptimeInt:
        case ZigTypeIdNamespace:
        case ZigTypeIdMetaType:
        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 ZigTypeIdBoundFn:
        case ZigTypeIdPromise:
        case ZigTypeIdVector:
            return type_entry;
    }
    zig_unreachable();
}

// Set name to nullptr to make the variable anonymous (not visible to programmer).
// TODO merge with definition of add_local_var in ir.cpp
ZigVar *add_variable(CodeGen *g, AstNode *source_node, Scope *parent_scope, Buf *name,
    bool is_const, ConstExprValue *const_value, Tld *src_tld, ZigType *var_type)
{
    Error err;
    assert(const_value != nullptr);
    assert(var_type != nullptr);

    ZigVar *variable_entry = allocate<ZigVar>(1);
    variable_entry->const_value = const_value;
    variable_entry->var_type = var_type;
    variable_entry->parent_scope = parent_scope;
    variable_entry->shadowable = false;
    variable_entry->mem_slot_index = SIZE_MAX;
    variable_entry->src_arg_index = SIZE_MAX;

    assert(name);
    buf_init_from_buf(&variable_entry->name, name);

    if ((err = type_resolve(g, var_type, ResolveStatusAlignmentKnown))) {
        variable_entry->var_type = g->builtin_types.entry_invalid;
    } else {
        variable_entry->align_bytes = get_abi_alignment(g, var_type);

        ZigVar *existing_var = find_variable(g, parent_scope, name, nullptr);
        if (existing_var && !existing_var->shadowable) {
            ErrorMsg *msg = add_node_error(g, source_node,
                    buf_sprintf("redeclaration of variable '%s'", buf_ptr(name)));
            add_error_note(g, msg, existing_var->decl_node, buf_sprintf("previous declaration is here"));
            variable_entry->var_type = g->builtin_types.entry_invalid;
        } else {
            ZigType *type;
            if (get_primitive_type(g, name, &type) != ErrorPrimitiveTypeNotFound) {
                add_node_error(g, source_node,
                        buf_sprintf("variable shadows primitive type '%s'", buf_ptr(name)));
                variable_entry->var_type = g->builtin_types.entry_invalid;
            } else {
                Scope *search_scope = nullptr;
                if (src_tld == nullptr) {
                    search_scope = parent_scope;
                } else if (src_tld->parent_scope != nullptr && src_tld->parent_scope->parent != nullptr) {
                    search_scope = src_tld->parent_scope->parent;
                }
                if (search_scope != nullptr) {
                    Tld *tld = find_decl(g, search_scope, name);
                    if (tld != nullptr) {
                        ErrorMsg *msg = add_node_error(g, source_node,
                                buf_sprintf("redefinition of '%s'", buf_ptr(name)));
                        add_error_note(g, msg, tld->source_node, buf_sprintf("previous definition is here"));
                        variable_entry->var_type = g->builtin_types.entry_invalid;
                    }
                }
            }
        }
    }

    Scope *child_scope;
    if (source_node && source_node->type == NodeTypeParamDecl) {
        child_scope = create_var_scope(g, source_node, parent_scope, variable_entry);
    } else {
        // it's already in the decls table
        child_scope = parent_scope;
    }


    variable_entry->src_is_const = is_const;
    variable_entry->gen_is_const = is_const;
    variable_entry->decl_node = source_node;
    variable_entry->child_scope = child_scope;


    return variable_entry;
}

static void resolve_decl_var(CodeGen *g, TldVar *tld_var) {
    AstNode *source_node = tld_var->base.source_node;
    AstNodeVariableDeclaration *var_decl = &source_node->data.variable_declaration;

    bool is_const = var_decl->is_const;
    bool is_extern = var_decl->is_extern;
    bool is_export = var_decl->is_export;
    bool is_thread_local = var_decl->threadlocal_tok != nullptr;

    ZigType *explicit_type = nullptr;
    if (var_decl->type) {
        ZigType *proposed_type = analyze_type_expr(g, tld_var->base.parent_scope, var_decl->type);
        explicit_type = validate_var_type(g, var_decl->type, proposed_type);
    }

    assert(!is_export || !is_extern);

    VarLinkage linkage;
    if (is_export) {
        linkage = VarLinkageExportStrong;
    } else if (is_extern) {
        linkage = VarLinkageExternal;
    } else {
        linkage = VarLinkageInternal;
    }

    ConstExprValue *init_value = nullptr;

    // TODO more validation for types that can't be used for export/extern variables
    ZigType *implicit_type = nullptr;
    if (explicit_type && explicit_type->id == ZigTypeIdInvalid) {
        implicit_type = explicit_type;
    } else if (var_decl->expr) {
        init_value = analyze_const_value(g, tld_var->base.parent_scope, var_decl->expr, explicit_type, var_decl->symbol);
        assert(init_value);
        implicit_type = init_value->type;

        if (implicit_type->id == ZigTypeIdUnreachable) {
            add_node_error(g, source_node, buf_sprintf("variable initialization is unreachable"));
            implicit_type = g->builtin_types.entry_invalid;
        } else if ((!is_const || linkage == VarLinkageExternal) &&
                (implicit_type->id == ZigTypeIdComptimeFloat ||
                implicit_type->id == ZigTypeIdComptimeInt))
        {
            add_node_error(g, source_node, buf_sprintf("unable to infer variable type"));
            implicit_type = g->builtin_types.entry_invalid;
        } else if (implicit_type->id == ZigTypeIdNull) {
            add_node_error(g, source_node, buf_sprintf("unable to infer variable type"));
            implicit_type = g->builtin_types.entry_invalid;
        } else if (implicit_type->id == ZigTypeIdMetaType && !is_const) {
            add_node_error(g, source_node, buf_sprintf("variable of type 'type' must be constant"));
            implicit_type = g->builtin_types.entry_invalid;
        }
        assert(implicit_type->id == ZigTypeIdInvalid || init_value->special != ConstValSpecialRuntime);
    } else if (linkage != VarLinkageExternal) {
        add_node_error(g, source_node, buf_sprintf("variables must be initialized"));
        implicit_type = g->builtin_types.entry_invalid;
    }

    ZigType *type = explicit_type ? explicit_type : implicit_type;
    assert(type != nullptr); // should have been caught by the parser

    ConstExprValue *init_val = (init_value != nullptr) ? init_value : create_const_runtime(type);

    tld_var->var = add_variable(g, source_node, tld_var->base.parent_scope, var_decl->symbol,
            is_const, init_val, &tld_var->base, type);
    tld_var->var->linkage = linkage;
    tld_var->var->is_thread_local = is_thread_local;

    if (implicit_type != nullptr && type_is_invalid(implicit_type)) {
        tld_var->var->var_type = g->builtin_types.entry_invalid;
    }

    if (var_decl->align_expr != nullptr) {
        if (!analyze_const_align(g, tld_var->base.parent_scope, var_decl->align_expr, &tld_var->var->align_bytes)) {
            tld_var->var->var_type = g->builtin_types.entry_invalid;
        }
    }

    if (var_decl->section_expr != nullptr) {
        if (var_decl->is_extern) {
            add_node_error(g, var_decl->section_expr,
                buf_sprintf("cannot set section of external variable '%s'", buf_ptr(var_decl->symbol)));
        } else if (!analyze_const_string(g, tld_var->base.parent_scope, var_decl->section_expr, &tld_var->section_name)) {
            tld_var->section_name = nullptr;
        }
    }

    if (is_thread_local && is_const) {
        add_node_error(g, source_node, buf_sprintf("threadlocal variable cannot be constant"));
    }

    g->global_vars.append(tld_var);
}

void resolve_top_level_decl(CodeGen *g, Tld *tld, bool pointer_only, AstNode *source_node) {
    if (tld->resolution != TldResolutionUnresolved)
        return;

    if (tld->dep_loop_flag) {
        add_node_error(g, tld->source_node, buf_sprintf("'%s' depends on itself", buf_ptr(tld->name)));
        tld->resolution = TldResolutionInvalid;
        return;
    }

    tld->dep_loop_flag = true;
    g->tld_ref_source_node_stack.append(source_node);

    switch (tld->id) {
        case TldIdVar:
            {
                TldVar *tld_var = (TldVar *)tld;
                resolve_decl_var(g, tld_var);
                break;
            }
        case TldIdFn:
            {
                TldFn *tld_fn = (TldFn *)tld;
                resolve_decl_fn(g, tld_fn);
                break;
            }
        case TldIdContainer:
            {
                TldContainer *tld_container = (TldContainer *)tld;
                resolve_decl_container(g, tld_container);
                break;
            }
        case TldIdCompTime:
            {
                TldCompTime *tld_comptime = (TldCompTime *)tld;
                resolve_decl_comptime(g, tld_comptime);
                break;
            }
    }

    tld->resolution = TldResolutionOk;
    tld->dep_loop_flag = false;
    g->tld_ref_source_node_stack.pop();
}

Tld *find_decl(CodeGen *g, Scope *scope, Buf *name) {
    // we must resolve all the use decls
    ImportTableEntry *import = get_scope_import(scope);
    for (size_t i = 0; i < import->use_decls.length; i += 1) {
        AstNode *use_decl_node = import->use_decls.at(i);
        if (use_decl_node->data.use.resolution == TldResolutionUnresolved) {
            preview_use_decl(g, use_decl_node);
            resolve_use_decl(g, use_decl_node);
        }
    }

    while (scope) {
        if (scope->id == ScopeIdDecls) {
            ScopeDecls *decls_scope = (ScopeDecls *)scope;
            auto entry = decls_scope->decl_table.maybe_get(name);
            if (entry)
                return entry->value;
        }
        scope = scope->parent;
    }
    return nullptr;
}

ZigVar *find_variable(CodeGen *g, Scope *scope, Buf *name, ScopeFnDef **crossed_fndef_scope) {
    ScopeFnDef *my_crossed_fndef_scope = nullptr;
    while (scope) {
        if (scope->id == ScopeIdVarDecl) {
            ScopeVarDecl *var_scope = (ScopeVarDecl *)scope;
            if (buf_eql_buf(name, &var_scope->var->name)) {
                if (crossed_fndef_scope != nullptr)
                    *crossed_fndef_scope = my_crossed_fndef_scope;
                return var_scope->var;
            }
        } else if (scope->id == ScopeIdDecls) {
            ScopeDecls *decls_scope = (ScopeDecls *)scope;
            auto entry = decls_scope->decl_table.maybe_get(name);
            if (entry) {
                Tld *tld = entry->value;
                if (tld->id == TldIdVar) {
                    TldVar *tld_var = (TldVar *)tld;
                    if (tld_var->var) {
                        if (crossed_fndef_scope != nullptr)
                            *crossed_fndef_scope = nullptr;
                        return tld_var->var;
                    }
                }
            }
        } else if (scope->id == ScopeIdFnDef) {
            my_crossed_fndef_scope = (ScopeFnDef *)scope;
        }
        scope = scope->parent;
    }

    return nullptr;
}

ZigFn *scope_fn_entry(Scope *scope) {
    while (scope) {
        if (scope->id == ScopeIdFnDef) {
            ScopeFnDef *fn_scope = (ScopeFnDef *)scope;
            return fn_scope->fn_entry;
        }
        scope = scope->parent;
    }
    return nullptr;
}

TypeEnumField *find_enum_type_field(ZigType *enum_type, Buf *name) {
    assert(enum_type->id == ZigTypeIdEnum);
    if (enum_type->data.enumeration.src_field_count == 0)
        return nullptr;
    auto entry = enum_type->data.enumeration.fields_by_name.maybe_get(name);
    if (entry == nullptr)
        return nullptr;
    return entry->value;
}

TypeStructField *find_struct_type_field(ZigType *type_entry, Buf *name) {
    assert(type_entry->id == ZigTypeIdStruct);
    assert(type_is_resolved(type_entry, ResolveStatusZeroBitsKnown));
    if (type_entry->data.structure.src_field_count == 0)
        return nullptr;
    auto entry = type_entry->data.structure.fields_by_name.maybe_get(name);
    if (entry == nullptr)
        return nullptr;
    return entry->value;
}

TypeUnionField *find_union_type_field(ZigType *type_entry, Buf *name) {
    assert(type_entry->id == ZigTypeIdUnion);
    assert(type_entry->data.unionation.zero_bits_known);
    if (type_entry->data.unionation.src_field_count == 0)
        return nullptr;
    auto entry = type_entry->data.unionation.fields_by_name.maybe_get(name);
    if (entry == nullptr)
        return nullptr;
    return entry->value;
}

TypeUnionField *find_union_field_by_tag(ZigType *type_entry, const BigInt *tag) {
    assert(type_entry->id == ZigTypeIdUnion);
    assert(type_entry->data.unionation.zero_bits_known);
    for (uint32_t i = 0; i < type_entry->data.unionation.src_field_count; i += 1) {
        TypeUnionField *field = &type_entry->data.unionation.fields[i];
        if (bigint_cmp(&field->enum_field->value, tag) == CmpEQ) {
            return field;
        }
    }
    return nullptr;
}

TypeEnumField *find_enum_field_by_tag(ZigType *enum_type, const BigInt *tag) {
    assert(enum_type->data.enumeration.zero_bits_known);
    for (uint32_t i = 0; i < enum_type->data.enumeration.src_field_count; i += 1) {
        TypeEnumField *field = &enum_type->data.enumeration.fields[i];
        if (bigint_cmp(&field->value, tag) == CmpEQ) {
            return field;
        }
    }
    return nullptr;
}


static bool is_container(ZigType *type_entry) {
    switch (type_entry->id) {
        case ZigTypeIdInvalid:
            zig_unreachable();
        case ZigTypeIdStruct:
        case ZigTypeIdEnum:
        case ZigTypeIdUnion:
            return true;
        case ZigTypeIdPointer:
        case ZigTypeIdMetaType:
        case ZigTypeIdVoid:
        case ZigTypeIdBool:
        case ZigTypeIdUnreachable:
        case ZigTypeIdInt:
        case ZigTypeIdFloat:
        case ZigTypeIdArray:
        case ZigTypeIdComptimeFloat:
        case ZigTypeIdComptimeInt:
        case ZigTypeIdUndefined:
        case ZigTypeIdNull:
        case ZigTypeIdOptional:
        case ZigTypeIdErrorUnion:
        case ZigTypeIdErrorSet:
        case ZigTypeIdFn:
        case ZigTypeIdNamespace:
        case ZigTypeIdBoundFn:
        case ZigTypeIdArgTuple:
        case ZigTypeIdOpaque:
        case ZigTypeIdPromise:
        case ZigTypeIdVector:
            return false;
    }
    zig_unreachable();
}

bool is_ref(ZigType *type_entry) {
    return type_entry->id == ZigTypeIdPointer && type_entry->data.pointer.ptr_len == PtrLenSingle;
}

bool is_array_ref(ZigType *type_entry) {
    ZigType *array = is_ref(type_entry) ?
        type_entry->data.pointer.child_type : type_entry;
    return array->id == ZigTypeIdArray;
}

bool is_container_ref(ZigType *type_entry) {
    return is_ref(type_entry) ?
        is_container(type_entry->data.pointer.child_type) : is_container(type_entry);
}

ZigType *container_ref_type(ZigType *type_entry) {
    assert(is_container_ref(type_entry));
    return is_ref(type_entry) ?
        type_entry->data.pointer.child_type : type_entry;
}

void resolve_container_type(CodeGen *g, ZigType *type_entry) {
    switch (type_entry->id) {
        case ZigTypeIdStruct:
            resolve_struct_type(g, type_entry);
            break;
        case ZigTypeIdEnum:
            resolve_enum_type(g, type_entry);
            break;
        case ZigTypeIdUnion:
            resolve_union_type(g, type_entry);
            break;
        case ZigTypeIdPointer:
        case ZigTypeIdMetaType:
        case ZigTypeIdVoid:
        case ZigTypeIdBool:
        case ZigTypeIdUnreachable:
        case ZigTypeIdInt:
        case ZigTypeIdFloat:
        case ZigTypeIdArray:
        case ZigTypeIdComptimeFloat:
        case ZigTypeIdComptimeInt:
        case ZigTypeIdUndefined:
        case ZigTypeIdNull:
        case ZigTypeIdOptional:
        case ZigTypeIdErrorUnion:
        case ZigTypeIdErrorSet:
        case ZigTypeIdFn:
        case ZigTypeIdNamespace:
        case ZigTypeIdBoundFn:
        case ZigTypeIdInvalid:
        case ZigTypeIdArgTuple:
        case ZigTypeIdOpaque:
        case ZigTypeIdPromise:
        case ZigTypeIdVector:
            zig_unreachable();
    }
}

ZigType *get_src_ptr_type(ZigType *type) {
    if (type->id == ZigTypeIdPointer) return type;
    if (type->id == ZigTypeIdFn) return type;
    if (type->id == ZigTypeIdPromise) return type;
    if (type->id == ZigTypeIdOptional) {
        if (type->data.maybe.child_type->id == ZigTypeIdPointer) {
            return type->data.maybe.child_type->data.pointer.allow_zero ? nullptr : type->data.maybe.child_type;
        }
        if (type->data.maybe.child_type->id == ZigTypeIdFn) return type->data.maybe.child_type;
        if (type->data.maybe.child_type->id == ZigTypeIdPromise) return type->data.maybe.child_type;
    }
    return nullptr;
}

ZigType *get_codegen_ptr_type(ZigType *type) {
    ZigType *ty = get_src_ptr_type(type);
    if (ty == nullptr || !type_has_bits(ty))
        return nullptr;
    return ty;
}

bool type_is_nonnull_ptr(ZigType *type) {
    return type_is_codegen_pointer(type) && !ptr_allows_addr_zero(type);
}

bool type_is_codegen_pointer(ZigType *type) {
    return get_codegen_ptr_type(type) == type;
}

uint32_t get_ptr_align(CodeGen *g, ZigType *type) {
    ZigType *ptr_type = get_src_ptr_type(type);
    if (ptr_type->id == ZigTypeIdPointer) {
        return (ptr_type->data.pointer.explicit_alignment == 0) ?
            get_abi_alignment(g, ptr_type->data.pointer.child_type) : ptr_type->data.pointer.explicit_alignment;
    } else if (ptr_type->id == ZigTypeIdFn) {
        // I tried making this use LLVMABIAlignmentOfType but it trips this assertion in LLVM:
        // "Cannot getTypeInfo() on a type that is unsized!"
        // when getting the alignment of `?extern fn() void`.
        // See http://lists.llvm.org/pipermail/llvm-dev/2018-September/126142.html
        return (ptr_type->data.fn.fn_type_id.alignment == 0) ? 1 : ptr_type->data.fn.fn_type_id.alignment;
    } else if (ptr_type->id == ZigTypeIdPromise) {
        return get_coro_frame_align_bytes(g);
    } else {
        zig_unreachable();
    }
}

bool get_ptr_const(ZigType *type) {
    ZigType *ptr_type = get_src_ptr_type(type);
    if (ptr_type->id == ZigTypeIdPointer) {
        return ptr_type->data.pointer.is_const;
    } else if (ptr_type->id == ZigTypeIdFn) {
        return true;
    } else if (ptr_type->id == ZigTypeIdPromise) {
        return true;
    } else {
        zig_unreachable();
    }
}

AstNode *get_param_decl_node(ZigFn *fn_entry, size_t index) {
    if (fn_entry->param_source_nodes)
        return fn_entry->param_source_nodes[index];
    else if (fn_entry->proto_node)
        return fn_entry->proto_node->data.fn_proto.params.at(index);
    else
        return nullptr;
}

static void define_local_param_variables(CodeGen *g, ZigFn *fn_table_entry) {
    ZigType *fn_type = fn_table_entry->type_entry;
    assert(!fn_type->data.fn.is_generic);
    FnTypeId *fn_type_id = &fn_type->data.fn.fn_type_id;
    for (size_t i = 0; i < fn_type_id->param_count; i += 1) {
        FnTypeParamInfo *param_info = &fn_type_id->param_info[i];
        AstNode *param_decl_node = get_param_decl_node(fn_table_entry, i);
        Buf *param_name;
        bool is_var_args = param_decl_node && param_decl_node->data.param_decl.is_var_args;
        if (param_decl_node && !is_var_args) {
            param_name = param_decl_node->data.param_decl.name;
        } else {
            param_name = buf_sprintf("arg%" ZIG_PRI_usize "", i);
        }
        if (param_name == nullptr) {
            continue;
        }

        ZigType *param_type = param_info->type;
        bool is_noalias = param_info->is_noalias;

        if (is_noalias && get_codegen_ptr_type(param_type) == nullptr) {
            add_node_error(g, param_decl_node, buf_sprintf("noalias on non-pointer parameter"));
        }

        ZigVar *var = add_variable(g, param_decl_node, fn_table_entry->child_scope,
                param_name, true, create_const_runtime(param_type), nullptr, param_type);
        var->src_arg_index = i;
        fn_table_entry->child_scope = var->child_scope;
        var->shadowable = var->shadowable || is_var_args;

        if (type_has_bits(param_type)) {
            fn_table_entry->variable_list.append(var);
        }

        if (fn_type->data.fn.gen_param_info) {
            var->gen_arg_index = fn_type->data.fn.gen_param_info[i].gen_index;
        }
    }
}

bool resolve_inferred_error_set(CodeGen *g, ZigType *err_set_type, AstNode *source_node) {
    assert(err_set_type->id == ZigTypeIdErrorSet);
    ZigFn *infer_fn = err_set_type->data.error_set.infer_fn;
    if (infer_fn != nullptr) {
        if (infer_fn->anal_state == FnAnalStateInvalid) {
            return false;
        } else if (infer_fn->anal_state == FnAnalStateReady) {
            analyze_fn_body(g, infer_fn);
            if (err_set_type->data.error_set.infer_fn != nullptr) {
                assert(g->errors.length != 0);
                return false;
            }
        } else {
            add_node_error(g, source_node,
                buf_sprintf("cannot resolve inferred error set '%s': function '%s' not fully analyzed yet",
                    buf_ptr(&err_set_type->name), buf_ptr(&err_set_type->data.error_set.infer_fn->symbol_name)));
            return false;
        }
    }
    return true;
}

void analyze_fn_ir(CodeGen *g, ZigFn *fn_table_entry, AstNode *return_type_node) {
    ZigType *fn_type = fn_table_entry->type_entry;
    assert(!fn_type->data.fn.is_generic);
    FnTypeId *fn_type_id = &fn_type->data.fn.fn_type_id;

    ZigType *block_return_type = ir_analyze(g, &fn_table_entry->ir_executable,
            &fn_table_entry->analyzed_executable, fn_type_id->return_type, return_type_node);
    fn_table_entry->src_implicit_return_type = block_return_type;

    if (type_is_invalid(block_return_type) || fn_table_entry->analyzed_executable.invalid) {
        assert(g->errors.length > 0);
        fn_table_entry->anal_state = FnAnalStateInvalid;
        return;
    }

    if (fn_type_id->return_type->id == ZigTypeIdErrorUnion) {
        ZigType *return_err_set_type = fn_type_id->return_type->data.error_union.err_set_type;
        if (return_err_set_type->data.error_set.infer_fn != nullptr) {
            ZigType *inferred_err_set_type;
            if (fn_table_entry->src_implicit_return_type->id == ZigTypeIdErrorSet) {
                inferred_err_set_type = fn_table_entry->src_implicit_return_type;
            } else if (fn_table_entry->src_implicit_return_type->id == ZigTypeIdErrorUnion) {
                inferred_err_set_type = fn_table_entry->src_implicit_return_type->data.error_union.err_set_type;
            } else {
                add_node_error(g, return_type_node,
                        buf_sprintf("function with inferred error set must return at least one possible error"));
                fn_table_entry->anal_state = FnAnalStateInvalid;
                return;
            }

            if (inferred_err_set_type->data.error_set.infer_fn != nullptr) {
                if (!resolve_inferred_error_set(g, inferred_err_set_type, return_type_node)) {
                    fn_table_entry->anal_state = FnAnalStateInvalid;
                    return;
                }
            }

            return_err_set_type->data.error_set.infer_fn = nullptr;
            if (type_is_global_error_set(inferred_err_set_type)) {
                return_err_set_type->data.error_set.err_count = UINT32_MAX;
            } else {
                return_err_set_type->data.error_set.err_count = inferred_err_set_type->data.error_set.err_count;
                if (inferred_err_set_type->data.error_set.err_count > 0) {
                    return_err_set_type->data.error_set.errors = allocate<ErrorTableEntry *>(inferred_err_set_type->data.error_set.err_count);
                    for (uint32_t i = 0; i < inferred_err_set_type->data.error_set.err_count; i += 1) {
                        return_err_set_type->data.error_set.errors[i] = inferred_err_set_type->data.error_set.errors[i];
                    }
                }
            }
        }
    }

    if (g->verbose_ir) {
        fprintf(stderr, "fn %s() { // (analyzed)\n", buf_ptr(&fn_table_entry->symbol_name));
        ir_print(g, stderr, &fn_table_entry->analyzed_executable, 4);
        fprintf(stderr, "}\n");
    }

    fn_table_entry->anal_state = FnAnalStateComplete;
}

static void analyze_fn_body(CodeGen *g, ZigFn *fn_table_entry) {
    assert(fn_table_entry->anal_state != FnAnalStateProbing);
    if (fn_table_entry->anal_state != FnAnalStateReady)
        return;

    fn_table_entry->anal_state = FnAnalStateProbing;

    AstNode *return_type_node = (fn_table_entry->proto_node != nullptr) ?
        fn_table_entry->proto_node->data.fn_proto.return_type : fn_table_entry->fndef_scope->base.source_node;

    assert(fn_table_entry->fndef_scope);
    if (!fn_table_entry->child_scope)
        fn_table_entry->child_scope = &fn_table_entry->fndef_scope->base;

    define_local_param_variables(g, fn_table_entry);

    ZigType *fn_type = fn_table_entry->type_entry;
    assert(!fn_type->data.fn.is_generic);

    ir_gen_fn(g, fn_table_entry);
    if (fn_table_entry->ir_executable.invalid) {
        fn_table_entry->anal_state = FnAnalStateInvalid;
        return;
    }
    if (g->verbose_ir) {
        fprintf(stderr, "\n");
        ast_render(g, stderr, fn_table_entry->body_node, 4);
        fprintf(stderr, "\n{ // (IR)\n");
        ir_print(g, stderr, &fn_table_entry->ir_executable, 4);
        fprintf(stderr, "}\n");
    }

    analyze_fn_ir(g, fn_table_entry, return_type_node);
}

static void add_symbols_from_import(CodeGen *g, AstNode *src_use_node, AstNode *dst_use_node) {
    if (src_use_node->data.use.resolution == TldResolutionUnresolved) {
        preview_use_decl(g, src_use_node);
    }

    ConstExprValue *use_target_value = src_use_node->data.use.value;
    if (type_is_invalid(use_target_value->type)) {
        dst_use_node->owner->any_imports_failed = true;
        return;
    }

    dst_use_node->data.use.resolution = TldResolutionOk;

    assert(use_target_value->special != ConstValSpecialRuntime);

    ImportTableEntry *target_import = use_target_value->data.x_import;
    assert(target_import);

    if (target_import->any_imports_failed) {
        dst_use_node->owner->any_imports_failed = true;
    }

    auto it = target_import->decls_scope->decl_table.entry_iterator();
    for (;;) {
        auto *entry = it.next();
        if (!entry)
            break;

        Tld *target_tld = entry->value;
        if (target_tld->import != target_import ||
            target_tld->visib_mod == VisibModPrivate)
        {
            continue;
        }

        Buf *target_tld_name = entry->key;

        auto existing_entry = dst_use_node->owner->decls_scope->decl_table.put_unique(target_tld_name, target_tld);
        if (existing_entry) {
            Tld *existing_decl = existing_entry->value;
            if (existing_decl != target_tld) {
                ErrorMsg *msg = add_node_error(g, dst_use_node,
                        buf_sprintf("import of '%s' overrides existing definition",
                            buf_ptr(target_tld_name)));
                add_error_note(g, msg, existing_decl->source_node, buf_sprintf("previous definition here"));
                add_error_note(g, msg, target_tld->source_node, buf_sprintf("imported definition here"));
            }
        }
    }

    for (size_t i = 0; i < target_import->use_decls.length; i += 1) {
        AstNode *use_decl_node = target_import->use_decls.at(i);
        if (use_decl_node->data.use.visib_mod != VisibModPrivate)
            add_symbols_from_import(g, use_decl_node, dst_use_node);
    }
}

void resolve_use_decl(CodeGen *g, AstNode *node) {
    assert(node->type == NodeTypeUse);

    if (node->data.use.resolution == TldResolutionOk ||
        node->data.use.resolution == TldResolutionInvalid)
    {
        return;
    }
    add_symbols_from_import(g, node, node);
}

void preview_use_decl(CodeGen *g, AstNode *node) {
    assert(node->type == NodeTypeUse);

    if (node->data.use.resolution == TldResolutionOk ||
        node->data.use.resolution == TldResolutionInvalid)
    {
        return;
    }

    node->data.use.resolution = TldResolutionResolving;
    ConstExprValue *result = analyze_const_value(g, &node->owner->decls_scope->base,
        node->data.use.expr, g->builtin_types.entry_namespace, nullptr);

    if (type_is_invalid(result->type))
        node->owner->any_imports_failed = true;

    node->data.use.value = result;
}

ImportTableEntry *add_source_file(CodeGen *g, PackageTableEntry *package, Buf *resolved_path, Buf *source_code) {
    if (g->verbose_tokenize) {
        fprintf(stderr, "\nOriginal Source (%s):\n", buf_ptr(resolved_path));
        fprintf(stderr, "----------------\n");
        fprintf(stderr, "%s\n", buf_ptr(source_code));

        fprintf(stderr, "\nTokens:\n");
        fprintf(stderr, "---------\n");
    }

    Tokenization tokenization = {0};
    tokenize(source_code, &tokenization);

    if (tokenization.err) {
        ErrorMsg *err = err_msg_create_with_line(resolved_path, tokenization.err_line, tokenization.err_column,
                source_code, tokenization.line_offsets, tokenization.err);

        print_err_msg(err, g->err_color);
        exit(1);
    }

    if (g->verbose_tokenize) {
        print_tokens(source_code, tokenization.tokens);

        fprintf(stderr, "\nAST:\n");
        fprintf(stderr, "------\n");
    }

    ImportTableEntry *import_entry = allocate<ImportTableEntry>(1);
    import_entry->package = package;
    import_entry->source_code = source_code;
    import_entry->line_offsets = tokenization.line_offsets;
    import_entry->path = resolved_path;

    import_entry->root = ast_parse(source_code, tokenization.tokens, import_entry, g->err_color);
    assert(import_entry->root);
    if (g->verbose_ast) {
        ast_print(stderr, import_entry->root, 0);
    }

    Buf *src_dirname = buf_alloc();
    Buf *src_basename = buf_alloc();
    os_path_split(resolved_path, src_dirname, src_basename);

    import_entry->di_file = ZigLLVMCreateFile(g->dbuilder, buf_ptr(src_basename), buf_ptr(src_dirname));
    g->import_table.put(resolved_path, import_entry);
    g->import_queue.append(import_entry);

    import_entry->decls_scope = create_decls_scope(g, import_entry->root, nullptr, nullptr, import_entry);


    assert(import_entry->root->type == NodeTypeContainerDecl);
    for (size_t decl_i = 0; decl_i < import_entry->root->data.container_decl.decls.length; decl_i += 1) {
        AstNode *top_level_decl = import_entry->root->data.container_decl.decls.at(decl_i);

        if (top_level_decl->type == NodeTypeFnDef) {
            AstNode *proto_node = top_level_decl->data.fn_def.fn_proto;
            assert(proto_node->type == NodeTypeFnProto);
            Buf *proto_name = proto_node->data.fn_proto.name;

            bool is_pub = (proto_node->data.fn_proto.visib_mod == VisibModPub);
            bool ok_cc = (proto_node->data.fn_proto.cc == CallingConventionUnspecified ||
                    proto_node->data.fn_proto.cc == CallingConventionCold);

            if (is_pub && ok_cc) {
                if (buf_eql_str(proto_name, "main")) {
                    g->have_pub_main = true;
                    g->subsystem = TargetSubsystemConsole;
                } else if (buf_eql_str(proto_name, "panic")) {
                    g->have_pub_panic = true;
                }
            }
        }
    }

    return import_entry;
}

void scan_import(CodeGen *g, ImportTableEntry *import) {
    if (!import->scanned) {
        import->scanned = true;
        scan_decls(g, import->decls_scope, import->root);
    }
}

void semantic_analyze(CodeGen *g) {
    for (; g->import_queue_index < g->import_queue.length; g->import_queue_index += 1) {
        ImportTableEntry *import = g->import_queue.at(g->import_queue_index);
        scan_import(g, import);
    }

    for (; g->use_queue_index < g->use_queue.length; g->use_queue_index += 1) {
        AstNode *use_decl_node = g->use_queue.at(g->use_queue_index);
        preview_use_decl(g, use_decl_node);
    }

    for (size_t i = 0; i < g->use_queue.length; i += 1) {
        AstNode *use_decl_node = g->use_queue.at(i);
        resolve_use_decl(g, use_decl_node);
    }

    while (g->resolve_queue_index < g->resolve_queue.length ||
           g->fn_defs_index < g->fn_defs.length)
    {
        for (; g->resolve_queue_index < g->resolve_queue.length; g->resolve_queue_index += 1) {
            Tld *tld = g->resolve_queue.at(g->resolve_queue_index);
            bool pointer_only = false;
            AstNode *source_node = nullptr;
            resolve_top_level_decl(g, tld, pointer_only, source_node);
        }

        for (; g->fn_defs_index < g->fn_defs.length; g->fn_defs_index += 1) {
            ZigFn *fn_entry = g->fn_defs.at(g->fn_defs_index);
            analyze_fn_body(g, fn_entry);
        }
    }
}

ZigType *get_int_type(CodeGen *g, bool is_signed, uint32_t size_in_bits) {
    assert(size_in_bits <= 65535);
    TypeId type_id = {};
    type_id.id = ZigTypeIdInt;
    type_id.data.integer.is_signed = is_signed;
    type_id.data.integer.bit_count = size_in_bits;

    {
        auto entry = g->type_table.maybe_get(type_id);
        if (entry)
            return entry->value;
    }

    ZigType *new_entry = make_int_type(g, is_signed, size_in_bits);
    g->type_table.put(type_id, new_entry);
    return new_entry;
}

bool is_valid_vector_elem_type(ZigType *elem_type) {
    return elem_type->id == ZigTypeIdInt ||
        elem_type->id == ZigTypeIdFloat ||
        get_codegen_ptr_type(elem_type) != nullptr;
}

ZigType *get_vector_type(CodeGen *g, uint32_t len, ZigType *elem_type) {
    assert(is_valid_vector_elem_type(elem_type));

    TypeId type_id = {};
    type_id.id = ZigTypeIdVector;
    type_id.data.vector.len = len;
    type_id.data.vector.elem_type = elem_type;

    {
        auto entry = g->type_table.maybe_get(type_id);
        if (entry)
            return entry->value;
    }

    ZigType *entry = new_type_table_entry(ZigTypeIdVector);
    entry->zero_bits = (len == 0) || !type_has_bits(elem_type);
    entry->type_ref = entry->zero_bits ? LLVMVoidType() : LLVMVectorType(elem_type->type_ref, len);
    entry->data.vector.len = len;
    entry->data.vector.elem_type = elem_type;

    buf_resize(&entry->name, 0);
    buf_appendf(&entry->name, "@Vector(%u, %s)", len, buf_ptr(&elem_type->name));

    entry->di_type = ZigLLVMDIBuilderCreateVectorType(g->dbuilder,
            len * type_size_bits(g, elem_type),
            LLVMABIAlignmentOfType(g->target_data_ref, entry->type_ref), elem_type->di_type, len);

    g->type_table.put(type_id, entry);
    return entry;
}

ZigType **get_c_int_type_ptr(CodeGen *g, CIntType c_int_type) {
    return &g->builtin_types.entry_c_int[c_int_type];
}

ZigType *get_c_int_type(CodeGen *g, CIntType c_int_type) {
    return *get_c_int_type_ptr(g, c_int_type);
}

bool handle_is_ptr(ZigType *type_entry) {
    switch (type_entry->id) {
        case ZigTypeIdInvalid:
        case ZigTypeIdMetaType:
        case ZigTypeIdComptimeFloat:
        case ZigTypeIdComptimeInt:
        case ZigTypeIdUndefined:
        case ZigTypeIdNull:
        case ZigTypeIdNamespace:
        case ZigTypeIdBoundFn:
        case ZigTypeIdArgTuple:
        case ZigTypeIdOpaque:
             zig_unreachable();
        case ZigTypeIdUnreachable:
        case ZigTypeIdVoid:
        case ZigTypeIdBool:
        case ZigTypeIdInt:
        case ZigTypeIdFloat:
        case ZigTypeIdPointer:
        case ZigTypeIdErrorSet:
        case ZigTypeIdFn:
        case ZigTypeIdEnum:
        case ZigTypeIdPromise:
        case ZigTypeIdVector:
             return false;
        case ZigTypeIdArray:
        case ZigTypeIdStruct:
             return type_has_bits(type_entry);
        case ZigTypeIdErrorUnion:
             return type_has_bits(type_entry->data.error_union.payload_type);
        case ZigTypeIdOptional:
             return type_has_bits(type_entry->data.maybe.child_type) &&
                    !type_is_codegen_pointer(type_entry->data.maybe.child_type) &&
                    type_entry->data.maybe.child_type->id != ZigTypeIdErrorSet;
        case ZigTypeIdUnion:
             assert(type_entry->data.unionation.zero_bits_known);
             if (type_entry->data.unionation.gen_field_count == 0)
                 return false;
             if (!type_has_bits(type_entry))
                 return false;
             return true;

    }
    zig_unreachable();
}

static uint32_t hash_ptr(void *ptr) {
    return (uint32_t)(((uintptr_t)ptr) % UINT32_MAX);
}

static uint32_t hash_size(size_t x) {
    return (uint32_t)(x % UINT32_MAX);
}

uint32_t fn_table_entry_hash(ZigFn* value) {
    return ptr_hash(value);
}

bool fn_table_entry_eql(ZigFn *a, ZigFn *b) {
    return ptr_eq(a, b);
}

uint32_t fn_type_id_hash(FnTypeId *id) {
    uint32_t result = 0;
    result += ((uint32_t)(id->cc)) * (uint32_t)3349388391;
    result += id->is_var_args ? (uint32_t)1931444534 : 0;
    result += hash_ptr(id->return_type);
    result += hash_ptr(id->async_allocator_type);
    result += id->alignment * 0xd3b3f3e2;
    for (size_t i = 0; i < id->param_count; i += 1) {
        FnTypeParamInfo *info = &id->param_info[i];
        result += info->is_noalias ? (uint32_t)892356923 : 0;
        result += hash_ptr(info->type);
    }
    return result;
}

bool fn_type_id_eql(FnTypeId *a, FnTypeId *b) {
    if (a->cc != b->cc ||
        a->return_type != b->return_type ||
        a->is_var_args != b->is_var_args ||
        a->param_count != b->param_count ||
        a->alignment != b->alignment ||
        a->async_allocator_type != b->async_allocator_type)
    {
        return false;
    }
    for (size_t i = 0; i < a->param_count; i += 1) {
        FnTypeParamInfo *a_param_info = &a->param_info[i];
        FnTypeParamInfo *b_param_info = &b->param_info[i];

        if (a_param_info->type != b_param_info->type ||
            a_param_info->is_noalias != b_param_info->is_noalias)
        {
            return false;
        }
    }
    return true;
}

static uint32_t hash_const_val_error_set(ConstExprValue *const_val) {
    assert(const_val->data.x_err_set != nullptr);
    return const_val->data.x_err_set->value ^ 2630160122;
}

static uint32_t hash_const_val_ptr(ConstExprValue *const_val) {
    uint32_t hash_val = 0;
    switch (const_val->data.x_ptr.mut) {
        case ConstPtrMutRuntimeVar:
            hash_val += (uint32_t)3500721036;
            break;
        case ConstPtrMutComptimeConst:
            hash_val += (uint32_t)4214318515;
            break;
        case ConstPtrMutComptimeVar:
            hash_val += (uint32_t)1103195694;
            break;
    }
    switch (const_val->data.x_ptr.special) {
        case ConstPtrSpecialInvalid:
            zig_unreachable();
        case ConstPtrSpecialRef:
            hash_val += (uint32_t)2478261866;
            hash_val += hash_ptr(const_val->data.x_ptr.data.ref.pointee);
            return hash_val;
        case ConstPtrSpecialBaseArray:
            hash_val += (uint32_t)1764906839;
            hash_val += hash_ptr(const_val->data.x_ptr.data.base_array.array_val);
            hash_val += hash_size(const_val->data.x_ptr.data.base_array.elem_index);
            hash_val += const_val->data.x_ptr.data.base_array.is_cstr ? 1297263887 : 200363492;
            return hash_val;
        case ConstPtrSpecialBaseStruct:
            hash_val += (uint32_t)3518317043;
            hash_val += hash_ptr(const_val->data.x_ptr.data.base_struct.struct_val);
            hash_val += hash_size(const_val->data.x_ptr.data.base_struct.field_index);
            return hash_val;
        case ConstPtrSpecialBaseErrorUnionCode:
            hash_val += (uint32_t)2994743799;
            hash_val += hash_ptr(const_val->data.x_ptr.data.base_err_union_code.err_union_val);
            return hash_val;
        case ConstPtrSpecialBaseErrorUnionPayload:
            hash_val += (uint32_t)3456080131;
            hash_val += hash_ptr(const_val->data.x_ptr.data.base_err_union_payload.err_union_val);
            return hash_val;
        case ConstPtrSpecialBaseOptionalPayload:
            hash_val += (uint32_t)3163140517;
            hash_val += hash_ptr(const_val->data.x_ptr.data.base_optional_payload.optional_val);
            return hash_val;
        case ConstPtrSpecialHardCodedAddr:
            hash_val += (uint32_t)4048518294;
            hash_val += hash_size(const_val->data.x_ptr.data.hard_coded_addr.addr);
            return hash_val;
        case ConstPtrSpecialDiscard:
            hash_val += 2010123162;
            return hash_val;
        case ConstPtrSpecialFunction:
            hash_val += (uint32_t)2590901619;
            hash_val += hash_ptr(const_val->data.x_ptr.data.fn.fn_entry);
            return hash_val;
        case ConstPtrSpecialNull:
            hash_val += (uint32_t)1486246455;
            return hash_val;
    }
    zig_unreachable();
}

static uint32_t hash_const_val(ConstExprValue *const_val) {
    assert(const_val->special == ConstValSpecialStatic);
    switch (const_val->type->id) {
        case ZigTypeIdOpaque:
            zig_unreachable();
        case ZigTypeIdBool:
            return const_val->data.x_bool ? (uint32_t)127863866 : (uint32_t)215080464;
        case ZigTypeIdMetaType:
            return hash_ptr(const_val->data.x_type);
        case ZigTypeIdVoid:
            return (uint32_t)4149439618;
        case ZigTypeIdInt:
        case ZigTypeIdComptimeInt:
            {
                uint32_t result = 1331471175;
                for (size_t i = 0; i < const_val->data.x_bigint.digit_count; i += 1) {
                    uint64_t digit = bigint_ptr(&const_val->data.x_bigint)[i];
                    result ^= ((uint32_t)(digit >> 32)) ^ (uint32_t)(result);
                }
                return result;
            }
        case ZigTypeIdEnum:
            {
                uint32_t result = 31643936;
                for (size_t i = 0; i < const_val->data.x_enum_tag.digit_count; i += 1) {
                    uint64_t digit = bigint_ptr(&const_val->data.x_enum_tag)[i];
                    result ^= ((uint32_t)(digit >> 32)) ^ (uint32_t)(result);
                }
                return result;
            }
        case ZigTypeIdFloat:
            switch (const_val->type->data.floating.bit_count) {
                case 16:
                    {
                        uint16_t result;
                        static_assert(sizeof(result) == sizeof(const_val->data.x_f16), "");
                        memcpy(&result, &const_val->data.x_f16, sizeof(result));
                        return result * 65537u;
                    }
                case 32:
                    {
                        uint32_t result;
                        memcpy(&result, &const_val->data.x_f32, 4);
                        return result ^ 4084870010;
                    }
                case 64:
                    {
                        uint32_t ints[2];
                        memcpy(&ints[0], &const_val->data.x_f64, 8);
                        return ints[0] ^ ints[1] ^ 0x22ed43c6;
                    }
                case 128:
                    {
                        uint32_t ints[4];
                        memcpy(&ints[0], &const_val->data.x_f128, 16);
                        return ints[0] ^ ints[1] ^ ints[2] ^ ints[3] ^ 0xb5ffef27;
                    }
                default:
                    zig_unreachable();
            }
        case ZigTypeIdComptimeFloat:
            {
                float128_t f128 = bigfloat_to_f128(&const_val->data.x_bigfloat);
                uint32_t ints[4];
                memcpy(&ints[0], &f128, 16);
                return ints[0] ^ ints[1] ^ ints[2] ^ ints[3] ^ 0xed8b3dfb;
            }
        case ZigTypeIdArgTuple:
            return (uint32_t)const_val->data.x_arg_tuple.start_index * (uint32_t)281907309 +
                (uint32_t)const_val->data.x_arg_tuple.end_index * (uint32_t)2290442768;
        case ZigTypeIdFn:
            assert(const_val->data.x_ptr.mut == ConstPtrMutComptimeConst);
            assert(const_val->data.x_ptr.special == ConstPtrSpecialFunction);
            return 3677364617 ^ hash_ptr(const_val->data.x_ptr.data.fn.fn_entry);
        case ZigTypeIdPointer:
            return hash_const_val_ptr(const_val);
        case ZigTypeIdPromise:
            // TODO better hashing algorithm
            return 223048345;
        case ZigTypeIdUndefined:
            return 162837799;
        case ZigTypeIdNull:
            return 844854567;
        case ZigTypeIdArray:
            // TODO better hashing algorithm
            return 1166190605;
        case ZigTypeIdStruct:
            // TODO better hashing algorithm
            return 1532530855;
        case ZigTypeIdUnion:
            // TODO better hashing algorithm
            return 2709806591;
        case ZigTypeIdOptional:
            if (get_codegen_ptr_type(const_val->type) != nullptr) {
                return hash_const_val_ptr(const_val) * 1992916303;
            } else if (const_val->type->data.maybe.child_type->id == ZigTypeIdErrorSet) {
                return hash_const_val_error_set(const_val) * 3147031929;
            } else {
                if (const_val->data.x_optional) {
                    return hash_const_val(const_val->data.x_optional) * 1992916303;
                } else {
                    return 4016830364;
                }
            }
        case ZigTypeIdErrorUnion:
            // TODO better hashing algorithm
            return 3415065496;
        case ZigTypeIdErrorSet:
            return hash_const_val_error_set(const_val);
        case ZigTypeIdNamespace:
            return hash_ptr(const_val->data.x_import);
        case ZigTypeIdVector:
            // TODO better hashing algorithm
            return 3647867726;
        case ZigTypeIdBoundFn:
        case ZigTypeIdInvalid:
        case ZigTypeIdUnreachable:
            zig_unreachable();
    }
    zig_unreachable();
}

uint32_t generic_fn_type_id_hash(GenericFnTypeId *id) {
    uint32_t result = 0;
    result += hash_ptr(id->fn_entry);
    for (size_t i = 0; i < id->param_count; i += 1) {
        ConstExprValue *generic_param = &id->params[i];
        if (generic_param->special != ConstValSpecialRuntime) {
            result += hash_const_val(generic_param);
            result += hash_ptr(generic_param->type);
        }
    }
    return result;
}

bool generic_fn_type_id_eql(GenericFnTypeId *a, GenericFnTypeId *b) {
    assert(a->fn_entry);
    if (a->fn_entry != b->fn_entry) return false;
    if (a->param_count != b->param_count) return false;
    for (size_t i = 0; i < a->param_count; i += 1) {
        ConstExprValue *a_val = &a->params[i];
        ConstExprValue *b_val = &b->params[i];
        if (a_val->type != b_val->type) return false;
        if (a_val->special != ConstValSpecialRuntime && b_val->special != ConstValSpecialRuntime) {
            assert(a_val->special == ConstValSpecialStatic);
            assert(b_val->special == ConstValSpecialStatic);
            if (!const_values_equal(a->fn_entry->codegen, a_val, b_val)) {
                return false;
            }
        } else {
            assert(a_val->special == ConstValSpecialRuntime && b_val->special == ConstValSpecialRuntime);
        }
    }
    return true;
}

static bool can_mutate_comptime_var_state(ConstExprValue *value) {
    assert(value != nullptr);
    switch (value->type->id) {
        case ZigTypeIdInvalid:
            zig_unreachable();
        case ZigTypeIdMetaType:
        case ZigTypeIdVoid:
        case ZigTypeIdBool:
        case ZigTypeIdUnreachable:
        case ZigTypeIdInt:
        case ZigTypeIdVector:
        case ZigTypeIdFloat:
        case ZigTypeIdComptimeFloat:
        case ZigTypeIdComptimeInt:
        case ZigTypeIdUndefined:
        case ZigTypeIdNull:
        case ZigTypeIdNamespace:
        case ZigTypeIdBoundFn:
        case ZigTypeIdFn:
        case ZigTypeIdOpaque:
        case ZigTypeIdPromise:
        case ZigTypeIdErrorSet:
        case ZigTypeIdEnum:
            return false;

        case ZigTypeIdPointer:
            return value->data.x_ptr.mut == ConstPtrMutComptimeVar;

        case ZigTypeIdArray:
            if (value->type->data.array.len == 0)
                return false;
            switch (value->data.x_array.special) {
                case ConstArraySpecialUndef:
                case ConstArraySpecialBuf:
                    return false;
                case ConstArraySpecialNone:
                    for (uint32_t i = 0; i < value->type->data.array.len; i += 1) {
                        if (can_mutate_comptime_var_state(&value->data.x_array.data.s_none.elements[i]))
                            return true;
                    }
                    return false;
            }
            zig_unreachable();
        case ZigTypeIdStruct:
            for (uint32_t i = 0; i < value->type->data.structure.src_field_count; i += 1) {
                if (can_mutate_comptime_var_state(&value->data.x_struct.fields[i]))
                    return true;
            }
            return false;

        case ZigTypeIdOptional:
            if (get_codegen_ptr_type(value->type) != nullptr)
                return value->data.x_ptr.mut == ConstPtrMutComptimeVar;
            if (value->data.x_optional == nullptr)
                return false;
            return can_mutate_comptime_var_state(value->data.x_optional);

        case ZigTypeIdErrorUnion:
            if (value->data.x_err_union.error_set->data.x_err_set != nullptr)
                return false;
            assert(value->data.x_err_union.payload != nullptr);
            return can_mutate_comptime_var_state(value->data.x_err_union.payload);

        case ZigTypeIdUnion:
            return can_mutate_comptime_var_state(value->data.x_union.payload);

        case ZigTypeIdArgTuple:
            zig_panic("TODO var args at comptime is currently not supported");
    }
    zig_unreachable();
}

static bool return_type_is_cacheable(ZigType *return_type) {
    switch (return_type->id) {
        case ZigTypeIdInvalid:
            zig_unreachable();
        case ZigTypeIdMetaType:
        case ZigTypeIdVoid:
        case ZigTypeIdBool:
        case ZigTypeIdUnreachable:
        case ZigTypeIdInt:
        case ZigTypeIdFloat:
        case ZigTypeIdComptimeFloat:
        case ZigTypeIdComptimeInt:
        case ZigTypeIdUndefined:
        case ZigTypeIdNull:
        case ZigTypeIdNamespace:
        case ZigTypeIdBoundFn:
        case ZigTypeIdFn:
        case ZigTypeIdOpaque:
        case ZigTypeIdPromise:
        case ZigTypeIdErrorSet:
        case ZigTypeIdEnum:
        case ZigTypeIdPointer:
        case ZigTypeIdVector:
            return true;

        case ZigTypeIdArray:
        case ZigTypeIdStruct:
        case ZigTypeIdUnion:
            return false;

        case ZigTypeIdOptional:
            return return_type_is_cacheable(return_type->data.maybe.child_type);

        case ZigTypeIdErrorUnion:
            return return_type_is_cacheable(return_type->data.error_union.payload_type);

        case ZigTypeIdArgTuple:
            zig_panic("TODO var args at comptime is currently not supported");
    }
    zig_unreachable();
}

bool fn_eval_cacheable(Scope *scope, ZigType *return_type) {
    if (!return_type_is_cacheable(return_type))
        return false;
    while (scope) {
        if (scope->id == ScopeIdVarDecl) {
            ScopeVarDecl *var_scope = (ScopeVarDecl *)scope;
            if (type_is_invalid(var_scope->var->var_type))
                return false;
            if (can_mutate_comptime_var_state(var_scope->var->const_value))
                return false;
        } else if (scope->id == ScopeIdFnDef) {
            return true;
        } else {
            zig_unreachable();
        }

        scope = scope->parent;
    }
    zig_unreachable();
}

uint32_t fn_eval_hash(Scope* scope) {
    uint32_t result = 0;
    while (scope) {
        if (scope->id == ScopeIdVarDecl) {
            ScopeVarDecl *var_scope = (ScopeVarDecl *)scope;
            result += hash_const_val(var_scope->var->const_value);
        } else if (scope->id == ScopeIdFnDef) {
            ScopeFnDef *fn_scope = (ScopeFnDef *)scope;
            result += hash_ptr(fn_scope->fn_entry);
            return result;
        } else {
            zig_unreachable();
        }

        scope = scope->parent;
    }
    zig_unreachable();
}

bool fn_eval_eql(Scope *a, Scope *b) {
    assert(a->codegen != nullptr);
    assert(b->codegen != nullptr);
    while (a && b) {
        if (a->id != b->id)
            return false;

        if (a->id == ScopeIdVarDecl) {
            ScopeVarDecl *a_var_scope = (ScopeVarDecl *)a;
            ScopeVarDecl *b_var_scope = (ScopeVarDecl *)b;
            if (a_var_scope->var->var_type != b_var_scope->var->var_type)
                return false;
            if (a_var_scope->var->var_type == a_var_scope->var->const_value->type &&
                b_var_scope->var->var_type == b_var_scope->var->const_value->type)
            {
                if (!const_values_equal(a->codegen, a_var_scope->var->const_value, b_var_scope->var->const_value))
                    return false;
            } else {
                zig_panic("TODO comptime ptr reinterpret for fn_eval_eql");
            }
        } else if (a->id == ScopeIdFnDef) {
            ScopeFnDef *a_fn_scope = (ScopeFnDef *)a;
            ScopeFnDef *b_fn_scope = (ScopeFnDef *)b;
            if (a_fn_scope->fn_entry != b_fn_scope->fn_entry)
                return false;

            return true;
        } else {
            zig_unreachable();
        }

        a = a->parent;
        b = b->parent;
    }
    return false;
}

// Whether the type has bits at runtime.
bool type_has_bits(ZigType *type_entry) {
    assert(type_entry);
    assert(!type_is_invalid(type_entry));
    assert(type_is_resolved(type_entry, ResolveStatusZeroBitsKnown));
    return !type_entry->zero_bits;
}

// Whether you can infer the value based solely on the type.
OnePossibleValue type_has_one_possible_value(CodeGen *g, ZigType *type_entry) {
    assert(type_entry != nullptr);

    if (type_entry->one_possible_value != OnePossibleValueInvalid)
        return type_entry->one_possible_value;

    Error err;
    if ((err = type_resolve(g, type_entry, ResolveStatusZeroBitsKnown)))
        return OnePossibleValueInvalid;
    switch (type_entry->id) {
        case ZigTypeIdInvalid:
            zig_unreachable();
        case ZigTypeIdOpaque:
        case ZigTypeIdComptimeFloat:
        case ZigTypeIdComptimeInt:
        case ZigTypeIdMetaType:
        case ZigTypeIdNamespace:
        case ZigTypeIdBoundFn:
        case ZigTypeIdArgTuple:
        case ZigTypeIdOptional:
        case ZigTypeIdFn:
        case ZigTypeIdBool:
        case ZigTypeIdFloat:
        case ZigTypeIdPromise:
        case ZigTypeIdErrorUnion:
            return OnePossibleValueNo;
        case ZigTypeIdUndefined:
        case ZigTypeIdNull:
        case ZigTypeIdVoid:
        case ZigTypeIdUnreachable:
            return OnePossibleValueYes;
        case ZigTypeIdArray:
            if (type_entry->data.array.len == 0)
                return OnePossibleValueYes;
            return type_has_one_possible_value(g, type_entry->data.array.child_type);
        case ZigTypeIdStruct:
            for (size_t i = 0; i < type_entry->data.structure.src_field_count; i += 1) {
                TypeStructField *field = &type_entry->data.structure.fields[i];
                switch (type_has_one_possible_value(g, field->type_entry)) {
                    case OnePossibleValueInvalid:
                        return OnePossibleValueInvalid;
                    case OnePossibleValueNo:
                        return OnePossibleValueNo;
                    case OnePossibleValueYes:
                        continue;
                }
            }
            return OnePossibleValueYes;
        case ZigTypeIdErrorSet:
        case ZigTypeIdEnum:
        case ZigTypeIdInt:
        case ZigTypeIdVector:
            return type_has_bits(type_entry) ? OnePossibleValueNo : OnePossibleValueYes;
        case ZigTypeIdPointer: {
            ZigType *elem_type = type_entry->data.pointer.child_type;
            // If the recursive function call asks, then we are not one possible value.
            type_entry->one_possible_value = OnePossibleValueNo;
            // Now update it to be the value of the recursive call.
            type_entry->one_possible_value = type_has_one_possible_value(g, elem_type);
            return type_entry->one_possible_value;
        }
        case ZigTypeIdUnion:
            if (type_entry->data.unionation.src_field_count > 1)
                return OnePossibleValueNo;
            return type_has_one_possible_value(g, type_entry->data.unionation.fields[0].type_entry);
    }
    zig_unreachable();
}

ReqCompTime type_requires_comptime(CodeGen *g, ZigType *type_entry) {
    Error err;
    if ((err = type_resolve(g, type_entry, ResolveStatusZeroBitsKnown)))
        return ReqCompTimeInvalid;
    switch (type_entry->id) {
        case ZigTypeIdInvalid:
        case ZigTypeIdOpaque:
            zig_unreachable();
        case ZigTypeIdComptimeFloat:
        case ZigTypeIdComptimeInt:
        case ZigTypeIdUndefined:
        case ZigTypeIdNull:
        case ZigTypeIdMetaType:
        case ZigTypeIdNamespace:
        case ZigTypeIdBoundFn:
        case ZigTypeIdArgTuple:
            return ReqCompTimeYes;
        case ZigTypeIdArray:
            return type_requires_comptime(g, type_entry->data.array.child_type);
        case ZigTypeIdStruct:
            return type_entry->data.structure.requires_comptime ? ReqCompTimeYes : ReqCompTimeNo;
        case ZigTypeIdUnion:
            return type_entry->data.unionation.requires_comptime ? ReqCompTimeYes : ReqCompTimeNo;
        case ZigTypeIdOptional:
            return type_requires_comptime(g, type_entry->data.maybe.child_type);
        case ZigTypeIdErrorUnion:
            return type_requires_comptime(g, type_entry->data.error_union.payload_type);
        case ZigTypeIdPointer:
            if (type_entry->data.pointer.child_type->id == ZigTypeIdOpaque) {
                return ReqCompTimeNo;
            } else {
                return type_requires_comptime(g, type_entry->data.pointer.child_type);
            }
        case ZigTypeIdFn:
            return type_entry->data.fn.is_generic ? ReqCompTimeYes : ReqCompTimeNo;
        case ZigTypeIdEnum:
        case ZigTypeIdErrorSet:
        case ZigTypeIdBool:
        case ZigTypeIdInt:
        case ZigTypeIdVector:
        case ZigTypeIdFloat:
        case ZigTypeIdVoid:
        case ZigTypeIdUnreachable:
        case ZigTypeIdPromise:
            return ReqCompTimeNo;
    }
    zig_unreachable();
}

void init_const_str_lit(CodeGen *g, ConstExprValue *const_val, Buf *str) {
    auto entry = g->string_literals_table.maybe_get(str);
    if (entry != nullptr) {
        *const_val = *entry->value;
        return;
    }

    const_val->special = ConstValSpecialStatic;
    const_val->type = get_array_type(g, g->builtin_types.entry_u8, buf_len(str));
    const_val->data.x_array.special = ConstArraySpecialBuf;
    const_val->data.x_array.data.s_buf = str;

    g->string_literals_table.put(str, const_val);
}

ConstExprValue *create_const_str_lit(CodeGen *g, Buf *str) {
    ConstExprValue *const_val = create_const_vals(1);
    init_const_str_lit(g, const_val, str);
    return const_val;
}

void init_const_c_str_lit(CodeGen *g, ConstExprValue *const_val, Buf *str) {
    // first we build the underlying array
    size_t len_with_null = buf_len(str) + 1;
    ConstExprValue *array_val = create_const_vals(1);
    array_val->special = ConstValSpecialStatic;
    array_val->type = get_array_type(g, g->builtin_types.entry_u8, len_with_null);
    // TODO buf optimization
    array_val->data.x_array.data.s_none.elements = create_const_vals(len_with_null);
    for (size_t i = 0; i < buf_len(str); i += 1) {
        ConstExprValue *this_char = &array_val->data.x_array.data.s_none.elements[i];
        this_char->special = ConstValSpecialStatic;
        this_char->type = g->builtin_types.entry_u8;
        bigint_init_unsigned(&this_char->data.x_bigint, (uint8_t)buf_ptr(str)[i]);
    }
    ConstExprValue *null_char = &array_val->data.x_array.data.s_none.elements[len_with_null - 1];
    null_char->special = ConstValSpecialStatic;
    null_char->type = g->builtin_types.entry_u8;
    bigint_init_unsigned(&null_char->data.x_bigint, 0);

    // then make the pointer point to it
    const_val->special = ConstValSpecialStatic;
    // TODO make this `[*]null u8` instead of `[*]u8`
    const_val->type = get_pointer_to_type_extra(g, g->builtin_types.entry_u8, true, false,
            PtrLenUnknown, 0, 0, 0);
    const_val->data.x_ptr.special = ConstPtrSpecialBaseArray;
    const_val->data.x_ptr.data.base_array.array_val = array_val;
    const_val->data.x_ptr.data.base_array.elem_index = 0;
    const_val->data.x_ptr.data.base_array.is_cstr = true;
}
ConstExprValue *create_const_c_str_lit(CodeGen *g, Buf *str) {
    ConstExprValue *const_val = create_const_vals(1);
    init_const_c_str_lit(g, const_val, str);
    return const_val;
}

void init_const_bigint(ConstExprValue *const_val, ZigType *type, const BigInt *bigint) {
    const_val->special = ConstValSpecialStatic;
    const_val->type = type;
    bigint_init_bigint(&const_val->data.x_bigint, bigint);
}

ConstExprValue *create_const_bigint(ZigType *type, const BigInt *bigint) {
    ConstExprValue *const_val = create_const_vals(1);
    init_const_bigint(const_val, type, bigint);
    return const_val;
}


void init_const_unsigned_negative(ConstExprValue *const_val, ZigType *type, uint64_t x, bool negative) {
    const_val->special = ConstValSpecialStatic;
    const_val->type = type;
    bigint_init_unsigned(&const_val->data.x_bigint, x);
    const_val->data.x_bigint.is_negative = negative;
}

ConstExprValue *create_const_unsigned_negative(ZigType *type, uint64_t x, bool negative) {
    ConstExprValue *const_val = create_const_vals(1);
    init_const_unsigned_negative(const_val, type, x, negative);
    return const_val;
}

void init_const_usize(CodeGen *g, ConstExprValue *const_val, uint64_t x) {
    return init_const_unsigned_negative(const_val, g->builtin_types.entry_usize, x, false);
}

ConstExprValue *create_const_usize(CodeGen *g, uint64_t x) {
    return create_const_unsigned_negative(g->builtin_types.entry_usize, x, false);
}

void init_const_signed(ConstExprValue *const_val, ZigType *type, int64_t x) {
    const_val->special = ConstValSpecialStatic;
    const_val->type = type;
    bigint_init_signed(&const_val->data.x_bigint, x);
}

ConstExprValue *create_const_signed(ZigType *type, int64_t x) {
    ConstExprValue *const_val = create_const_vals(1);
    init_const_signed(const_val, type, x);
    return const_val;
}

void init_const_float(ConstExprValue *const_val, ZigType *type, double value) {
    const_val->special = ConstValSpecialStatic;
    const_val->type = type;
    if (type->id == ZigTypeIdComptimeFloat) {
        bigfloat_init_64(&const_val->data.x_bigfloat, value);
    } else if (type->id == ZigTypeIdFloat) {
        switch (type->data.floating.bit_count) {
            case 16:
                const_val->data.x_f16 = zig_double_to_f16(value);
                break;
            case 32:
                const_val->data.x_f32 = value;
                break;
            case 64:
                const_val->data.x_f64 = value;
                break;
            case 128:
                // if we need this, we should add a function that accepts a float128_t param
                zig_unreachable();
            default:
                zig_unreachable();
        }
    } else {
        zig_unreachable();
    }
}

ConstExprValue *create_const_float(ZigType *type, double value) {
    ConstExprValue *const_val = create_const_vals(1);
    init_const_float(const_val, type, value);
    return const_val;
}

void init_const_enum(ConstExprValue *const_val, ZigType *type, const BigInt *tag) {
    const_val->special = ConstValSpecialStatic;
    const_val->type = type;
    bigint_init_bigint(&const_val->data.x_enum_tag, tag);
}

ConstExprValue *create_const_enum(ZigType *type, const BigInt *tag) {
    ConstExprValue *const_val = create_const_vals(1);
    init_const_enum(const_val, type, tag);
    return const_val;
}


void init_const_bool(CodeGen *g, ConstExprValue *const_val, bool value) {
    const_val->special = ConstValSpecialStatic;
    const_val->type = g->builtin_types.entry_bool;
    const_val->data.x_bool = value;
}

ConstExprValue *create_const_bool(CodeGen *g, bool value) {
    ConstExprValue *const_val = create_const_vals(1);
    init_const_bool(g, const_val, value);
    return const_val;
}

void init_const_runtime(ConstExprValue *const_val, ZigType *type) {
    const_val->special = ConstValSpecialRuntime;
    const_val->type = type;
}

ConstExprValue *create_const_runtime(ZigType *type) {
    ConstExprValue *const_val = create_const_vals(1);
    init_const_runtime(const_val, type);
    return const_val;
}

void init_const_type(CodeGen *g, ConstExprValue *const_val, ZigType *type_value) {
    const_val->special = ConstValSpecialStatic;
    const_val->type = g->builtin_types.entry_type;
    const_val->data.x_type = type_value;
}

ConstExprValue *create_const_type(CodeGen *g, ZigType *type_value) {
    ConstExprValue *const_val = create_const_vals(1);
    init_const_type(g, const_val, type_value);
    return const_val;
}

void init_const_slice(CodeGen *g, ConstExprValue *const_val, ConstExprValue *array_val,
        size_t start, size_t len, bool is_const)
{
    assert(array_val->type->id == ZigTypeIdArray);

    ZigType *ptr_type = get_pointer_to_type_extra(g, array_val->type->data.array.child_type,
            is_const, false, PtrLenUnknown, 0, 0, 0);

    const_val->special = ConstValSpecialStatic;
    const_val->type = get_slice_type(g, ptr_type);
    const_val->data.x_struct.fields = create_const_vals(2);

    init_const_ptr_array(g, &const_val->data.x_struct.fields[slice_ptr_index], array_val, start, is_const,
            PtrLenUnknown);
    init_const_usize(g, &const_val->data.x_struct.fields[slice_len_index], len);
}

ConstExprValue *create_const_slice(CodeGen *g, ConstExprValue *array_val, size_t start, size_t len, bool is_const) {
    ConstExprValue *const_val = create_const_vals(1);
    init_const_slice(g, const_val, array_val, start, len, is_const);
    return const_val;
}

void init_const_ptr_array(CodeGen *g, ConstExprValue *const_val, ConstExprValue *array_val,
        size_t elem_index, bool is_const, PtrLen ptr_len)
{
    assert(array_val->type->id == ZigTypeIdArray);
    ZigType *child_type = array_val->type->data.array.child_type;

    const_val->special = ConstValSpecialStatic;
    const_val->type = get_pointer_to_type_extra(g, child_type, is_const, false,
            ptr_len, 0, 0, 0);
    const_val->data.x_ptr.special = ConstPtrSpecialBaseArray;
    const_val->data.x_ptr.data.base_array.array_val = array_val;
    const_val->data.x_ptr.data.base_array.elem_index = elem_index;
}

ConstExprValue *create_const_ptr_array(CodeGen *g, ConstExprValue *array_val, size_t elem_index, bool is_const,
        PtrLen ptr_len)
{
    ConstExprValue *const_val = create_const_vals(1);
    init_const_ptr_array(g, const_val, array_val, elem_index, is_const, ptr_len);
    return const_val;
}

void init_const_ptr_ref(CodeGen *g, ConstExprValue *const_val, ConstExprValue *pointee_val, bool is_const) {
    const_val->special = ConstValSpecialStatic;
    const_val->type = get_pointer_to_type(g, pointee_val->type, is_const);
    const_val->data.x_ptr.special = ConstPtrSpecialRef;
    const_val->data.x_ptr.data.ref.pointee = pointee_val;
}

ConstExprValue *create_const_ptr_ref(CodeGen *g, ConstExprValue *pointee_val, bool is_const) {
    ConstExprValue *const_val = create_const_vals(1);
    init_const_ptr_ref(g, const_val, pointee_val, is_const);
    return const_val;
}

void init_const_ptr_hard_coded_addr(CodeGen *g, ConstExprValue *const_val, ZigType *pointee_type,
        size_t addr, bool is_const)
{
    const_val->special = ConstValSpecialStatic;
    const_val->type = get_pointer_to_type(g, pointee_type, is_const);
    const_val->data.x_ptr.special = ConstPtrSpecialHardCodedAddr;
    const_val->data.x_ptr.data.hard_coded_addr.addr = addr;
}

ConstExprValue *create_const_ptr_hard_coded_addr(CodeGen *g, ZigType *pointee_type,
        size_t addr, bool is_const)
{
    ConstExprValue *const_val = create_const_vals(1);
    init_const_ptr_hard_coded_addr(g, const_val, pointee_type, addr, is_const);
    return const_val;
}

void init_const_arg_tuple(CodeGen *g, ConstExprValue *const_val, size_t arg_index_start, size_t arg_index_end) {
    const_val->special = ConstValSpecialStatic;
    const_val->type = g->builtin_types.entry_arg_tuple;
    const_val->data.x_arg_tuple.start_index = arg_index_start;
    const_val->data.x_arg_tuple.end_index = arg_index_end;
}

ConstExprValue *create_const_arg_tuple(CodeGen *g, size_t arg_index_start, size_t arg_index_end) {
    ConstExprValue *const_val = create_const_vals(1);
    init_const_arg_tuple(g, const_val, arg_index_start, arg_index_end);
    return const_val;
}


void init_const_undefined(CodeGen *g, ConstExprValue *const_val) {
    Error err;
    ZigType *wanted_type = const_val->type;
    if (wanted_type->id == ZigTypeIdArray) {
        const_val->special = ConstValSpecialStatic;
        const_val->data.x_array.special = ConstArraySpecialUndef;
    } else if (wanted_type->id == ZigTypeIdStruct) {
        if ((err = ensure_complete_type(g, wanted_type))) {
            return;
        }

        const_val->special = ConstValSpecialStatic;
        size_t field_count = wanted_type->data.structure.src_field_count;
        const_val->data.x_struct.fields = create_const_vals(field_count);
        for (size_t i = 0; i < field_count; i += 1) {
            ConstExprValue *field_val = &const_val->data.x_struct.fields[i];
            field_val->type = wanted_type->data.structure.fields[i].type_entry;
            assert(field_val->type);
            init_const_undefined(g, field_val);
            ConstParent *parent = get_const_val_parent(g, field_val);
            if (parent != nullptr) {
                parent->id = ConstParentIdStruct;
                parent->data.p_struct.struct_val = const_val;
                parent->data.p_struct.field_index = i;
            }
        }
    } else {
        const_val->special = ConstValSpecialUndef;
    }
}

ConstExprValue *create_const_vals(size_t count) {
    ConstGlobalRefs *global_refs = allocate<ConstGlobalRefs>(count);
    ConstExprValue *vals = allocate<ConstExprValue>(count);
    for (size_t i = 0; i < count; i += 1) {
        vals[i].global_refs = &global_refs[i];
    }
    return vals;
}

Error ensure_complete_type(CodeGen *g, ZigType *type_entry) {
    return type_resolve(g, type_entry, ResolveStatusSizeKnown);
}

Error type_resolve(CodeGen *g, ZigType *ty, ResolveStatus status) {
    if (type_is_invalid(ty))
        return ErrorSemanticAnalyzeFail;
    switch (status) {
        case ResolveStatusUnstarted:
            return ErrorNone;
        case ResolveStatusInvalid:
            zig_unreachable();
        case ResolveStatusZeroBitsKnown:
            if (ty->id == ZigTypeIdStruct) {
                return resolve_struct_zero_bits(g, ty);
            } else if (ty->id == ZigTypeIdEnum) {
                return resolve_enum_zero_bits(g, ty);
            } else if (ty->id == ZigTypeIdUnion) {
                return resolve_union_zero_bits(g, ty);
            }
            return ErrorNone;
        case ResolveStatusAlignmentKnown:
            if (ty->id == ZigTypeIdStruct) {
                return resolve_struct_alignment(g, ty);
            } else if (ty->id == ZigTypeIdEnum) {
                return resolve_enum_zero_bits(g, ty);
            } else if (ty->id == ZigTypeIdUnion) {
                return resolve_union_zero_bits(g, ty);
            }
            return ErrorNone;
        case ResolveStatusSizeKnown:
            if (ty->id == ZigTypeIdStruct) {
                return resolve_struct_type(g, ty);
            } else if (ty->id == ZigTypeIdEnum) {
                return resolve_enum_type(g, ty);
            } else if (ty->id == ZigTypeIdUnion) {
                return resolve_union_type(g, ty);
            }
            return ErrorNone;
    }
    zig_unreachable();
}

bool ir_get_var_is_comptime(ZigVar *var) {
    // The is_comptime field can be left null, which means not comptime.
    if (var->is_comptime == nullptr)
        return false;
    // When the is_comptime field references an instruction that has to get analyzed, this
    // is the value.
    if (var->is_comptime->child != nullptr) {
        assert(var->is_comptime->child->value.type->id == ZigTypeIdBool);
        return var->is_comptime->child->value.data.x_bool;
    }
    // As an optimization, is_comptime values which are constant are allowed
    // to be omitted from analysis. In this case, there is no child instruction
    // and we simply look at the unanalyzed const parent instruction.
    assert(var->is_comptime->value.type->id == ZigTypeIdBool);
    return var->is_comptime->value.data.x_bool;
}

bool const_values_equal_ptr(ConstExprValue *a, ConstExprValue *b) {
    if (a->data.x_ptr.special != b->data.x_ptr.special)
        return false;
    if (a->data.x_ptr.mut != b->data.x_ptr.mut)
        return false;
    switch (a->data.x_ptr.special) {
        case ConstPtrSpecialInvalid:
            zig_unreachable();
        case ConstPtrSpecialRef:
            if (a->data.x_ptr.data.ref.pointee != b->data.x_ptr.data.ref.pointee)
                return false;
            return true;
        case ConstPtrSpecialBaseArray:
            if (a->data.x_ptr.data.base_array.array_val != b->data.x_ptr.data.base_array.array_val &&
                a->data.x_ptr.data.base_array.array_val->global_refs !=
                b->data.x_ptr.data.base_array.array_val->global_refs)
            {
                return false;
            }
            if (a->data.x_ptr.data.base_array.elem_index != b->data.x_ptr.data.base_array.elem_index)
                return false;
            if (a->data.x_ptr.data.base_array.is_cstr != b->data.x_ptr.data.base_array.is_cstr)
                return false;
            return true;
        case ConstPtrSpecialBaseStruct:
            if (a->data.x_ptr.data.base_struct.struct_val != b->data.x_ptr.data.base_struct.struct_val &&
                a->data.x_ptr.data.base_struct.struct_val->global_refs !=
                b->data.x_ptr.data.base_struct.struct_val->global_refs)
            {
                return false;
            }
            if (a->data.x_ptr.data.base_struct.field_index != b->data.x_ptr.data.base_struct.field_index)
                return false;
            return true;
        case ConstPtrSpecialBaseErrorUnionCode:
            if (a->data.x_ptr.data.base_err_union_code.err_union_val !=
                b->data.x_ptr.data.base_err_union_code.err_union_val &&
                a->data.x_ptr.data.base_err_union_code.err_union_val->global_refs !=
                b->data.x_ptr.data.base_err_union_code.err_union_val->global_refs)
            {
                return false;
            }
            return true;
        case ConstPtrSpecialBaseErrorUnionPayload:
            if (a->data.x_ptr.data.base_err_union_payload.err_union_val !=
                b->data.x_ptr.data.base_err_union_payload.err_union_val &&
                a->data.x_ptr.data.base_err_union_payload.err_union_val->global_refs !=
                b->data.x_ptr.data.base_err_union_payload.err_union_val->global_refs)
            {
                return false;
            }
            return true;
        case ConstPtrSpecialBaseOptionalPayload:
            if (a->data.x_ptr.data.base_optional_payload.optional_val !=
                b->data.x_ptr.data.base_optional_payload.optional_val &&
                a->data.x_ptr.data.base_optional_payload.optional_val->global_refs !=
                b->data.x_ptr.data.base_optional_payload.optional_val->global_refs)
            {
                return false;
            }
            return true;
        case ConstPtrSpecialHardCodedAddr:
            if (a->data.x_ptr.data.hard_coded_addr.addr != b->data.x_ptr.data.hard_coded_addr.addr)
                return false;
            return true;
        case ConstPtrSpecialDiscard:
            return true;
        case ConstPtrSpecialFunction:
            return a->data.x_ptr.data.fn.fn_entry == b->data.x_ptr.data.fn.fn_entry;
        case ConstPtrSpecialNull:
            return true;
    }
    zig_unreachable();
}

static bool const_values_equal_array(CodeGen *g, ConstExprValue *a, ConstExprValue *b, size_t len) {
    assert(a->data.x_array.special != ConstArraySpecialUndef);
    assert(b->data.x_array.special != ConstArraySpecialUndef);
    if (a->data.x_array.special == ConstArraySpecialBuf &&
        b->data.x_array.special == ConstArraySpecialBuf)
    {
        return buf_eql_buf(a->data.x_array.data.s_buf, b->data.x_array.data.s_buf);
    }
    expand_undef_array(g, a);
    expand_undef_array(g, b);

    ConstExprValue *a_elems = a->data.x_array.data.s_none.elements;
    ConstExprValue *b_elems = b->data.x_array.data.s_none.elements;

    for (size_t i = 0; i < len; i += 1) {
        if (!const_values_equal(g, &a_elems[i], &b_elems[i]))
            return false;
    }

    return true;
}

bool const_values_equal(CodeGen *g, ConstExprValue *a, ConstExprValue *b) {
    assert(a->type->id == b->type->id);
    assert(a->special == ConstValSpecialStatic);
    assert(b->special == ConstValSpecialStatic);
    switch (a->type->id) {
        case ZigTypeIdOpaque:
            zig_unreachable();
        case ZigTypeIdEnum:
            return bigint_cmp(&a->data.x_enum_tag, &b->data.x_enum_tag) == CmpEQ;
        case ZigTypeIdUnion: {
            ConstUnionValue *union1 = &a->data.x_union;
            ConstUnionValue *union2 = &b->data.x_union;

            if (bigint_cmp(&union1->tag, &union2->tag) == CmpEQ) {
                TypeUnionField *field = find_union_field_by_tag(a->type, &union1->tag);
                assert(field != nullptr);
                if (type_has_bits(field->type_entry)) {
                    zig_panic("TODO const expr analyze union field value for equality");
                } else {
                    return true;
                }
            }
            return false;
        }
        case ZigTypeIdMetaType:
            return a->data.x_type == b->data.x_type;
        case ZigTypeIdVoid:
            return true;
        case ZigTypeIdErrorSet:
            return a->data.x_err_set->value == b->data.x_err_set->value;
        case ZigTypeIdBool:
            return a->data.x_bool == b->data.x_bool;
        case ZigTypeIdFloat:
            assert(a->type->data.floating.bit_count == b->type->data.floating.bit_count);
            switch (a->type->data.floating.bit_count) {
                case 16:
                    return f16_eq(a->data.x_f16, b->data.x_f16);
                case 32:
                    return a->data.x_f32 == b->data.x_f32;
                case 64:
                    return a->data.x_f64 == b->data.x_f64;
                case 128:
                    return f128M_eq(&a->data.x_f128, &b->data.x_f128);
                default:
                    zig_unreachable();
            }
        case ZigTypeIdComptimeFloat:
            return bigfloat_cmp(&a->data.x_bigfloat, &b->data.x_bigfloat) == CmpEQ;
        case ZigTypeIdInt:
        case ZigTypeIdComptimeInt:
            return bigint_cmp(&a->data.x_bigint, &b->data.x_bigint) == CmpEQ;
        case ZigTypeIdPointer:
        case ZigTypeIdFn:
            return const_values_equal_ptr(a, b);
        case ZigTypeIdVector:
            assert(a->type->data.vector.len == b->type->data.vector.len);
            return const_values_equal_array(g, a, b, a->type->data.vector.len);
        case ZigTypeIdArray: {
            assert(a->type->data.array.len == b->type->data.array.len);
            return const_values_equal_array(g, a, b, a->type->data.array.len);
        }
        case ZigTypeIdStruct:
            for (size_t i = 0; i < a->type->data.structure.src_field_count; i += 1) {
                ConstExprValue *field_a = &a->data.x_struct.fields[i];
                ConstExprValue *field_b = &b->data.x_struct.fields[i];
                if (!const_values_equal(g, field_a, field_b))
                    return false;
            }
            return true;
        case ZigTypeIdUndefined:
            zig_panic("TODO");
        case ZigTypeIdNull:
            zig_panic("TODO");
        case ZigTypeIdOptional:
            if (get_codegen_ptr_type(a->type) != nullptr)
                return const_values_equal_ptr(a, b);
            if (a->data.x_optional == nullptr || b->data.x_optional == nullptr) {
                return (a->data.x_optional == nullptr && b->data.x_optional == nullptr);
            } else {
                return const_values_equal(g, a->data.x_optional, b->data.x_optional);
            }
        case ZigTypeIdErrorUnion:
            zig_panic("TODO");
        case ZigTypeIdNamespace:
            return a->data.x_import == b->data.x_import;
        case ZigTypeIdArgTuple:
            return a->data.x_arg_tuple.start_index == b->data.x_arg_tuple.start_index &&
                   a->data.x_arg_tuple.end_index == b->data.x_arg_tuple.end_index;
        case ZigTypeIdBoundFn:
        case ZigTypeIdInvalid:
        case ZigTypeIdUnreachable:
        case ZigTypeIdPromise:
            zig_unreachable();
    }
    zig_unreachable();
}

void eval_min_max_value_int(CodeGen *g, ZigType *int_type, BigInt *bigint, bool is_max) {
    assert(int_type->id == ZigTypeIdInt);
    if (int_type->data.integral.bit_count == 0) {
        bigint_init_unsigned(bigint, 0);
        return;
    }
    if (is_max) {
        // is_signed=true   (1 << (bit_count - 1)) - 1
        // is_signed=false  (1 << (bit_count - 0)) - 1
        BigInt one = {0};
        bigint_init_unsigned(&one, 1);

        size_t shift_amt = int_type->data.integral.bit_count - (int_type->data.integral.is_signed ? 1 : 0);
        BigInt bit_count_bi = {0};
        bigint_init_unsigned(&bit_count_bi, shift_amt);

        BigInt shifted_bi = {0};
        bigint_shl(&shifted_bi, &one, &bit_count_bi);

        bigint_sub(bigint, &shifted_bi, &one);
    } else if (int_type->data.integral.is_signed) {
        // - (1 << (bit_count - 1))
        BigInt one = {0};
        bigint_init_unsigned(&one, 1);

        BigInt bit_count_bi = {0};
        bigint_init_unsigned(&bit_count_bi, int_type->data.integral.bit_count - 1);

        BigInt shifted_bi = {0};
        bigint_shl(&shifted_bi, &one, &bit_count_bi);

        bigint_negate(bigint, &shifted_bi);
    } else {
        bigint_init_unsigned(bigint, 0);
    }
}

void eval_min_max_value(CodeGen *g, ZigType *type_entry, ConstExprValue *const_val, bool is_max) {
    if (type_entry->id == ZigTypeIdInt) {
        const_val->special = ConstValSpecialStatic;
        eval_min_max_value_int(g, type_entry, &const_val->data.x_bigint, is_max);
    } else if (type_entry->id == ZigTypeIdBool) {
        const_val->special = ConstValSpecialStatic;
        const_val->data.x_bool = is_max;
    } else if (type_entry->id == ZigTypeIdVoid) {
        // nothing to do
    } else {
        zig_unreachable();
    }
}

static void render_const_val_ptr(CodeGen *g, Buf *buf, ConstExprValue *const_val, ZigType *type_entry) {
    assert(type_entry->id == ZigTypeIdPointer);

    if (type_entry->data.pointer.child_type->id == ZigTypeIdOpaque) {
        buf_append_buf(buf, &type_entry->name);
        return;
    }

    switch (const_val->data.x_ptr.special) {
        case ConstPtrSpecialInvalid:
            zig_unreachable();
        case ConstPtrSpecialRef:
        case ConstPtrSpecialBaseStruct:
        case ConstPtrSpecialBaseErrorUnionCode:
        case ConstPtrSpecialBaseErrorUnionPayload:
        case ConstPtrSpecialBaseOptionalPayload:
            buf_appendf(buf, "*");
            // TODO we need a source node for const_ptr_pointee because it can generate compile errors
            render_const_value(g, buf, const_ptr_pointee(nullptr, g, const_val, nullptr));
            return;
        case ConstPtrSpecialBaseArray:
            if (const_val->data.x_ptr.data.base_array.is_cstr) {
                buf_appendf(buf, "*(c str lit)");
                return;
            } else {
                buf_appendf(buf, "*");
                // TODO we need a source node for const_ptr_pointee because it can generate compile errors
                render_const_value(g, buf, const_ptr_pointee(nullptr, g, const_val, nullptr));
                return;
            }
        case ConstPtrSpecialHardCodedAddr:
            buf_appendf(buf, "(%s)(%" ZIG_PRI_x64 ")", buf_ptr(&type_entry->name),
                    const_val->data.x_ptr.data.hard_coded_addr.addr);
            return;
        case ConstPtrSpecialDiscard:
            buf_append_str(buf, "*_");
            return;
        case ConstPtrSpecialFunction:
            {
                ZigFn *fn_entry = const_val->data.x_ptr.data.fn.fn_entry;
                buf_appendf(buf, "@ptrCast(%s, %s)", buf_ptr(&const_val->type->name), buf_ptr(&fn_entry->symbol_name));
                return;
            }
        case ConstPtrSpecialNull:
            buf_append_str(buf, "null");
            return;
    }
    zig_unreachable();
}

static void render_const_val_err_set(CodeGen *g, Buf *buf, ConstExprValue *const_val, ZigType *type_entry) {
    if (const_val->data.x_err_set == nullptr) {
        buf_append_str(buf, "null");
    } else {
        buf_appendf(buf, "%s.%s", buf_ptr(&type_entry->name), buf_ptr(&const_val->data.x_err_set->name));
    }
}

static void render_const_val_array(CodeGen *g, Buf *buf, Buf *type_name, ConstExprValue *const_val, uint64_t start, uint64_t len) {
    ConstArrayValue *array = &const_val->data.x_array;
    switch (array->special) {
        case ConstArraySpecialUndef:
            buf_append_str(buf, "undefined");
            return;
        case ConstArraySpecialBuf: {
            Buf *array_buf = array->data.s_buf;
            const char *base = &buf_ptr(array_buf)[start];
            assert(start + len <= buf_len(array_buf));

            buf_append_char(buf, '"');
            for (size_t i = 0; i < len; i += 1) {
                uint8_t c = base[i];
                if (c == '"') {
                    buf_append_str(buf, "\\\"");
                } else {
                    buf_append_char(buf, c);
                }
            }
            buf_append_char(buf, '"');
            return;
        }
        case ConstArraySpecialNone: {
            ConstExprValue *base = &array->data.s_none.elements[start];
            assert(start + len <= const_val->type->data.array.len);

            buf_appendf(buf, "%s{", buf_ptr(type_name));
            for (uint64_t i = 0; i < len; i += 1) {
                if (i != 0) buf_appendf(buf, ",");
                render_const_value(g, buf, &base[i]);
            }
            buf_appendf(buf, "}");
            return;
        }
    }
    zig_unreachable();
}

void render_const_value(CodeGen *g, Buf *buf, ConstExprValue *const_val) {
    switch (const_val->special) {
        case ConstValSpecialRuntime:
            buf_appendf(buf, "(runtime value)");
            return;
        case ConstValSpecialUndef:
            buf_appendf(buf, "undefined");
            return;
        case ConstValSpecialStatic:
            break;
    }
    assert(const_val->type);

    ZigType *type_entry = const_val->type;
    switch (type_entry->id) {
        case ZigTypeIdOpaque:
            zig_unreachable();
        case ZigTypeIdInvalid:
            buf_appendf(buf, "(invalid)");
            return;
        case ZigTypeIdVoid:
            buf_appendf(buf, "{}");
            return;
        case ZigTypeIdComptimeFloat:
            bigfloat_append_buf(buf, &const_val->data.x_bigfloat);
            return;
        case ZigTypeIdFloat:
            switch (type_entry->data.floating.bit_count) {
                case 16:
                    buf_appendf(buf, "%f", zig_f16_to_double(const_val->data.x_f16));
                    return;
                case 32:
                    buf_appendf(buf, "%f", const_val->data.x_f32);
                    return;
                case 64:
                    buf_appendf(buf, "%f", const_val->data.x_f64);
                    return;
                case 128:
                    {
                        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));
                        // TODO actual f128 printing to decimal
                        int len = snprintf(buf_ptr(buf) + old_len, extra_len, "%f", double_value);
                        assert(len > 0);
                        buf_resize(buf, old_len + len);
                        return;
                    }
                default:
                    zig_unreachable();
            }
        case ZigTypeIdComptimeInt:
        case ZigTypeIdInt:
            bigint_append_buf(buf, &const_val->data.x_bigint, 10);
            return;
        case ZigTypeIdMetaType:
            buf_appendf(buf, "%s", buf_ptr(&const_val->data.x_type->name));
            return;
        case ZigTypeIdUnreachable:
            buf_appendf(buf, "unreachable");
            return;
        case ZigTypeIdBool:
            {
                const char *value = const_val->data.x_bool ? "true" : "false";
                buf_appendf(buf, "%s", value);
                return;
            }
        case ZigTypeIdFn:
            {
                assert(const_val->data.x_ptr.mut == ConstPtrMutComptimeConst);
                assert(const_val->data.x_ptr.special == ConstPtrSpecialFunction);
                ZigFn *fn_entry = const_val->data.x_ptr.data.fn.fn_entry;
                buf_appendf(buf, "%s", buf_ptr(&fn_entry->symbol_name));
                return;
            }
        case ZigTypeIdPointer:
            return render_const_val_ptr(g, buf, const_val, type_entry);
        case ZigTypeIdArray: {
            uint64_t len = type_entry->data.array.len;
            render_const_val_array(g, buf, &type_entry->name, const_val, 0, len);
            return;
        }
        case ZigTypeIdVector: {
            uint32_t len = type_entry->data.vector.len;
            render_const_val_array(g, buf, &type_entry->name, const_val, 0, len);
            return;
        }
        case ZigTypeIdNull:
            {
                buf_appendf(buf, "null");
                return;
            }
        case ZigTypeIdUndefined:
            {
                buf_appendf(buf, "undefined");
                return;
            }
        case ZigTypeIdOptional:
            {
                if (get_codegen_ptr_type(const_val->type) != nullptr)
                    return render_const_val_ptr(g, buf, const_val, type_entry->data.maybe.child_type);
                if (type_entry->data.maybe.child_type->id == ZigTypeIdErrorSet)
                    return render_const_val_err_set(g, buf, const_val, type_entry->data.maybe.child_type);
                if (const_val->data.x_optional) {
                    render_const_value(g, buf, const_val->data.x_optional);
                } else {
                    buf_appendf(buf, "null");
                }
                return;
            }
        case ZigTypeIdNamespace:
            {
                ImportTableEntry *import = const_val->data.x_import;
                if (import->c_import_node) {
                    buf_appendf(buf, "(namespace from C import)");
                } else {
                    buf_appendf(buf, "(namespace: %s)", buf_ptr(import->path));
                }
                return;
            }
        case ZigTypeIdBoundFn:
            {
                ZigFn *fn_entry = const_val->data.x_bound_fn.fn;
                buf_appendf(buf, "(bound fn %s)", buf_ptr(&fn_entry->symbol_name));
                return;
            }
        case ZigTypeIdStruct:
            {
                if (is_slice(type_entry)) {
                    ConstExprValue *len_val = &const_val->data.x_struct.fields[slice_len_index];
                    size_t len = bigint_as_unsigned(&len_val->data.x_bigint);

                    ConstExprValue *ptr_val = &const_val->data.x_struct.fields[slice_ptr_index];
                    if (ptr_val->special == ConstValSpecialUndef) {
                        assert(len == 0);
                        buf_appendf(buf, "((%s)(undefined))[0..0]", buf_ptr(&type_entry->name));
                        return;
                    }
                    assert(ptr_val->data.x_ptr.special == ConstPtrSpecialBaseArray);
                    ConstExprValue *array = ptr_val->data.x_ptr.data.base_array.array_val;
                    size_t start = ptr_val->data.x_ptr.data.base_array.elem_index;

                    render_const_val_array(g, buf, &type_entry->name, array, start, len);
                } else {
                    buf_appendf(buf, "(struct %s constant)", buf_ptr(&type_entry->name));
                }
                return;
            }
        case ZigTypeIdEnum:
            {
                TypeEnumField *field = find_enum_field_by_tag(type_entry, &const_val->data.x_enum_tag);
                buf_appendf(buf, "%s.%s", buf_ptr(&type_entry->name), buf_ptr(field->name));
                return;
            }
        case ZigTypeIdErrorUnion:
            {
                buf_appendf(buf, "%s(", buf_ptr(&type_entry->name));
                ErrorTableEntry *err_set = const_val->data.x_err_union.error_set->data.x_err_set;
                if (err_set == nullptr) {
                    render_const_value(g, buf, const_val->data.x_err_union.payload);
                } else {
                    buf_appendf(buf, "%s.%s", buf_ptr(&type_entry->data.error_union.err_set_type->name),
                            buf_ptr(&err_set->name));
                }
                buf_appendf(buf, ")");
                return;
            }
        case ZigTypeIdUnion:
            {
                const BigInt *tag = &const_val->data.x_union.tag;
                TypeUnionField *field = find_union_field_by_tag(type_entry, tag);
                buf_appendf(buf, "%s { .%s = ", buf_ptr(&type_entry->name), buf_ptr(field->name));
                render_const_value(g, buf, const_val->data.x_union.payload);
                buf_append_str(buf, "}");
                return;
            }
        case ZigTypeIdErrorSet:
            return render_const_val_err_set(g, buf, const_val, type_entry);
        case ZigTypeIdArgTuple:
            {
                buf_appendf(buf, "(args value)");
                return;
            }
        case ZigTypeIdPromise:
            zig_unreachable();
    }
    zig_unreachable();
}

ZigType *make_int_type(CodeGen *g, bool is_signed, uint32_t size_in_bits) {
    assert(size_in_bits <= 65535);
    ZigType *entry = new_type_table_entry(ZigTypeIdInt);
    entry->type_ref = (size_in_bits == 0) ? LLVMVoidType() : LLVMIntType(size_in_bits);
    entry->zero_bits = (size_in_bits == 0);

    const char u_or_i = is_signed ? 'i' : 'u';
    buf_resize(&entry->name, 0);
    buf_appendf(&entry->name, "%c%" PRIu32, u_or_i, size_in_bits);

    unsigned dwarf_tag;
    if (is_signed) {
        if (size_in_bits == 8) {
            dwarf_tag = ZigLLVMEncoding_DW_ATE_signed_char();
        } else {
            dwarf_tag = ZigLLVMEncoding_DW_ATE_signed();
        }
    } else {
        if (size_in_bits == 8) {
            dwarf_tag = ZigLLVMEncoding_DW_ATE_unsigned_char();
        } else {
            dwarf_tag = ZigLLVMEncoding_DW_ATE_unsigned();
        }
    }

    uint64_t debug_size_in_bits = (size_in_bits == 0) ?
        0 : (8*LLVMStoreSizeOfType(g->target_data_ref, entry->type_ref));
    entry->di_type = ZigLLVMCreateDebugBasicType(g->dbuilder, buf_ptr(&entry->name), debug_size_in_bits, dwarf_tag);
    entry->data.integral.is_signed = is_signed;
    entry->data.integral.bit_count = size_in_bits;
    return entry;
}

uint32_t type_id_hash(TypeId x) {
    switch (x.id) {
        case ZigTypeIdInvalid:
        case ZigTypeIdOpaque:
        case ZigTypeIdMetaType:
        case ZigTypeIdVoid:
        case ZigTypeIdBool:
        case ZigTypeIdUnreachable:
        case ZigTypeIdFloat:
        case ZigTypeIdStruct:
        case ZigTypeIdComptimeFloat:
        case ZigTypeIdComptimeInt:
        case ZigTypeIdUndefined:
        case ZigTypeIdNull:
        case ZigTypeIdOptional:
        case ZigTypeIdErrorSet:
        case ZigTypeIdEnum:
        case ZigTypeIdUnion:
        case ZigTypeIdFn:
        case ZigTypeIdNamespace:
        case ZigTypeIdBoundFn:
        case ZigTypeIdArgTuple:
        case ZigTypeIdPromise:
            zig_unreachable();
        case ZigTypeIdErrorUnion:
            return hash_ptr(x.data.error_union.err_set_type) ^ hash_ptr(x.data.error_union.payload_type);
        case ZigTypeIdPointer:
            return hash_ptr(x.data.pointer.child_type) +
                ((x.data.pointer.ptr_len == PtrLenSingle) ? (uint32_t)1120226602 : (uint32_t)3200913342) +
                (x.data.pointer.is_const ? (uint32_t)2749109194 : (uint32_t)4047371087) +
                (x.data.pointer.is_volatile ? (uint32_t)536730450 : (uint32_t)1685612214) +
                (x.data.pointer.allow_zero ? (uint32_t)3324284834 : (uint32_t)3584904923) +
                (((uint32_t)x.data.pointer.alignment) ^ (uint32_t)0x777fbe0e) +
                (((uint32_t)x.data.pointer.bit_offset_in_host) ^ (uint32_t)2639019452) +
                (((uint32_t)x.data.pointer.host_int_bytes) ^ (uint32_t)529908881);
        case ZigTypeIdArray:
            return hash_ptr(x.data.array.child_type) +
                ((uint32_t)x.data.array.size ^ (uint32_t)2122979968);
        case ZigTypeIdInt:
            return (x.data.integer.is_signed ? (uint32_t)2652528194 : (uint32_t)163929201) +
                    (((uint32_t)x.data.integer.bit_count) ^ (uint32_t)2998081557);
        case ZigTypeIdVector:
            return hash_ptr(x.data.vector.elem_type) * (x.data.vector.len * 526582681);
    }
    zig_unreachable();
}

bool type_id_eql(TypeId a, TypeId b) {
    if (a.id != b.id)
        return false;
    switch (a.id) {
        case ZigTypeIdInvalid:
        case ZigTypeIdMetaType:
        case ZigTypeIdVoid:
        case ZigTypeIdBool:
        case ZigTypeIdUnreachable:
        case ZigTypeIdFloat:
        case ZigTypeIdStruct:
        case ZigTypeIdComptimeFloat:
        case ZigTypeIdComptimeInt:
        case ZigTypeIdUndefined:
        case ZigTypeIdNull:
        case ZigTypeIdOptional:
        case ZigTypeIdPromise:
        case ZigTypeIdErrorSet:
        case ZigTypeIdEnum:
        case ZigTypeIdUnion:
        case ZigTypeIdFn:
        case ZigTypeIdNamespace:
        case ZigTypeIdBoundFn:
        case ZigTypeIdArgTuple:
        case ZigTypeIdOpaque:
            zig_unreachable();
        case ZigTypeIdErrorUnion:
            return a.data.error_union.err_set_type == b.data.error_union.err_set_type &&
                a.data.error_union.payload_type == b.data.error_union.payload_type;

        case ZigTypeIdPointer:
            return a.data.pointer.child_type == b.data.pointer.child_type &&
                a.data.pointer.ptr_len == b.data.pointer.ptr_len &&
                a.data.pointer.is_const == b.data.pointer.is_const &&
                a.data.pointer.is_volatile == b.data.pointer.is_volatile &&
                a.data.pointer.allow_zero == b.data.pointer.allow_zero &&
                a.data.pointer.alignment == b.data.pointer.alignment &&
                a.data.pointer.bit_offset_in_host == b.data.pointer.bit_offset_in_host &&
                a.data.pointer.host_int_bytes == b.data.pointer.host_int_bytes;
        case ZigTypeIdArray:
            return a.data.array.child_type == b.data.array.child_type &&
                a.data.array.size == b.data.array.size;
        case ZigTypeIdInt:
            return a.data.integer.is_signed == b.data.integer.is_signed &&
                a.data.integer.bit_count == b.data.integer.bit_count;
        case ZigTypeIdVector:
            return a.data.vector.elem_type == b.data.vector.elem_type &&
                a.data.vector.len == b.data.vector.len;
    }
    zig_unreachable();
}

uint32_t zig_llvm_fn_key_hash(ZigLLVMFnKey x) {
    switch (x.id) {
        case ZigLLVMFnIdCtz:
            return (uint32_t)(x.data.ctz.bit_count) * (uint32_t)810453934;
        case ZigLLVMFnIdClz:
            return (uint32_t)(x.data.clz.bit_count) * (uint32_t)2428952817;
        case ZigLLVMFnIdPopCount:
            return (uint32_t)(x.data.clz.bit_count) * (uint32_t)101195049;
        case ZigLLVMFnIdFloor:
            return (uint32_t)(x.data.floating.bit_count) * (uint32_t)1899859168;
        case ZigLLVMFnIdCeil:
            return (uint32_t)(x.data.floating.bit_count) * (uint32_t)1953839089;
        case ZigLLVMFnIdSqrt:
            return (uint32_t)(x.data.floating.bit_count) * (uint32_t)2225366385;
        case ZigLLVMFnIdBswap:
            return (uint32_t)(x.data.bswap.bit_count) * (uint32_t)3661994335;
        case ZigLLVMFnIdBitReverse:
            return (uint32_t)(x.data.bit_reverse.bit_count) * (uint32_t)2621398431;
        case ZigLLVMFnIdOverflowArithmetic:
            return ((uint32_t)(x.data.overflow_arithmetic.bit_count) * 87135777) +
                ((uint32_t)(x.data.overflow_arithmetic.add_sub_mul) * 31640542) +
                ((uint32_t)(x.data.overflow_arithmetic.is_signed) ? 1062315172 : 314955820) +
                x.data.overflow_arithmetic.vector_len * 1435156945;
    }
    zig_unreachable();
}

bool zig_llvm_fn_key_eql(ZigLLVMFnKey a, ZigLLVMFnKey b) {
    if (a.id != b.id)
        return false;
    switch (a.id) {
        case ZigLLVMFnIdCtz:
            return a.data.ctz.bit_count == b.data.ctz.bit_count;
        case ZigLLVMFnIdClz:
            return a.data.clz.bit_count == b.data.clz.bit_count;
        case ZigLLVMFnIdPopCount:
            return a.data.pop_count.bit_count == b.data.pop_count.bit_count;
        case ZigLLVMFnIdBswap:
            return a.data.bswap.bit_count == b.data.bswap.bit_count;
        case ZigLLVMFnIdBitReverse:
            return a.data.bit_reverse.bit_count == b.data.bit_reverse.bit_count;
        case ZigLLVMFnIdFloor:
        case ZigLLVMFnIdCeil:
        case ZigLLVMFnIdSqrt:
            return a.data.floating.bit_count == b.data.floating.bit_count;
        case ZigLLVMFnIdOverflowArithmetic:
            return (a.data.overflow_arithmetic.bit_count == b.data.overflow_arithmetic.bit_count) &&
                (a.data.overflow_arithmetic.add_sub_mul == b.data.overflow_arithmetic.add_sub_mul) &&
                (a.data.overflow_arithmetic.is_signed == b.data.overflow_arithmetic.is_signed) &&
                (a.data.overflow_arithmetic.vector_len == b.data.overflow_arithmetic.vector_len);
    }
    zig_unreachable();
}

// Canonicalize the array value as ConstArraySpecialNone
void expand_undef_array(CodeGen *g, ConstExprValue *const_val) {
    size_t elem_count;
    ZigType *elem_type;
    if (const_val->type->id == ZigTypeIdArray) {
        elem_count = const_val->type->data.array.len;
        elem_type = const_val->type->data.array.child_type;
    } else if (const_val->type->id == ZigTypeIdVector) {
        elem_count = const_val->type->data.vector.len;
        elem_type = const_val->type->data.vector.elem_type;
    } else {
        zig_unreachable();
    }
    if (const_val->special == ConstValSpecialUndef) {
        const_val->special = ConstValSpecialStatic;
        const_val->data.x_array.special = ConstArraySpecialUndef;
    }
    switch (const_val->data.x_array.special) {
        case ConstArraySpecialNone:
            return;
        case ConstArraySpecialUndef: {
            const_val->data.x_array.special = ConstArraySpecialNone;
            const_val->data.x_array.data.s_none.elements = create_const_vals(elem_count);
            for (size_t i = 0; i < elem_count; i += 1) {
                ConstExprValue *element_val = &const_val->data.x_array.data.s_none.elements[i];
                element_val->type = elem_type;
                init_const_undefined(g, element_val);
                element_val->parent.id = ConstParentIdArray;
                element_val->parent.data.p_array.array_val = const_val;
                element_val->parent.data.p_array.elem_index = i;
            }
            return;
        }
        case ConstArraySpecialBuf: {
            Buf *buf = const_val->data.x_array.data.s_buf;
            // If we're doing this it means that we are potentially modifying the data,
            // so we can't have it be in the string literals table
            g->string_literals_table.maybe_remove(buf);

            const_val->data.x_array.special = ConstArraySpecialNone;
            assert(elem_count == buf_len(buf));
            const_val->data.x_array.data.s_none.elements = create_const_vals(elem_count);
            for (size_t i = 0; i < elem_count; i += 1) {
                ConstExprValue *this_char = &const_val->data.x_array.data.s_none.elements[i];
                this_char->special = ConstValSpecialStatic;
                this_char->type = g->builtin_types.entry_u8;
                bigint_init_unsigned(&this_char->data.x_bigint, (uint8_t)buf_ptr(buf)[i]);
                this_char->parent.id = ConstParentIdArray;
                this_char->parent.data.p_array.array_val = const_val;
                this_char->parent.data.p_array.elem_index = i;
            }
            return;
        }
    }
    zig_unreachable();
}

// Deprecated. Reference the parent field directly.
ConstParent *get_const_val_parent(CodeGen *g, ConstExprValue *value) {
    return &value->parent;
}

static const ZigTypeId all_type_ids[] = {
    ZigTypeIdMetaType,
    ZigTypeIdVoid,
    ZigTypeIdBool,
    ZigTypeIdUnreachable,
    ZigTypeIdInt,
    ZigTypeIdFloat,
    ZigTypeIdPointer,
    ZigTypeIdArray,
    ZigTypeIdStruct,
    ZigTypeIdComptimeFloat,
    ZigTypeIdComptimeInt,
    ZigTypeIdUndefined,
    ZigTypeIdNull,
    ZigTypeIdOptional,
    ZigTypeIdErrorUnion,
    ZigTypeIdErrorSet,
    ZigTypeIdEnum,
    ZigTypeIdUnion,
    ZigTypeIdFn,
    ZigTypeIdNamespace,
    ZigTypeIdBoundFn,
    ZigTypeIdArgTuple,
    ZigTypeIdOpaque,
    ZigTypeIdPromise,
    ZigTypeIdVector,
};

ZigTypeId type_id_at_index(size_t index) {
    assert(index < array_length(all_type_ids));
    return all_type_ids[index];
}

size_t type_id_len() {
    return array_length(all_type_ids);
}

size_t type_id_index(ZigType *entry) {
    switch (entry->id) {
        case ZigTypeIdInvalid:
            zig_unreachable();
        case ZigTypeIdMetaType:
            return 0;
        case ZigTypeIdVoid:
            return 1;
        case ZigTypeIdBool:
            return 2;
        case ZigTypeIdUnreachable:
            return 3;
        case ZigTypeIdInt:
            return 4;
        case ZigTypeIdFloat:
            return 5;
        case ZigTypeIdPointer:
            return 6;
        case ZigTypeIdArray:
            return 7;
        case ZigTypeIdStruct:
            if (entry->data.structure.is_slice)
                return 6;
            return 8;
        case ZigTypeIdComptimeFloat:
            return 9;
        case ZigTypeIdComptimeInt:
            return 10;
        case ZigTypeIdUndefined:
            return 11;
        case ZigTypeIdNull:
            return 12;
        case ZigTypeIdOptional:
            return 13;
        case ZigTypeIdErrorUnion:
            return 14;
        case ZigTypeIdErrorSet:
            return 15;
        case ZigTypeIdEnum:
            return 16;
        case ZigTypeIdUnion:
            return 17;
        case ZigTypeIdFn:
            return 18;
        case ZigTypeIdNamespace:
            return 19;
        case ZigTypeIdBoundFn:
            return 20;
        case ZigTypeIdArgTuple:
            return 21;
        case ZigTypeIdOpaque:
            return 22;
        case ZigTypeIdPromise:
            return 23;
        case ZigTypeIdVector:
            return 24;
    }
    zig_unreachable();
}

const char *type_id_name(ZigTypeId id) {
    switch (id) {
        case ZigTypeIdInvalid:
            zig_unreachable();
        case ZigTypeIdMetaType:
            return "Type";
        case ZigTypeIdVoid:
            return "Void";
        case ZigTypeIdBool:
            return "Bool";
        case ZigTypeIdUnreachable:
            return "NoReturn";
        case ZigTypeIdInt:
            return "Int";
        case ZigTypeIdFloat:
            return "Float";
        case ZigTypeIdPointer:
            return "Pointer";
        case ZigTypeIdArray:
            return "Array";
        case ZigTypeIdStruct:
            return "Struct";
        case ZigTypeIdComptimeFloat:
            return "ComptimeFloat";
        case ZigTypeIdComptimeInt:
            return "ComptimeInt";
        case ZigTypeIdUndefined:
            return "Undefined";
        case ZigTypeIdNull:
            return "Null";
        case ZigTypeIdOptional:
            return "Optional";
        case ZigTypeIdErrorUnion:
            return "ErrorUnion";
        case ZigTypeIdErrorSet:
            return "ErrorSet";
        case ZigTypeIdEnum:
            return "Enum";
        case ZigTypeIdUnion:
            return "Union";
        case ZigTypeIdFn:
            return "Fn";
        case ZigTypeIdNamespace:
            return "Namespace";
        case ZigTypeIdBoundFn:
            return "BoundFn";
        case ZigTypeIdArgTuple:
            return "ArgTuple";
        case ZigTypeIdOpaque:
            return "Opaque";
        case ZigTypeIdPromise:
            return "Promise";
        case ZigTypeIdVector:
            return "Vector";
    }
    zig_unreachable();
}

LinkLib *create_link_lib(Buf *name) {
    LinkLib *link_lib = allocate<LinkLib>(1);
    link_lib->name = name;
    return link_lib;
}

LinkLib *add_link_lib(CodeGen *g, Buf *name) {
    bool is_libc = buf_eql_str(name, "c");

    if (is_libc && g->libc_link_lib != nullptr)
        return g->libc_link_lib;

    for (size_t i = 0; i < g->link_libs_list.length; i += 1) {
        LinkLib *existing_lib = g->link_libs_list.at(i);
        if (buf_eql_buf(existing_lib->name, name)) {
            return existing_lib;
        }
    }

    LinkLib *link_lib = create_link_lib(name);
    g->link_libs_list.append(link_lib);

    if (is_libc)
        g->libc_link_lib = link_lib;

    return link_lib;
}

uint32_t get_abi_alignment(CodeGen *g, ZigType *type_entry) {
    assert(type_is_resolved(type_entry, ResolveStatusAlignmentKnown));
    if (type_entry->zero_bits) return 0;

    // We need to make this function work without requiring ensure_complete_type
    // so that we can have structs with fields that are pointers to their own type.
    if (type_entry->id == ZigTypeIdStruct) {
        assert(type_entry->data.structure.abi_alignment != 0);
        return type_entry->data.structure.abi_alignment;
    } else if (type_entry->id == ZigTypeIdUnion) {
        assert(type_entry->data.unionation.abi_alignment != 0);
        return type_entry->data.unionation.abi_alignment;
    } else if (type_entry->id == ZigTypeIdOpaque) {
        return 1;
    } else {
        uint32_t llvm_alignment = LLVMABIAlignmentOfType(g->target_data_ref, type_entry->type_ref);
        return llvm_alignment;
    }
}

ZigType *get_align_amt_type(CodeGen *g) {
    if (g->align_amt_type == nullptr) {
        // according to LLVM the maximum alignment is 1 << 29.
        g->align_amt_type = get_int_type(g, false, 29);
    }
    return g->align_amt_type;
}

uint32_t type_ptr_hash(const ZigType *ptr) {
    return hash_ptr((void*)ptr);
}

bool type_ptr_eql(const ZigType *a, const ZigType *b) {
    return a == b;
}

ConstExprValue *get_builtin_value(CodeGen *codegen, const char *name) {
    Tld *tld = codegen->compile_var_import->decls_scope->decl_table.get(buf_create_from_str(name));
    resolve_top_level_decl(codegen, tld, false, nullptr);
    assert(tld->id == TldIdVar);
    TldVar *tld_var = (TldVar *)tld;
    ConstExprValue *var_value = tld_var->var->const_value;
    assert(var_value != nullptr);
    return var_value;
}

bool type_is_global_error_set(ZigType *err_set_type) {
    assert(err_set_type->id == ZigTypeIdErrorSet);
    assert(err_set_type->data.error_set.infer_fn == nullptr);
    return err_set_type->data.error_set.err_count == UINT32_MAX;
}

uint32_t get_coro_frame_align_bytes(CodeGen *g) {
    uint32_t a = g->pointer_size_bytes * 2;
    // promises have at least alignment 8 so that we can have 3 extra bits when doing atomicrmw
    if (a < 8) a = 8;
    return a;
}

bool type_can_fail(ZigType *type_entry) {
    return type_entry->id == ZigTypeIdErrorUnion || type_entry->id == ZigTypeIdErrorSet;
}

bool fn_type_can_fail(FnTypeId *fn_type_id) {
    return type_can_fail(fn_type_id->return_type) || fn_type_id->cc == CallingConventionAsync;
}

// ErrorNone - result pointer has the type
// ErrorOverflow - an integer primitive type has too large a bit width
// ErrorPrimitiveTypeNotFound - result pointer unchanged
Error get_primitive_type(CodeGen *g, Buf *name, ZigType **result) {
    if (buf_len(name) >= 2) {
        uint8_t first_c = buf_ptr(name)[0];
        if (first_c == 'i' || first_c == 'u') {
            for (size_t i = 1; i < buf_len(name); i += 1) {
                uint8_t c = buf_ptr(name)[i];
                if (c < '0' || c > '9') {
                    goto not_integer;
                }
            }
            bool is_signed = (first_c == 'i');
            unsigned long int bit_count = strtoul(buf_ptr(name) + 1, nullptr, 10);
            // strtoul returns ULONG_MAX on errors, so this comparison catches that as well.
            if (bit_count >= 65536) return ErrorOverflow;
            *result = get_int_type(g, is_signed, bit_count);
            return ErrorNone;
        }
    }

not_integer:

    auto primitive_table_entry = g->primitive_type_table.maybe_get(name);
    if (primitive_table_entry == nullptr)
        return ErrorPrimitiveTypeNotFound;

    *result = primitive_table_entry->value;
    return ErrorNone;
}

Error file_fetch(CodeGen *g, Buf *resolved_path, Buf *contents) {
    if (g->enable_cache) {
        return cache_add_file_fetch(&g->cache_hash, resolved_path, contents);
    } else {
        return os_fetch_file_path(resolved_path, contents, false);
    }
}

X64CABIClass type_c_abi_x86_64_class(CodeGen *g, ZigType *ty) {
    size_t ty_size = type_size(g, ty);
    if (get_codegen_ptr_type(ty) != nullptr)
        return X64CABIClass_INTEGER;
    switch (ty->id) {
        case ZigTypeIdEnum:
        case ZigTypeIdInt:
        case ZigTypeIdBool:
            return X64CABIClass_INTEGER;
        case ZigTypeIdFloat:
        case ZigTypeIdVector:
            return X64CABIClass_SSE;
        case ZigTypeIdStruct: {
            // "If the size of an object is larger than four eightbytes, or it contains unaligned
            // fields, it has class MEMORY"
            if (ty_size > 32)
                return X64CABIClass_MEMORY;
            if (ty->data.structure.layout != ContainerLayoutExtern) {
                // TODO determine whether packed structs have any unaligned fields
                return X64CABIClass_Unknown;
            }
            // "If the size of the aggregate exceeds two eightbytes and the first eight-
            // byte isn’t SSE or any other eightbyte isn’t SSEUP, the whole argument
            // is passed in memory."
            if (ty_size > 16) {
                // Zig doesn't support vectors and large fp registers yet, so this will always
                // be memory.
                return X64CABIClass_MEMORY;
            }
            X64CABIClass working_class = X64CABIClass_Unknown;
            for (uint32_t i = 0; i < ty->data.structure.src_field_count; i += 1) {
                X64CABIClass field_class = type_c_abi_x86_64_class(g, ty->data.structure.fields->type_entry);
                if (field_class == X64CABIClass_Unknown)
                    return X64CABIClass_Unknown;
                if (i == 0 || field_class == X64CABIClass_MEMORY || working_class == X64CABIClass_SSE) {
                    working_class = field_class;
                }
            }
            return working_class;
        }
        case ZigTypeIdUnion: {
            // "If the size of an object is larger than four eightbytes, or it contains unaligned
            // fields, it has class MEMORY"
            if (ty_size > 32)
                return X64CABIClass_MEMORY;
            if (ty->data.unionation.layout != ContainerLayoutExtern)
                return X64CABIClass_MEMORY;
            // "If the size of the aggregate exceeds two eightbytes and the first eight-
            // byte isn’t SSE or any other eightbyte isn’t SSEUP, the whole argument
            // is passed in memory."
            if (ty_size > 16) {
                // Zig doesn't support vectors and large fp registers yet, so this will always
                // be memory.
                return X64CABIClass_MEMORY;
            }
            X64CABIClass working_class = X64CABIClass_Unknown;
            for (uint32_t i = 0; i < ty->data.unionation.src_field_count; i += 1) {
                X64CABIClass field_class = type_c_abi_x86_64_class(g, ty->data.unionation.fields->type_entry);
                if (field_class == X64CABIClass_Unknown)
                    return X64CABIClass_Unknown;
                if (i == 0 || field_class == X64CABIClass_MEMORY || working_class == X64CABIClass_SSE) {
                    working_class = field_class;
                }
            }
            return working_class;
        }
        default:
            return X64CABIClass_Unknown;
    }
}

// NOTE this does not depend on x86_64
bool type_is_c_abi_int(CodeGen *g, ZigType *ty) {
    return (ty->id == ZigTypeIdInt ||
        ty->id == ZigTypeIdFloat ||
        ty->id == ZigTypeIdBool ||
        ty->id == ZigTypeIdEnum ||
        ty->id == ZigTypeIdVoid ||
        ty->id == ZigTypeIdUnreachable ||
        get_codegen_ptr_type(ty) != nullptr);
}

uint32_t get_host_int_bytes(CodeGen *g, ZigType *struct_type, TypeStructField *field) {
    assert(struct_type->id == ZigTypeIdStruct);
    if (struct_type->data.structure.layout != ContainerLayoutPacked) {
        return 0;
    }
    LLVMTypeRef field_type = LLVMStructGetTypeAtIndex(struct_type->type_ref, field->gen_index);
    return LLVMStoreSizeOfType(g->target_data_ref, field_type);
}

Error ensure_const_val_repr(IrAnalyze *ira, CodeGen *codegen, AstNode *source_node,
        ConstExprValue *const_val, ZigType *wanted_type)
{
    ConstExprValue ptr_val = {};
    ptr_val.special = ConstValSpecialStatic;
    ptr_val.type = get_pointer_to_type(codegen, wanted_type, true);
    ptr_val.data.x_ptr.mut = ConstPtrMutComptimeConst;
    ptr_val.data.x_ptr.special = ConstPtrSpecialRef;
    ptr_val.data.x_ptr.data.ref.pointee = const_val;
    if (const_ptr_pointee(ira, codegen, &ptr_val, source_node) == nullptr)
        return ErrorSemanticAnalyzeFail;

    return ErrorNone;
}

const char *container_string(ContainerKind kind) {
    switch (kind) {
        case ContainerKindEnum: return "enum";
        case ContainerKindStruct: return "struct";
        case ContainerKindUnion: return "union";
    }
    zig_unreachable();
}

bool ptr_allows_addr_zero(ZigType *ptr_type) {
    if (ptr_type->id == ZigTypeIdPointer) {
        return ptr_type->data.pointer.allow_zero;
    } else if (ptr_type->id == ZigTypeIdOptional) {
        return true;
    }
    return false;
}
