Commit ce3c52471d

Andrew Kelley <superjoe30@gmail.com>
2016-10-12 05:45:33
IR if statements WIP
1 parent 77ae344
src/all_types.hpp
@@ -1409,13 +1409,17 @@ enum AtomicOrder {
 struct IrBasicBlock {
     ZigList<IrInstruction *> instruction_list;
     IrBasicBlock *other;
+    const char *name_hint;
+    size_t debug_id;
 };
 
 enum IrInstructionId {
     IrInstructionIdInvalid,
+    IrInstructionIdBr,
     IrInstructionIdCondBr,
     IrInstructionIdSwitchBr,
     IrInstructionIdPhi,
+    IrInstructionIdUnOp,
     IrInstructionIdBinOp,
     IrInstructionIdLoadVar,
     IrInstructionIdStoreVar,
@@ -1442,9 +1446,15 @@ struct IrInstruction {
 struct IrInstructionCondBr {
     IrInstruction base;
 
-    // If cond_inst_index == SIZE_MAX, then this is an unconditional branch.
-    size_t cond_inst_index;
-    size_t dest_basic_block_index;
+    IrInstruction *condition;
+    IrBasicBlock *then_block;
+    IrBasicBlock *else_block;
+};
+
+struct IrInstructionBr {
+    IrInstruction base;
+
+    IrBasicBlock *dest_block;
 };
 
 struct IrInstructionSwitchBrCase {
@@ -1464,11 +1474,35 @@ struct IrInstructionSwitchBr {
 struct IrInstructionPhi {
     IrInstruction base;
 
-    size_t incoming_block_count;
+    size_t incoming_count;
     IrBasicBlock **incoming_blocks;
     IrInstruction **incoming_values;
 };
 
+enum IrUnOp {
+    IrUnOpInvalid,
+    IrUnOpBoolNot,
+    IrUnOpBinNot,
+    IrUnOpNegation,
+    IrUnOpNegationWrap,
+    IrUnOpAddressOf,
+    IrUnOpConstAddressOf,
+    IrUnOpDereference,
+    IrUnOpError,
+    IrUnOpMaybe,
+    IrUnOpUnwrapError,
+    IrUnOpUnwrapMaybe,
+    IrUnOpErrorReturn,
+    IrUnOpMaybeReturn,
+};
+
+struct IrInstructionUnOp {
+    IrInstruction base;
+
+    IrUnOp op_id;
+    IrInstruction *value;
+};
+
 enum IrBinOp {
     IrBinOpInvalid,
     IrBinOpBoolOr,
@@ -1529,8 +1563,7 @@ struct IrInstructionCall {
 struct IrInstructionBuiltinCall {
     IrInstruction base;
 
-    BuiltinFnId fn_id;
-    size_t arg_count;
+    BuiltinFnEntry *fn;
     IrInstruction **args;
 };
 
src/analyze.cpp
@@ -1067,76 +1067,6 @@ static TypeTableEntry *analyze_fn_proto_type(CodeGen *g, ImportTableEntry *impor
     return get_fn_type(g, &fn_type_id, gen_debug_info);
 }
 
-static Buf *resolve_const_expr_str(CodeGen *g, ImportTableEntry *import, BlockContext *context, AstNode **node) {
-    TypeTableEntry *str_type = get_slice_type(g, g->builtin_types.entry_u8, true);
-    TypeTableEntry *resolved_type = analyze_expression(g, import, context, str_type, *node);
-
-    if (resolved_type->id == TypeTableEntryIdInvalid) {
-        return nullptr;
-    }
-
-    ConstExprValue *const_str_val = &get_resolved_expr(*node)->const_val;
-
-    if (!const_str_val->ok) {
-        add_node_error(g, *node, buf_sprintf("unable to evaluate constant expression"));
-        return nullptr;
-    }
-
-    ConstExprValue *ptr_field = const_str_val->data.x_struct.fields[0];
-    uint64_t len = ptr_field->data.x_ptr.len;
-    Buf *result = buf_alloc();
-    for (uint64_t i = 0; i < len; i += 1) {
-        ConstExprValue *char_val = ptr_field->data.x_ptr.ptr[i];
-        uint64_t big_c = char_val->data.x_bignum.data.x_uint;
-        assert(big_c <= UINT8_MAX);
-        uint8_t c = big_c;
-        buf_append_char(result, c);
-    }
-    return result;
-}
-
-static bool resolve_const_expr_bool(CodeGen *g, ImportTableEntry *import, BlockContext *context,
-        AstNode **node, bool *value)
-{
-    TypeTableEntry *resolved_type = analyze_expression(g, import, context, g->builtin_types.entry_bool, *node);
-
-    if (resolved_type->id == TypeTableEntryIdInvalid) {
-        return false;
-    }
-
-    ConstExprValue *const_bool_val = &get_resolved_expr(*node)->const_val;
-
-    if (!const_bool_val->ok) {
-        add_node_error(g, *node, buf_sprintf("unable to evaluate constant expression"));
-        return false;
-    }
-
-    *value = const_bool_val->data.x_bool;
-    return true;
-}
-
-static FnTableEntry *resolve_const_expr_fn(CodeGen *g, ImportTableEntry *import, BlockContext *context,
-        AstNode **node)
-{
-    TypeTableEntry *resolved_type = analyze_expression(g, import, context, nullptr, *node);
-
-    if (resolved_type->id == TypeTableEntryIdInvalid) {
-        return nullptr;
-    } else if (resolved_type->id == TypeTableEntryIdFn) {
-        ConstExprValue *const_val = &get_resolved_expr(*node)->const_val;
-
-        if (!const_val->ok) {
-            add_node_error(g, *node, buf_sprintf("unable to evaluate constant expression"));
-            return nullptr;
-        }
-
-        return const_val->data.x_fn;
-    } else {
-        add_node_error(g, *node, buf_sprintf("expected function, got '%s'", buf_ptr(&resolved_type->name)));
-        return nullptr;
-    }
-}
-
 static void resolve_function_proto(CodeGen *g, AstNode *node, FnTableEntry *fn_table_entry,
         ImportTableEntry *import, BlockContext *containing_context)
 {
@@ -3073,16 +3003,6 @@ static TypeTableEntry *resolve_expr_const_val_as_bool(CodeGen *g, AstNode *node,
     return g->builtin_types.entry_bool;
 }
 
-static TypeTableEntry *resolve_expr_const_val_as_non_null(CodeGen *g, AstNode *node,
-        TypeTableEntry *type, ConstExprValue *other_val)
-{
-    assert(other_val->ok);
-    Expr *expr = get_resolved_expr(node);
-    expr->const_val.ok = true;
-    expr->const_val.data.x_maybe = other_val;
-    return type;
-}
-
 static TypeTableEntry *resolve_expr_const_val_as_c_string_lit(CodeGen *g, AstNode *node, Buf *str) {
     Expr *expr = get_resolved_expr(node);
     expr->const_val.ok = true;
@@ -4432,33 +4352,6 @@ static TypeTableEntry *analyze_if_var_expr(CodeGen *g, ImportTableEntry *import,
             node, then_node, else_node, cond_is_const, cond_bool_val);
 }
 
-static TypeTableEntry *analyze_min_max_value(CodeGen *g, ImportTableEntry *import, BlockContext *context,
-        AstNode *node, const char *err_format, bool is_max)
-{
-    assert(node->type == NodeTypeFnCallExpr);
-    assert(node->data.fn_call_expr.params.length == 1);
-
-    AstNode *type_node = node->data.fn_call_expr.params.at(0);
-    TypeTableEntry *type_entry = analyze_type_expr(g, import, context, type_node);
-
-    if (type_entry->id == TypeTableEntryIdInvalid) {
-        return g->builtin_types.entry_invalid;
-    } else if (type_entry->id == TypeTableEntryIdInt) {
-        eval_min_max_value(g, type_entry, &get_resolved_expr(node)->const_val, is_max);
-        return g->builtin_types.entry_num_lit_int;
-    } else if (type_entry->id == TypeTableEntryIdFloat) {
-        eval_min_max_value(g, type_entry, &get_resolved_expr(node)->const_val, is_max);
-        return g->builtin_types.entry_num_lit_float;
-    } else if (type_entry->id == TypeTableEntryIdBool) {
-        eval_min_max_value(g, type_entry, &get_resolved_expr(node)->const_val, is_max);
-        return type_entry;
-    } else {
-        add_node_error(g, node,
-                buf_sprintf(err_format, buf_ptr(&type_entry->name)));
-        return g->builtin_types.entry_invalid;
-    }
-}
-
 bool type_is_codegen_pointer(TypeTableEntry *type) {
     if (type->id == TypeTableEntryIdPointer) return true;
     if (type->id == TypeTableEntryIdFn) return true;
@@ -4469,1185 +4362,205 @@ bool type_is_codegen_pointer(TypeTableEntry *type) {
     return false;
 }
 
-static TypeTableEntry *analyze_import(CodeGen *g, ImportTableEntry *import, BlockContext *context,
-        AstNode *node)
+static TypeTableEntry *bad_method_call(CodeGen *g, AstNode *node, TypeTableEntry *container_type,
+        TypeTableEntry *expected_param_type, FnTableEntry *fn_table_entry)
 {
-    assert(node->type == NodeTypeFnCallExpr);
-
-    if (context->fn_entry) {
-        add_node_error(g, node, buf_sprintf("@import invalid inside function bodies"));
-        return g->builtin_types.entry_invalid;
+    ErrorMsg *msg = add_node_error(g, node,
+        buf_sprintf("function called as method of '%s', but first parameter is of type '%s'",
+            buf_ptr(&container_type->name),
+            buf_ptr(&expected_param_type->name)));
+    if (fn_table_entry) {
+        add_error_note(g, msg, fn_table_entry->proto_node, buf_sprintf("function declared here"));
     }
+    return g->builtin_types.entry_invalid;
+}
 
-    AstNode *first_param_node = node->data.fn_call_expr.params.at(0);
-    Buf *import_target_str = resolve_const_expr_str(g, import, context, first_param_node->parent_field);
-    if (!import_target_str) {
-        return g->builtin_types.entry_invalid;
-    }
+// Before calling this function, set node->data.fn_call_expr.fn_table_entry if the function is known
+// at compile time. Otherwise this is a function pointer call.
+static TypeTableEntry *analyze_fn_call_ptr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
+        TypeTableEntry *expected_type, AstNode *node, TypeTableEntry *fn_type,
+        AstNode *struct_node)
+{
+    assert(node->type == NodeTypeFnCallExpr);
 
-    Buf *import_target_path;
-    Buf *search_dir;
-    assert(import->package);
-    PackageTableEntry *target_package;
-    auto package_entry = import->package->package_table.maybe_get(import_target_str);
-    if (package_entry) {
-        target_package = package_entry->value;
-        import_target_path = &target_package->root_src_path;
-        search_dir = &target_package->root_src_dir;
-    } else {
-        // try it as a filename
-        target_package = import->package;
-        import_target_path = import_target_str;
-        search_dir = &import->package->root_src_dir;
+    if (fn_type->id == TypeTableEntryIdInvalid) {
+        return fn_type;
     }
 
-    Buf full_path = BUF_INIT;
-    os_path_join(search_dir, import_target_path, &full_path);
+    // The function call might include inline parameters which we need to ignore according to the
+    // fn_type.
+    FnTableEntry *fn_table_entry = node->data.fn_call_expr.fn_entry;
+    AstNode *generic_proto_node = fn_table_entry ?
+        fn_table_entry->proto_node->data.fn_proto.generic_proto_node : nullptr;
 
-    Buf *import_code = buf_alloc();
-    Buf *abs_full_path = buf_alloc();
-    int err;
-    if ((err = os_path_real(&full_path, abs_full_path))) {
-        if (err == ErrorFileNotFound) {
-            add_node_error(g, node,
-                    buf_sprintf("unable to find '%s'", buf_ptr(import_target_path)));
-            return g->builtin_types.entry_invalid;
-        } else {
-            g->error_during_imports = true;
-            add_node_error(g, node,
-                    buf_sprintf("unable to open '%s': %s", buf_ptr(&full_path), err_str(err)));
-            return g->builtin_types.entry_invalid;
-        }
-    }
+    // count parameters
+    size_t struct_node_1_or_0 = struct_node ? 1 : 0;
+    size_t src_param_count = fn_type->data.fn.fn_type_id.param_count +
+        (generic_proto_node ? generic_proto_node->data.fn_proto.inline_arg_count : 0);
+    size_t call_param_count = node->data.fn_call_expr.params.length;
+    size_t expect_arg_count = src_param_count - struct_node_1_or_0;
 
-    auto import_entry = g->import_table.maybe_get(abs_full_path);
-    if (import_entry) {
-        return resolve_expr_const_val_as_import(g, node, import_entry->value);
-    }
+    bool ok_invocation = true;
 
-    if ((err = os_fetch_file_path(abs_full_path, import_code))) {
-        if (err == ErrorFileNotFound) {
-            add_node_error(g, node,
-                    buf_sprintf("unable to find '%s'", buf_ptr(import_target_path)));
-            return g->builtin_types.entry_invalid;
-        } else {
+    if (fn_type->data.fn.fn_type_id.is_var_args) {
+        if (call_param_count < expect_arg_count) {
+            ok_invocation = false;
             add_node_error(g, node,
-                    buf_sprintf("unable to open '%s': %s", buf_ptr(&full_path), err_str(err)));
-            return g->builtin_types.entry_invalid;
+                buf_sprintf("expected at least %zu arguments, got %zu", src_param_count, call_param_count));
         }
+    } else if (expect_arg_count != call_param_count) {
+        ok_invocation = false;
+        add_node_error(g, node,
+                buf_sprintf("expected %zu arguments, got %zu", expect_arg_count, call_param_count));
     }
-    ImportTableEntry *target_import = add_source_file(g, target_package,
-            abs_full_path, search_dir, import_target_path, import_code);
-
-    scan_decls(g, target_import, target_import->block_context, target_import->root);
 
-    return resolve_expr_const_val_as_import(g, node, target_import);
-}
+    bool all_args_const_expr = true;
 
-static TypeTableEntry *analyze_c_import(CodeGen *g, ImportTableEntry *parent_import,
-        BlockContext *parent_context, AstNode *node)
-{
-    assert(node->type == NodeTypeFnCallExpr);
+    if (struct_node) {
+        Expr *struct_expr = get_resolved_expr(struct_node);
+        ConstExprValue *struct_const_val = &struct_expr->const_val;
+        if (!struct_const_val->ok) {
+            all_args_const_expr = false;
+        }
 
-    if (parent_context->fn_entry) {
-        add_node_error(g, node, buf_sprintf("@c_import invalid inside function bodies"));
-        return g->builtin_types.entry_invalid;
+        FnTypeParamInfo *param_info = &fn_type->data.fn.fn_type_id.param_info[0];
+        TypeTableEntry *expected_param_type = param_info->type;
+        TypeTableEntry *container_bare_type = container_ref_type(struct_expr->type_entry);
+        if (is_container_ref(expected_param_type)) {
+            TypeTableEntry *param_bare_type = container_ref_type(expected_param_type);
+            if (param_bare_type != container_bare_type) {
+                return bad_method_call(g, node, container_bare_type, expected_param_type, fn_table_entry);
+            }
+        } else {
+            return bad_method_call(g, node, container_bare_type, expected_param_type, fn_table_entry);
+        }
     }
 
-    AstNode *block_node = node->data.fn_call_expr.params.at(0);
+    // analyze each parameter. in the case of a method, we already analyzed the
+    // first parameter in order to figure out which struct we were calling a method on.
+    size_t next_type_i = struct_node_1_or_0;
+    for (size_t call_i = 0; call_i < call_param_count; call_i += 1) {
+        size_t proto_i = call_i + struct_node_1_or_0;
+        AstNode **param_node = &node->data.fn_call_expr.params.at(call_i);
+        // determine the expected type for each parameter
+        TypeTableEntry *expected_param_type = nullptr;
+        if (proto_i < src_param_count) {
+            if (generic_proto_node &&
+                generic_proto_node->data.fn_proto.params.at(proto_i)->data.param_decl.is_inline)
+            {
+                continue;
+            }
 
-    BlockContext *child_context = new_block_context(node, parent_context);
-    child_context->c_import_buf = buf_alloc();
+            FnTypeParamInfo *param_info = &fn_type->data.fn.fn_type_id.param_info[next_type_i];
+            next_type_i += 1;
 
-    TypeTableEntry *resolved_type = analyze_expression(g, parent_import, child_context,
-            g->builtin_types.entry_void, block_node);
+            expected_param_type = param_info->type;
+        }
+        TypeTableEntry *param_type = analyze_expression(g, import, context, expected_param_type, *param_node);
+        if (param_type->id == TypeTableEntryIdInvalid) {
+            return param_type;
+        }
 
-    if (resolved_type->id == TypeTableEntryIdInvalid) {
-        return resolved_type;
+        ConstExprValue *const_arg_val = &get_resolved_expr(*param_node)->const_val;
+        if (!const_arg_val->ok) {
+            all_args_const_expr = false;
+        }
     }
 
-    find_libc_include_path(g);
-
-    ImportTableEntry *child_import = allocate<ImportTableEntry>(1);
-    child_import->c_import_node = node;
-
-    ZigList<ErrorMsg *> errors = {0};
+    TypeTableEntry *return_type = fn_type->data.fn.fn_type_id.return_type;
 
-    int err;
-    if ((err = parse_h_buf(child_import, &errors, child_context->c_import_buf, g, node))) {
-        zig_panic("unable to parse h file: %s\n", err_str(err));
+    if (return_type->id == TypeTableEntryIdInvalid) {
+        return return_type;
     }
 
-    if (errors.length > 0) {
-        ErrorMsg *parent_err_msg = add_node_error(g, node, buf_sprintf("C import failed"));
-        for (size_t i = 0; i < errors.length; i += 1) {
-            ErrorMsg *err_msg = errors.at(i);
-            err_msg_add_note(parent_err_msg, err_msg);
+    ConstExprValue *result_val = &get_resolved_expr(node)->const_val;
+    if (ok_invocation && fn_table_entry && fn_table_entry->is_pure && fn_table_entry->want_pure != WantPureFalse) {
+        if (fn_table_entry->anal_state == FnAnalStateReady) {
+            analyze_fn_body(g, fn_table_entry);
+            if (fn_table_entry->proto_node->data.fn_proto.skip) {
+                return g->builtin_types.entry_invalid;
+            }
+        }
+        if (all_args_const_expr) {
+            if (fn_table_entry->is_pure && fn_table_entry->anal_state == FnAnalStateComplete) {
+                if (eval_fn(g, node, fn_table_entry, result_val, 1000, struct_node)) {
+                    // function evaluation generated an error
+                    return g->builtin_types.entry_invalid;
+                }
+                return return_type;
+            }
         }
-
-        return g->builtin_types.entry_invalid;
     }
-
-    if (g->verbose) {
-        fprintf(stderr, "\nc_import:\n");
-        fprintf(stderr, "-----------\n");
-        ast_render(stderr, child_import->root, 4);
+    if (!ok_invocation || !fn_table_entry || !fn_table_entry->is_pure || fn_table_entry->want_pure == WantPureFalse) {
+        // calling an impure fn is impure
+        mark_impure_fn(g, context, node);
+        if (fn_table_entry && fn_table_entry->want_pure == WantPureTrue) {
+            return g->builtin_types.entry_invalid;
+        }
     }
 
-    child_import->di_file = parent_import->di_file;
-    child_import->block_context = new_block_context(child_import->root, nullptr);
+    // TODO
+    //if (handle_is_ptr(return_type)) {
+    //    if (context->fn_entry) {
+    //        context->fn_entry->cast_alloca_list.append(node);
+    //    } else if (!result_val->ok) {
+    //        add_node_error(g, node, buf_sprintf("unable to evaluate constant expression"));
+    //    }
+    //}
 
-    scan_decls(g, child_import, child_import->block_context, child_import->root);
-    return resolve_expr_const_val_as_import(g, node, child_import);
+    return return_type;
 }
 
-static TypeTableEntry *analyze_err_name(CodeGen *g, ImportTableEntry *import,
-        BlockContext *context, AstNode *node)
+static TypeTableEntry *analyze_fn_call_with_inline_args(CodeGen *g, ImportTableEntry *import,
+        BlockContext *parent_context, TypeTableEntry *expected_type, AstNode *call_node,
+        FnTableEntry *fn_table_entry, AstNode *struct_node)
 {
-    assert(node->type == NodeTypeFnCallExpr);
-
-    AstNode *err_value = node->data.fn_call_expr.params.at(0);
-    TypeTableEntry *resolved_type = analyze_expression(g, import, context,
-            g->builtin_types.entry_pure_error, err_value);
-
-    if (resolved_type->id == TypeTableEntryIdInvalid) {
-        return resolved_type;
-    }
-
-    g->generate_error_name_table = true;
+    assert(call_node->type == NodeTypeFnCallExpr);
+    assert(fn_table_entry);
 
-    TypeTableEntry *str_type = get_slice_type(g, g->builtin_types.entry_u8, true);
-    return str_type;
-}
+    AstNode *decl_node = fn_table_entry->proto_node;
 
-static TypeTableEntry *analyze_embed_file(CodeGen *g, ImportTableEntry *import,
-        BlockContext *context, AstNode *node)
-{
-    assert(node->type == NodeTypeFnCallExpr);
+    // count parameters
+    size_t struct_node_1_or_0 = (struct_node ? 1 : 0);
+    size_t src_param_count = decl_node->data.fn_proto.params.length;
+    size_t call_param_count = call_node->data.fn_call_expr.params.length;
 
-    AstNode **first_param_node = &node->data.fn_call_expr.params.at(0);
-    Buf *rel_file_path = resolve_const_expr_str(g, import, context, first_param_node);
-    if (!rel_file_path) {
+    if (src_param_count != call_param_count + struct_node_1_or_0) {
+        add_node_error(g, call_node,
+            buf_sprintf("expected %zu arguments, got %zu", src_param_count - struct_node_1_or_0, call_param_count));
         return g->builtin_types.entry_invalid;
     }
 
-    // figure out absolute path to resource
-    Buf source_dir_path = BUF_INIT;
-    os_path_dirname(import->path, &source_dir_path);
+    size_t inline_or_var_type_arg_count = decl_node->data.fn_proto.inline_or_var_type_arg_count;
+    assert(inline_or_var_type_arg_count > 0);
+
+    BlockContext *child_context = decl_node->owner->block_context;
+    size_t next_generic_param_index = 0;
 
-    Buf file_path = BUF_INIT;
-    os_path_resolve(&source_dir_path, rel_file_path, &file_path);
+    GenericFnTypeId *generic_fn_type_id = allocate<GenericFnTypeId>(1);
+    generic_fn_type_id->decl_node = decl_node;
+    generic_fn_type_id->generic_param_count = inline_or_var_type_arg_count;
+    generic_fn_type_id->generic_params = allocate<GenericParamValue>(inline_or_var_type_arg_count);
 
-    // load from file system into const expr
-    Buf file_contents = BUF_INIT;
-    int err;
-    if ((err = os_fetch_file_path(&file_path, &file_contents))) {
-        if (err == ErrorFileNotFound) {
-            add_node_error(g, node,
-                    buf_sprintf("unable to find '%s'", buf_ptr(&file_path)));
-            return g->builtin_types.entry_invalid;
-        } else {
-            add_node_error(g, node,
-                    buf_sprintf("unable to open '%s': %s", buf_ptr(&file_path), err_str(err)));
-            return g->builtin_types.entry_invalid;
+    size_t next_impl_i = 0;
+    for (size_t call_i = 0; call_i < call_param_count; call_i += 1) {
+        size_t proto_i = call_i + struct_node_1_or_0;
+        AstNode *generic_param_decl_node = decl_node->data.fn_proto.params.at(proto_i);
+        assert(generic_param_decl_node->type == NodeTypeParamDecl);
+
+        AstNode **generic_param_type_node = &generic_param_decl_node->data.param_decl.type;
+        TypeTableEntry *expected_param_type = analyze_type_expr(g, decl_node->owner, child_context,
+                *generic_param_type_node);
+        if (expected_param_type->id == TypeTableEntryIdInvalid) {
+            return expected_param_type;
         }
-    }
 
-    // TODO add dependency on the file we embedded so that we know if it changes
-    // we'll have to invalidate the cache
-
-    return resolve_expr_const_val_as_string_lit(g, node, &file_contents);
-}
-
-static TypeTableEntry *analyze_cmpxchg(CodeGen *g, ImportTableEntry *import,
-        BlockContext *context, AstNode *node)
-{
-    assert(node->type == NodeTypeFnCallExpr);
-
-    AstNode **ptr_arg = &node->data.fn_call_expr.params.at(0);
-    AstNode **cmp_arg = &node->data.fn_call_expr.params.at(1);
-    AstNode **new_arg = &node->data.fn_call_expr.params.at(2);
-    AstNode **success_order_arg = &node->data.fn_call_expr.params.at(3);
-    AstNode **failure_order_arg = &node->data.fn_call_expr.params.at(4);
-
-    TypeTableEntry *ptr_type = analyze_expression(g, import, context, nullptr, *ptr_arg);
-    if (ptr_type->id == TypeTableEntryIdInvalid) {
-        return g->builtin_types.entry_invalid;
-    } else if (ptr_type->id != TypeTableEntryIdPointer) {
-        add_node_error(g, *ptr_arg,
-            buf_sprintf("expected pointer argument, got '%s'", buf_ptr(&ptr_type->name)));
-        return g->builtin_types.entry_invalid;
-    }
-
-    TypeTableEntry *child_type = ptr_type->data.pointer.child_type;
-    TypeTableEntry *cmp_type = analyze_expression(g, import, context, child_type, *cmp_arg);
-    TypeTableEntry *new_type = analyze_expression(g, import, context, child_type, *new_arg);
-
-    TypeTableEntry *success_order_type = analyze_expression(g, import, context,
-            g->builtin_types.entry_atomic_order_enum, *success_order_arg);
-    TypeTableEntry *failure_order_type = analyze_expression(g, import, context,
-            g->builtin_types.entry_atomic_order_enum, *failure_order_arg);
-
-    if (cmp_type->id == TypeTableEntryIdInvalid ||
-        new_type->id == TypeTableEntryIdInvalid ||
-        success_order_type->id == TypeTableEntryIdInvalid ||
-        failure_order_type->id == TypeTableEntryIdInvalid)
-    {
-        return g->builtin_types.entry_invalid;
-    }
-
-    ConstExprValue *success_order_val = &get_resolved_expr(*success_order_arg)->const_val;
-    ConstExprValue *failure_order_val = &get_resolved_expr(*failure_order_arg)->const_val;
-    if (!success_order_val->ok) {
-        add_node_error(g, *success_order_arg, buf_sprintf("unable to evaluate constant expression"));
-        return g->builtin_types.entry_invalid;
-    } else if (!failure_order_val->ok) {
-        add_node_error(g, *failure_order_arg, buf_sprintf("unable to evaluate constant expression"));
-        return g->builtin_types.entry_invalid;
-    }
-
-    if (success_order_val->data.x_enum.tag < AtomicOrderMonotonic) {
-        add_node_error(g, *success_order_arg,
-                buf_sprintf("success atomic ordering must be Monotonic or stricter"));
-        return g->builtin_types.entry_invalid;
-    }
-    if (failure_order_val->data.x_enum.tag < AtomicOrderMonotonic) {
-        add_node_error(g, *failure_order_arg,
-                buf_sprintf("failure atomic ordering must be Monotonic or stricter"));
-        return g->builtin_types.entry_invalid;
-    }
-    if (failure_order_val->data.x_enum.tag > success_order_val->data.x_enum.tag) {
-        add_node_error(g, *failure_order_arg,
-                buf_sprintf("failure atomic ordering must be no stricter than success"));
-        return g->builtin_types.entry_invalid;
-    }
-    if (failure_order_val->data.x_enum.tag == AtomicOrderRelease ||
-        failure_order_val->data.x_enum.tag == AtomicOrderAcqRel)
-    {
-        add_node_error(g, *failure_order_arg,
-                buf_sprintf("failure atomic ordering must not be Release or AcqRel"));
-        return g->builtin_types.entry_invalid;
-    }
-
-    return g->builtin_types.entry_bool;
-}
-
-static TypeTableEntry *analyze_fence(CodeGen *g, ImportTableEntry *import,
-        BlockContext *context, AstNode *node)
-{
-    assert(node->type == NodeTypeFnCallExpr);
-
-    AstNode **atomic_order_arg = &node->data.fn_call_expr.params.at(0);
-    TypeTableEntry *atomic_order_type = analyze_expression(g, import, context,
-            g->builtin_types.entry_atomic_order_enum, *atomic_order_arg);
-
-    if (atomic_order_type->id == TypeTableEntryIdInvalid) {
-        return g->builtin_types.entry_invalid;
-    }
-
-    ConstExprValue *atomic_order_val = &get_resolved_expr(*atomic_order_arg)->const_val;
-
-    if (!atomic_order_val->ok) {
-        add_node_error(g, *atomic_order_arg, buf_sprintf("unable to evaluate constant expression"));
-        return g->builtin_types.entry_invalid;
-    }
-
-    return g->builtin_types.entry_void;
-}
-
-static TypeTableEntry *analyze_div_exact(CodeGen *g, ImportTableEntry *import,
-        BlockContext *context, AstNode *node)
-{
-    assert(node->type == NodeTypeFnCallExpr);
-
-    AstNode **op1 = &node->data.fn_call_expr.params.at(0);
-    AstNode **op2 = &node->data.fn_call_expr.params.at(1);
-
-    TypeTableEntry *op1_type = analyze_expression(g, import, context, nullptr, *op1);
-    TypeTableEntry *op2_type = analyze_expression(g, import, context, nullptr, *op2);
-
-    AstNode *op_nodes[] = {*op1, *op2};
-    TypeTableEntry *op_types[] = {op1_type, op2_type};
-    TypeTableEntry *result_type = resolve_peer_type_compatibility(g, import, context, node,
-            op_nodes, op_types, 2);
-
-    if (result_type->id == TypeTableEntryIdInvalid) {
-        return g->builtin_types.entry_invalid;
-    } else if (result_type->id == TypeTableEntryIdInt) {
-        return result_type;
-    } else if (result_type->id == TypeTableEntryIdNumLitInt) {
-        // check for division by zero
-        // check for non exact division
-        zig_panic("TODO");
-    } else {
-        add_node_error(g, node,
-                buf_sprintf("expected integer type, got '%s'", buf_ptr(&result_type->name)));
-        return g->builtin_types.entry_invalid;
-    }
-}
-
-static TypeTableEntry *analyze_truncate(CodeGen *g, ImportTableEntry *import,
-        BlockContext *context, AstNode *node)
-{
-    assert(node->type == NodeTypeFnCallExpr);
-
-    AstNode **op1 = &node->data.fn_call_expr.params.at(0);
-    AstNode **op2 = &node->data.fn_call_expr.params.at(1);
-
-    TypeTableEntry *dest_type = analyze_type_expr(g, import, context, *op1);
-    TypeTableEntry *src_type = analyze_expression(g, import, context, nullptr, *op2);
-
-    if (dest_type->id == TypeTableEntryIdInvalid || src_type->id == TypeTableEntryIdInvalid) {
-        return g->builtin_types.entry_invalid;
-    } else if (dest_type->id != TypeTableEntryIdInt) {
-        add_node_error(g, *op1,
-                buf_sprintf("expected integer type, got '%s'", buf_ptr(&dest_type->name)));
-        return g->builtin_types.entry_invalid;
-    } else if (src_type->id != TypeTableEntryIdInt) {
-        add_node_error(g, *op2,
-                buf_sprintf("expected integer type, got '%s'", buf_ptr(&src_type->name)));
-        return g->builtin_types.entry_invalid;
-    } else if (src_type->data.integral.is_signed != dest_type->data.integral.is_signed) {
-        const char *sign_str = dest_type->data.integral.is_signed ? "signed" : "unsigned";
-        add_node_error(g, *op2,
-                buf_sprintf("expected %s integer type, got '%s'", sign_str, buf_ptr(&src_type->name)));
-        return g->builtin_types.entry_invalid;
-    } else if (src_type->data.integral.bit_count <= dest_type->data.integral.bit_count) {
-        add_node_error(g, *op2,
-                buf_sprintf("type '%s' has same or fewer bits than destination type '%s'",
-                    buf_ptr(&src_type->name), buf_ptr(&dest_type->name)));
-        return g->builtin_types.entry_invalid;
-    }
-
-    // TODO const expr eval
-
-    return dest_type;
-}
-
-static TypeTableEntry *analyze_compile_err(CodeGen *g, ImportTableEntry *import,
-        BlockContext *context, AstNode *node)
-{
-    AstNode *first_param_node = node->data.fn_call_expr.params.at(0);
-    Buf *err_msg = resolve_const_expr_str(g, import, context, first_param_node->parent_field);
-    if (!err_msg) {
-        return g->builtin_types.entry_invalid;
-    }
-
-    add_node_error(g, node, err_msg);
-
-    return g->builtin_types.entry_invalid;
-}
-
-static TypeTableEntry *analyze_int_type(CodeGen *g, ImportTableEntry *import,
-        BlockContext *context, AstNode *node)
-{
-    AstNode **is_signed_node = &node->data.fn_call_expr.params.at(0);
-    AstNode **bit_count_node = &node->data.fn_call_expr.params.at(1);
-
-    TypeTableEntry *bool_type = g->builtin_types.entry_bool;
-    TypeTableEntry *usize_type = g->builtin_types.entry_usize;
-    TypeTableEntry *is_signed_type = analyze_expression(g, import, context, bool_type, *is_signed_node);
-    TypeTableEntry *bit_count_type = analyze_expression(g, import, context, usize_type, *bit_count_node);
-
-    if (is_signed_type->id == TypeTableEntryIdInvalid ||
-        bit_count_type->id == TypeTableEntryIdInvalid)
-    {
-        return g->builtin_types.entry_invalid;
-    }
-
-    ConstExprValue *is_signed_val = &get_resolved_expr(*is_signed_node)->const_val;
-    ConstExprValue *bit_count_val = &get_resolved_expr(*bit_count_node)->const_val;
-
-    AstNode *bad_node = nullptr;
-    if (!is_signed_val->ok) {
-        bad_node = *is_signed_node;
-    } else if (!bit_count_val->ok) {
-        bad_node = *bit_count_node;
-    }
-    if (bad_node) {
-        add_node_error(g, bad_node, buf_sprintf("unable to evaluate constant expression"));
-        return g->builtin_types.entry_invalid;
-    }
-
-    bool depends_on_compile_var = is_signed_val->depends_on_compile_var || bit_count_val->depends_on_compile_var;
-
-    TypeTableEntry *int_type = get_int_type(g, is_signed_val->data.x_bool,
-            bit_count_val->data.x_bignum.data.x_uint);
-    return resolve_expr_const_val_as_type(g, node, int_type, depends_on_compile_var);
-
-}
-
-static TypeTableEntry *analyze_set_fn_test(CodeGen *g, ImportTableEntry *import,
-        BlockContext *context, AstNode *node)
-{
-    AstNode **fn_node = &node->data.fn_call_expr.params.at(0);
-    AstNode **value_node = &node->data.fn_call_expr.params.at(1);
-
-    FnTableEntry *fn_entry = resolve_const_expr_fn(g, import, context, fn_node);
-    if (!fn_entry) {
-        return g->builtin_types.entry_invalid;
-    }
-
-    bool ok = resolve_const_expr_bool(g, import, context, value_node, &fn_entry->is_test);
-    if (!ok) {
-        return g->builtin_types.entry_invalid;
-    }
-
-    if (fn_entry->fn_test_set_node) {
-        ErrorMsg *msg = add_node_error(g, node, buf_sprintf("function test attribute set twice"));
-        add_error_note(g, msg, fn_entry->fn_test_set_node, buf_sprintf("first set here"));
-        return g->builtin_types.entry_invalid;
-    }
-    fn_entry->fn_test_set_node = node;
-
-    g->test_fn_count += 1;
-    return g->builtin_types.entry_void;
-}
-
-static TypeTableEntry *analyze_set_fn_no_inline(CodeGen *g, ImportTableEntry *import,
-        BlockContext *context, AstNode *node)
-{
-    AstNode **fn_node = &node->data.fn_call_expr.params.at(0);
-    AstNode **value_node = &node->data.fn_call_expr.params.at(1);
-
-    FnTableEntry *fn_entry = resolve_const_expr_fn(g, import, context, fn_node);
-    if (!fn_entry) {
-        return g->builtin_types.entry_invalid;
-    }
-
-    bool is_noinline;
-    bool ok = resolve_const_expr_bool(g, import, context, value_node, &is_noinline);
-    if (!ok) {
-        return g->builtin_types.entry_invalid;
-    }
-
-    if (fn_entry->fn_no_inline_set_node) {
-        ErrorMsg *msg = add_node_error(g, node, buf_sprintf("function no inline attribute set twice"));
-        add_error_note(g, msg, fn_entry->fn_no_inline_set_node, buf_sprintf("first set here"));
-        return g->builtin_types.entry_invalid;
-    }
-    fn_entry->fn_no_inline_set_node = node;
-
-    if (fn_entry->fn_inline == FnInlineAlways) {
-        add_node_error(g, node, buf_sprintf("function is both inline and noinline"));
-        fn_entry->proto_node->data.fn_proto.skip = true;
-        return g->builtin_types.entry_invalid;
-    } else if (is_noinline) {
-        fn_entry->fn_inline = FnInlineNever;
-    }
-
-    return g->builtin_types.entry_void;
-}
-
-static TypeTableEntry *analyze_set_fn_static_eval(CodeGen *g, ImportTableEntry *import,
-        BlockContext *context, AstNode *node)
-{
-    AstNode **fn_node = &node->data.fn_call_expr.params.at(0);
-    AstNode **value_node = &node->data.fn_call_expr.params.at(1);
-
-    FnTableEntry *fn_entry = resolve_const_expr_fn(g, import, context, fn_node);
-    if (!fn_entry) {
-        return g->builtin_types.entry_invalid;
-    }
-
-    bool want_static_eval;
-    bool ok = resolve_const_expr_bool(g, import, context, value_node, &want_static_eval);
-    if (!ok) {
-        return g->builtin_types.entry_invalid;
-    }
-
-    if (fn_entry->fn_static_eval_set_node) {
-        ErrorMsg *msg = add_node_error(g, node, buf_sprintf("function static eval attribute set twice"));
-        add_error_note(g, msg, fn_entry->fn_static_eval_set_node, buf_sprintf("first set here"));
-        return g->builtin_types.entry_invalid;
-    }
-    fn_entry->fn_static_eval_set_node = node;
-
-    if (want_static_eval && !context->fn_entry->is_pure) {
-        add_node_error(g, node, buf_sprintf("attribute appears too late within function"));
-        return g->builtin_types.entry_invalid;
-    }
-
-    if (want_static_eval) {
-        fn_entry->want_pure = WantPureTrue;
-        fn_entry->want_pure_attr_node = node;
-    } else {
-        fn_entry->want_pure = WantPureFalse;
-        fn_entry->is_pure = false;
-    }
-
-    return g->builtin_types.entry_void;
-}
-
-static TypeTableEntry *analyze_set_fn_visible(CodeGen *g, ImportTableEntry *import,
-        BlockContext *context, AstNode *node)
-{
-    AstNode **fn_node = &node->data.fn_call_expr.params.at(0);
-    AstNode **value_node = &node->data.fn_call_expr.params.at(1);
-
-    FnTableEntry *fn_entry = resolve_const_expr_fn(g, import, context, fn_node);
-    if (!fn_entry) {
-        return g->builtin_types.entry_invalid;
-    }
-
-    bool want_export;
-    bool ok = resolve_const_expr_bool(g, import, context, value_node, &want_export);
-    if (!ok) {
-        return g->builtin_types.entry_invalid;
-    }
-
-    if (fn_entry->fn_export_set_node) {
-        ErrorMsg *msg = add_node_error(g, node, buf_sprintf("function visibility set twice"));
-        add_error_note(g, msg, fn_entry->fn_export_set_node, buf_sprintf("first set here"));
-        return g->builtin_types.entry_invalid;
-    }
-    fn_entry->fn_export_set_node = node;
-
-    AstNodeFnProto *fn_proto = &fn_entry->proto_node->data.fn_proto;
-    if (fn_proto->top_level_decl.visib_mod != VisibModExport) {
-        ErrorMsg *msg = add_node_error(g, node,
-            buf_sprintf("function must be marked export to set function visibility"));
-        add_error_note(g, msg, fn_entry->proto_node, buf_sprintf("function declared here"));
-        return g->builtin_types.entry_void;
-    }
-    if (!want_export) {
-        fn_proto->top_level_decl.visib_mod = VisibModPub;
-    }
-
-    return g->builtin_types.entry_void;
-}
-
-static TypeTableEntry *analyze_set_debug_safety(CodeGen *g, ImportTableEntry *import,
-        BlockContext *parent_context, AstNode *node)
-{
-    AstNode **target_node = &node->data.fn_call_expr.params.at(0);
-    AstNode **value_node = &node->data.fn_call_expr.params.at(1);
-
-    TypeTableEntry *target_type = analyze_expression(g, import, parent_context, nullptr, *target_node);
-    BlockContext *target_context;
-    ConstExprValue *const_val = &get_resolved_expr(*target_node)->const_val;
-    if (target_type->id == TypeTableEntryIdInvalid) {
-        return g->builtin_types.entry_invalid;
-    }
-    if (!const_val->ok) {
-        add_node_error(g, *target_node, buf_sprintf("unable to evaluate constant expression"));
-        return g->builtin_types.entry_invalid;
-    }
-    if (target_type->id == TypeTableEntryIdBlock) {
-        target_context = const_val->data.x_block;
-    } else if (target_type->id == TypeTableEntryIdFn) {
-        target_context = const_val->data.x_fn->fn_def_node->data.fn_def.block_context;
-    } else if (target_type->id == TypeTableEntryIdMetaType) {
-        TypeTableEntry *type_arg = const_val->data.x_type;
-        if (type_arg->id == TypeTableEntryIdStruct) {
-            target_context = type_arg->data.structure.block_context;
-        } else if (type_arg->id == TypeTableEntryIdEnum) {
-            target_context = type_arg->data.enumeration.block_context;
-        } else if (type_arg->id == TypeTableEntryIdUnion) {
-            target_context = type_arg->data.unionation.block_context;
-        } else {
-            add_node_error(g, *target_node,
-                buf_sprintf("expected scope reference, got type '%s'", buf_ptr(&type_arg->name)));
-            return g->builtin_types.entry_invalid;
-        }
-    } else {
-        add_node_error(g, *target_node,
-            buf_sprintf("expected scope reference, got type '%s'", buf_ptr(&target_type->name)));
-        return g->builtin_types.entry_invalid;
-    }
-
-    bool want_debug_safety;
-    bool ok = resolve_const_expr_bool(g, import, parent_context, value_node, &want_debug_safety);
-    if (!ok) {
-        return g->builtin_types.entry_invalid;
-    }
-
-    if (target_context->safety_set_node) {
-        ErrorMsg *msg = add_node_error(g, node, buf_sprintf("debug safety for scope set twice"));
-        add_error_note(g, msg, target_context->safety_set_node, buf_sprintf("first set here"));
-        return g->builtin_types.entry_invalid;
-    }
-    target_context->safety_set_node = node;
-
-    target_context->safety_off = !want_debug_safety;
-
-    return g->builtin_types.entry_void;
-}
-
-static TypeTableEntry *analyze_builtin_fn_call_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
-        TypeTableEntry *expected_type, AstNode *node)
-{
-    assert(node->type == NodeTypeFnCallExpr);
-
-    AstNode *fn_ref_expr = node->data.fn_call_expr.fn_ref_expr;
-    Buf *name = fn_ref_expr->data.symbol_expr.symbol;
-
-    auto entry = g->builtin_fn_table.maybe_get(name);
-
-    if (!entry) {
-        add_node_error(g, node,
-                buf_sprintf("invalid builtin function: '%s'", buf_ptr(name)));
-        return g->builtin_types.entry_invalid;
-    }
-
-    BuiltinFnEntry *builtin_fn = entry->value;
-    size_t actual_param_count = node->data.fn_call_expr.params.length;
-
-    node->data.fn_call_expr.builtin_fn = builtin_fn;
-
-    if (builtin_fn->param_count != actual_param_count) {
-        add_node_error(g, node,
-                buf_sprintf("expected %zu arguments, got %zu",
-                    builtin_fn->param_count, actual_param_count));
-        return g->builtin_types.entry_invalid;
-    }
-
-    builtin_fn->ref_count += 1;
-
-    switch (builtin_fn->id) {
-        case BuiltinFnIdInvalid:
-            zig_unreachable();
-        case BuiltinFnIdAddWithOverflow:
-        case BuiltinFnIdSubWithOverflow:
-        case BuiltinFnIdMulWithOverflow:
-        case BuiltinFnIdShlWithOverflow:
-            {
-                AstNode *type_node = node->data.fn_call_expr.params.at(0);
-                TypeTableEntry *int_type = analyze_type_expr(g, import, context, type_node);
-                if (int_type->id == TypeTableEntryIdInvalid) {
-                    return g->builtin_types.entry_bool;
-                } else if (int_type->id == TypeTableEntryIdInt) {
-                    AstNode *op1_node = node->data.fn_call_expr.params.at(1);
-                    AstNode *op2_node = node->data.fn_call_expr.params.at(2);
-                    AstNode *result_node = node->data.fn_call_expr.params.at(3);
-
-                    analyze_expression(g, import, context, int_type, op1_node);
-                    analyze_expression(g, import, context, int_type, op2_node);
-                    analyze_expression(g, import, context, get_pointer_to_type(g, int_type, false),
-                            result_node);
-                } else {
-                    add_node_error(g, type_node,
-                        buf_sprintf("expected integer type, got '%s'", buf_ptr(&int_type->name)));
-                }
-
-                // TODO constant expression evaluation
-
-                return g->builtin_types.entry_bool;
-            }
-        case BuiltinFnIdMemcpy:
-            {
-                AstNode *dest_node = node->data.fn_call_expr.params.at(0);
-                AstNode *src_node = node->data.fn_call_expr.params.at(1);
-                AstNode *len_node = node->data.fn_call_expr.params.at(2);
-                TypeTableEntry *dest_type = analyze_expression(g, import, context, nullptr, dest_node);
-                TypeTableEntry *src_type = analyze_expression(g, import, context, nullptr, src_node);
-                analyze_expression(g, import, context, builtin_fn->param_types[2], len_node);
-
-                if (dest_type->id != TypeTableEntryIdInvalid &&
-                    dest_type->id != TypeTableEntryIdPointer)
-                {
-                    add_node_error(g, dest_node,
-                            buf_sprintf("expected pointer argument, got '%s'", buf_ptr(&dest_type->name)));
-                }
-
-                if (src_type->id != TypeTableEntryIdInvalid &&
-                    src_type->id != TypeTableEntryIdPointer)
-                {
-                    add_node_error(g, src_node,
-                            buf_sprintf("expected pointer argument, got '%s'", buf_ptr(&src_type->name)));
-                }
-
-                if (dest_type->id == TypeTableEntryIdPointer &&
-                    src_type->id == TypeTableEntryIdPointer)
-                {
-                    uint64_t dest_align = get_memcpy_align(g, dest_type->data.pointer.child_type);
-                    uint64_t src_align = get_memcpy_align(g, src_type->data.pointer.child_type);
-                    if (dest_align != src_align) {
-                        add_node_error(g, dest_node, buf_sprintf(
-                            "misaligned memcpy, '%s' has alignment '%" PRIu64 ", '%s' has alignment %" PRIu64,
-                                    buf_ptr(&dest_type->name), dest_align,
-                                    buf_ptr(&src_type->name), src_align));
-                    }
-                }
-
-                return builtin_fn->return_type;
-            }
-        case BuiltinFnIdMemset:
-            {
-                AstNode *dest_node = node->data.fn_call_expr.params.at(0);
-                AstNode *char_node = node->data.fn_call_expr.params.at(1);
-                AstNode *len_node = node->data.fn_call_expr.params.at(2);
-                TypeTableEntry *dest_type = analyze_expression(g, import, context, nullptr, dest_node);
-                analyze_expression(g, import, context, builtin_fn->param_types[1], char_node);
-                analyze_expression(g, import, context, builtin_fn->param_types[2], len_node);
-
-                if (dest_type->id != TypeTableEntryIdInvalid &&
-                    dest_type->id != TypeTableEntryIdPointer)
-                {
-                    add_node_error(g, dest_node,
-                            buf_sprintf("expected pointer argument, got '%s'", buf_ptr(&dest_type->name)));
-                }
-
-                return builtin_fn->return_type;
-            }
-        case BuiltinFnIdSizeof:
-            {
-                AstNode *type_node = node->data.fn_call_expr.params.at(0);
-                TypeTableEntry *type_entry = analyze_type_expr(g, import, context, type_node);
-                if (type_entry->id == TypeTableEntryIdInvalid) {
-                    return g->builtin_types.entry_invalid;
-                } else if (type_entry->id == TypeTableEntryIdUnreachable) {
-                    add_node_error(g, first_executing_node(type_node),
-                            buf_sprintf("no size available for type '%s'", buf_ptr(&type_entry->name)));
-                    return g->builtin_types.entry_invalid;
-                } else {
-                    uint64_t size_in_bytes = type_size(g, type_entry);
-                    bool depends_on_compile_var = (type_entry == g->builtin_types.entry_usize ||
-                            type_entry == g->builtin_types.entry_isize);
-                    return resolve_expr_const_val_as_unsigned_num_lit(g, node, expected_type,
-                            size_in_bytes, depends_on_compile_var);
-                }
-            }
-        case BuiltinFnIdAlignof:
-            {
-                AstNode *type_node = node->data.fn_call_expr.params.at(0);
-                TypeTableEntry *type_entry = analyze_type_expr(g, import, context, type_node);
-                if (type_entry->id == TypeTableEntryIdInvalid) {
-                    return g->builtin_types.entry_invalid;
-                } else if (type_entry->id == TypeTableEntryIdUnreachable) {
-                    add_node_error(g, first_executing_node(type_node),
-                            buf_sprintf("no align available for type '%s'", buf_ptr(&type_entry->name)));
-                    return g->builtin_types.entry_invalid;
-                } else {
-                    uint64_t align_in_bytes = LLVMABISizeOfType(g->target_data_ref, type_entry->type_ref);
-                    return resolve_expr_const_val_as_unsigned_num_lit(g, node, expected_type,
-                            align_in_bytes, false);
-                }
-            }
-        case BuiltinFnIdMaxValue:
-            return analyze_min_max_value(g, import, context, node,
-                    "no max value available for type '%s'", true);
-        case BuiltinFnIdMinValue:
-            return analyze_min_max_value(g, import, context, node,
-                    "no min value available for type '%s'", false);
-        case BuiltinFnIdMemberCount:
-            {
-                AstNode *type_node = node->data.fn_call_expr.params.at(0);
-                TypeTableEntry *type_entry = analyze_type_expr(g, import, context, type_node);
-
-                if (type_entry->id == TypeTableEntryIdInvalid) {
-                    return type_entry;
-                } else if (type_entry->id == TypeTableEntryIdEnum) {
-                    uint64_t value_count = type_entry->data.enumeration.src_field_count;
-                    return resolve_expr_const_val_as_unsigned_num_lit(g, node, expected_type,
-                            value_count, false);
-                } else {
-                    add_node_error(g, node,
-                            buf_sprintf("no value count available for type '%s'", buf_ptr(&type_entry->name)));
-                    return g->builtin_types.entry_invalid;
-                }
-            }
-        case BuiltinFnIdTypeof:
-            {
-                AstNode *expr_node = node->data.fn_call_expr.params.at(0);
-                TypeTableEntry *type_entry = analyze_expression(g, import, context, nullptr, expr_node);
-
-                switch (type_entry->id) {
-                    case TypeTableEntryIdInvalid:
-                        return type_entry;
-                    case TypeTableEntryIdNumLitFloat:
-                    case TypeTableEntryIdNumLitInt:
-                    case TypeTableEntryIdUndefLit:
-                    case TypeTableEntryIdNullLit:
-                    case TypeTableEntryIdNamespace:
-                    case TypeTableEntryIdBlock:
-                    case TypeTableEntryIdGenericFn:
-                    case TypeTableEntryIdVar:
-                        add_node_error(g, expr_node,
-                                buf_sprintf("type '%s' not eligible for @typeOf", buf_ptr(&type_entry->name)));
-                        return g->builtin_types.entry_invalid;
-                    case TypeTableEntryIdMetaType:
-                    case TypeTableEntryIdVoid:
-                    case TypeTableEntryIdBool:
-                    case TypeTableEntryIdUnreachable:
-                    case TypeTableEntryIdInt:
-                    case TypeTableEntryIdFloat:
-                    case TypeTableEntryIdPointer:
-                    case TypeTableEntryIdArray:
-                    case TypeTableEntryIdStruct:
-                    case TypeTableEntryIdMaybe:
-                    case TypeTableEntryIdErrorUnion:
-                    case TypeTableEntryIdPureError:
-                    case TypeTableEntryIdEnum:
-                    case TypeTableEntryIdUnion:
-                    case TypeTableEntryIdFn:
-                    case TypeTableEntryIdTypeDecl:
-                        return resolve_expr_const_val_as_type(g, node, type_entry, false);
-                }
-            }
-        case BuiltinFnIdCInclude:
-            {
-                if (!context->c_import_buf) {
-                    add_node_error(g, node, buf_sprintf("@c_include valid only in c_import blocks"));
-                    return g->builtin_types.entry_invalid;
-                }
-
-                AstNode **str_node = node->data.fn_call_expr.params.at(0)->parent_field;
-                TypeTableEntry *str_type = get_slice_type(g, g->builtin_types.entry_u8, true);
-                TypeTableEntry *resolved_type = analyze_expression(g, import, context, str_type, *str_node);
-
-                if (resolved_type->id == TypeTableEntryIdInvalid) {
-                    return resolved_type;
-                }
-
-                ConstExprValue *const_str_val = &get_resolved_expr(*str_node)->const_val;
-
-                if (!const_str_val->ok) {
-                    add_node_error(g, *str_node, buf_sprintf("@c_include requires constant expression"));
-                    return g->builtin_types.entry_void;
-                }
-
-                buf_appendf(context->c_import_buf, "#include <");
-                ConstExprValue *ptr_field = const_str_val->data.x_struct.fields[0];
-                uint64_t len = ptr_field->data.x_ptr.len;
-                for (uint64_t i = 0; i < len; i += 1) {
-                    ConstExprValue *char_val = ptr_field->data.x_ptr.ptr[i];
-                    uint64_t big_c = char_val->data.x_bignum.data.x_uint;
-                    assert(big_c <= UINT8_MAX);
-                    uint8_t c = big_c;
-                    buf_append_char(context->c_import_buf, c);
-                }
-                buf_appendf(context->c_import_buf, ">\n");
-
-                return g->builtin_types.entry_void;
-            }
-        case BuiltinFnIdCDefine:
-            zig_panic("TODO");
-        case BuiltinFnIdCUndef:
-            zig_panic("TODO");
-
-        case BuiltinFnIdCompileVar:
-            {
-                AstNode **str_node = node->data.fn_call_expr.params.at(0)->parent_field;
-
-                Buf *var_name = resolve_const_expr_str(g, import, context, str_node);
-                if (!var_name) {
-                    return g->builtin_types.entry_invalid;
-                }
-
-                ConstExprValue *const_val = &get_resolved_expr(node)->const_val;
-                const_val->ok = true;
-                const_val->depends_on_compile_var = true;
-
-                if (buf_eql_str(var_name, "is_big_endian")) {
-                    return resolve_expr_const_val_as_bool(g, node, g->is_big_endian, true);
-                } else if (buf_eql_str(var_name, "is_release")) {
-                    return resolve_expr_const_val_as_bool(g, node, g->is_release_build, true);
-                } else if (buf_eql_str(var_name, "is_test")) {
-                    return resolve_expr_const_val_as_bool(g, node, g->is_test_build, true);
-                } else if (buf_eql_str(var_name, "os")) {
-                    const_val->data.x_enum.tag = g->target_os_index;
-                    return g->builtin_types.entry_os_enum;
-                } else if (buf_eql_str(var_name, "arch")) {
-                    const_val->data.x_enum.tag = g->target_arch_index;
-                    return g->builtin_types.entry_arch_enum;
-                } else if (buf_eql_str(var_name, "environ")) {
-                    const_val->data.x_enum.tag = g->target_environ_index;
-                    return g->builtin_types.entry_environ_enum;
-                } else if (buf_eql_str(var_name, "object_format")) {
-                    const_val->data.x_enum.tag = g->target_oformat_index;
-                    return g->builtin_types.entry_oformat_enum;
-                } else {
-                    add_node_error(g, *str_node,
-                        buf_sprintf("unrecognized compile variable: '%s'", buf_ptr(var_name)));
-                    return g->builtin_types.entry_invalid;
-                }
-            }
-        case BuiltinFnIdConstEval:
-            {
-                AstNode **expr_node = node->data.fn_call_expr.params.at(0)->parent_field;
-                TypeTableEntry *resolved_type = analyze_expression(g, import, context, expected_type, *expr_node);
-                if (resolved_type->id == TypeTableEntryIdInvalid) {
-                    return resolved_type;
-                }
-
-                ConstExprValue *const_expr_val = &get_resolved_expr(*expr_node)->const_val;
-
-                if (!const_expr_val->ok) {
-                    add_node_error(g, *expr_node, buf_sprintf("unable to evaluate constant expression"));
-                    return g->builtin_types.entry_invalid;
-                }
-
-                ConstExprValue *const_val = &get_resolved_expr(node)->const_val;
-                *const_val = *const_expr_val;
-
-                return resolved_type;
-            }
-        case BuiltinFnIdCtz:
-        case BuiltinFnIdClz:
-            {
-                AstNode *type_node = node->data.fn_call_expr.params.at(0);
-                TypeTableEntry *int_type = analyze_type_expr(g, import, context, type_node);
-                if (int_type->id == TypeTableEntryIdInvalid) {
-                    return int_type;
-                } else if (int_type->id == TypeTableEntryIdInt) {
-                    AstNode **expr_node = node->data.fn_call_expr.params.at(1)->parent_field;
-                    TypeTableEntry *resolved_type = analyze_expression(g, import, context, int_type, *expr_node);
-                    if (resolved_type->id == TypeTableEntryIdInvalid) {
-                        return resolved_type;
-                    }
-
-                    // TODO const expr eval
-
-                    return resolved_type;
-                } else {
-                    add_node_error(g, type_node,
-                        buf_sprintf("expected integer type, got '%s'", buf_ptr(&int_type->name)));
-                    return g->builtin_types.entry_invalid;
-                }
-            }
-        case BuiltinFnIdImport:
-            return analyze_import(g, import, context, node);
-        case BuiltinFnIdCImport:
-            return analyze_c_import(g, import, context, node);
-        case BuiltinFnIdErrName:
-            return analyze_err_name(g, import, context, node);
-        case BuiltinFnIdBreakpoint:
-            mark_impure_fn(g, context, node);
-            return g->builtin_types.entry_void;
-        case BuiltinFnIdReturnAddress:
-        case BuiltinFnIdFrameAddress:
-            mark_impure_fn(g, context, node);
-            return builtin_fn->return_type;
-        case BuiltinFnIdEmbedFile:
-            return analyze_embed_file(g, import, context, node);
-        case BuiltinFnIdCmpExchange:
-            return analyze_cmpxchg(g, import, context, node);
-        case BuiltinFnIdFence:
-            return analyze_fence(g, import, context, node);
-        case BuiltinFnIdDivExact:
-            return analyze_div_exact(g, import, context, node);
-        case BuiltinFnIdTruncate:
-            return analyze_truncate(g, import, context, node);
-        case BuiltinFnIdCompileErr:
-            return analyze_compile_err(g, import, context, node);
-        case BuiltinFnIdIntType:
-            return analyze_int_type(g, import, context, node);
-        case BuiltinFnIdUnreachable:
-            return g->builtin_types.entry_unreachable;
-        case BuiltinFnIdSetFnTest:
-            return analyze_set_fn_test(g, import, context, node);
-        case BuiltinFnIdSetFnNoInline:
-            return analyze_set_fn_no_inline(g, import, context, node);
-        case BuiltinFnIdSetFnStaticEval:
-            return analyze_set_fn_static_eval(g, import, context, node);
-        case BuiltinFnIdSetFnVisible:
-            return analyze_set_fn_visible(g, import, context, node);
-        case BuiltinFnIdSetDebugSafety:
-            return analyze_set_debug_safety(g, import, context, node);
-    }
-    zig_unreachable();
-}
-
-static TypeTableEntry *bad_method_call(CodeGen *g, AstNode *node, TypeTableEntry *container_type,
-        TypeTableEntry *expected_param_type, FnTableEntry *fn_table_entry)
-{
-    ErrorMsg *msg = add_node_error(g, node,
-        buf_sprintf("function called as method of '%s', but first parameter is of type '%s'",
-            buf_ptr(&container_type->name),
-            buf_ptr(&expected_param_type->name)));
-    if (fn_table_entry) {
-        add_error_note(g, msg, fn_table_entry->proto_node, buf_sprintf("function declared here"));
-    }
-    return g->builtin_types.entry_invalid;
-}
-
-// Before calling this function, set node->data.fn_call_expr.fn_table_entry if the function is known
-// at compile time. Otherwise this is a function pointer call.
-static TypeTableEntry *analyze_fn_call_ptr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
-        TypeTableEntry *expected_type, AstNode *node, TypeTableEntry *fn_type,
-        AstNode *struct_node)
-{
-    assert(node->type == NodeTypeFnCallExpr);
-
-    if (fn_type->id == TypeTableEntryIdInvalid) {
-        return fn_type;
-    }
-
-    // The function call might include inline parameters which we need to ignore according to the
-    // fn_type.
-    FnTableEntry *fn_table_entry = node->data.fn_call_expr.fn_entry;
-    AstNode *generic_proto_node = fn_table_entry ?
-        fn_table_entry->proto_node->data.fn_proto.generic_proto_node : nullptr;
-
-    // count parameters
-    size_t struct_node_1_or_0 = struct_node ? 1 : 0;
-    size_t src_param_count = fn_type->data.fn.fn_type_id.param_count +
-        (generic_proto_node ? generic_proto_node->data.fn_proto.inline_arg_count : 0);
-    size_t call_param_count = node->data.fn_call_expr.params.length;
-    size_t expect_arg_count = src_param_count - struct_node_1_or_0;
-
-    bool ok_invocation = true;
-
-    if (fn_type->data.fn.fn_type_id.is_var_args) {
-        if (call_param_count < expect_arg_count) {
-            ok_invocation = false;
-            add_node_error(g, node,
-                buf_sprintf("expected at least %zu arguments, got %zu", src_param_count, call_param_count));
-        }
-    } else if (expect_arg_count != call_param_count) {
-        ok_invocation = false;
-        add_node_error(g, node,
-                buf_sprintf("expected %zu arguments, got %zu", expect_arg_count, call_param_count));
-    }
-
-    bool all_args_const_expr = true;
-
-    if (struct_node) {
-        Expr *struct_expr = get_resolved_expr(struct_node);
-        ConstExprValue *struct_const_val = &struct_expr->const_val;
-        if (!struct_const_val->ok) {
-            all_args_const_expr = false;
-        }
-
-        FnTypeParamInfo *param_info = &fn_type->data.fn.fn_type_id.param_info[0];
-        TypeTableEntry *expected_param_type = param_info->type;
-        TypeTableEntry *container_bare_type = container_ref_type(struct_expr->type_entry);
-        if (is_container_ref(expected_param_type)) {
-            TypeTableEntry *param_bare_type = container_ref_type(expected_param_type);
-            if (param_bare_type != container_bare_type) {
-                return bad_method_call(g, node, container_bare_type, expected_param_type, fn_table_entry);
-            }
-        } else {
-            return bad_method_call(g, node, container_bare_type, expected_param_type, fn_table_entry);
-        }
-    }
-
-    // analyze each parameter. in the case of a method, we already analyzed the
-    // first parameter in order to figure out which struct we were calling a method on.
-    size_t next_type_i = struct_node_1_or_0;
-    for (size_t call_i = 0; call_i < call_param_count; call_i += 1) {
-        size_t proto_i = call_i + struct_node_1_or_0;
-        AstNode **param_node = &node->data.fn_call_expr.params.at(call_i);
-        // determine the expected type for each parameter
-        TypeTableEntry *expected_param_type = nullptr;
-        if (proto_i < src_param_count) {
-            if (generic_proto_node &&
-                generic_proto_node->data.fn_proto.params.at(proto_i)->data.param_decl.is_inline)
-            {
-                continue;
-            }
-
-            FnTypeParamInfo *param_info = &fn_type->data.fn.fn_type_id.param_info[next_type_i];
-            next_type_i += 1;
-
-            expected_param_type = param_info->type;
-        }
-        TypeTableEntry *param_type = analyze_expression(g, import, context, expected_param_type, *param_node);
-        if (param_type->id == TypeTableEntryIdInvalid) {
-            return param_type;
-        }
-
-        ConstExprValue *const_arg_val = &get_resolved_expr(*param_node)->const_val;
-        if (!const_arg_val->ok) {
-            all_args_const_expr = false;
-        }
-    }
-
-    TypeTableEntry *return_type = fn_type->data.fn.fn_type_id.return_type;
-
-    if (return_type->id == TypeTableEntryIdInvalid) {
-        return return_type;
-    }
-
-    ConstExprValue *result_val = &get_resolved_expr(node)->const_val;
-    if (ok_invocation && fn_table_entry && fn_table_entry->is_pure && fn_table_entry->want_pure != WantPureFalse) {
-        if (fn_table_entry->anal_state == FnAnalStateReady) {
-            analyze_fn_body(g, fn_table_entry);
-            if (fn_table_entry->proto_node->data.fn_proto.skip) {
-                return g->builtin_types.entry_invalid;
-            }
-        }
-        if (all_args_const_expr) {
-            if (fn_table_entry->is_pure && fn_table_entry->anal_state == FnAnalStateComplete) {
-                if (eval_fn(g, node, fn_table_entry, result_val, 1000, struct_node)) {
-                    // function evaluation generated an error
-                    return g->builtin_types.entry_invalid;
-                }
-                return return_type;
-            }
-        }
-    }
-    if (!ok_invocation || !fn_table_entry || !fn_table_entry->is_pure || fn_table_entry->want_pure == WantPureFalse) {
-        // calling an impure fn is impure
-        mark_impure_fn(g, context, node);
-        if (fn_table_entry && fn_table_entry->want_pure == WantPureTrue) {
-            return g->builtin_types.entry_invalid;
-        }
-    }
-
-    // TODO
-    //if (handle_is_ptr(return_type)) {
-    //    if (context->fn_entry) {
-    //        context->fn_entry->cast_alloca_list.append(node);
-    //    } else if (!result_val->ok) {
-    //        add_node_error(g, node, buf_sprintf("unable to evaluate constant expression"));
-    //    }
-    //}
-
-    return return_type;
-}
-
-static TypeTableEntry *analyze_fn_call_with_inline_args(CodeGen *g, ImportTableEntry *import,
-        BlockContext *parent_context, TypeTableEntry *expected_type, AstNode *call_node,
-        FnTableEntry *fn_table_entry, AstNode *struct_node)
-{
-    assert(call_node->type == NodeTypeFnCallExpr);
-    assert(fn_table_entry);
-
-    AstNode *decl_node = fn_table_entry->proto_node;
-
-    // count parameters
-    size_t struct_node_1_or_0 = (struct_node ? 1 : 0);
-    size_t src_param_count = decl_node->data.fn_proto.params.length;
-    size_t call_param_count = call_node->data.fn_call_expr.params.length;
-
-    if (src_param_count != call_param_count + struct_node_1_or_0) {
-        add_node_error(g, call_node,
-            buf_sprintf("expected %zu arguments, got %zu", src_param_count - struct_node_1_or_0, call_param_count));
-        return g->builtin_types.entry_invalid;
-    }
-
-    size_t inline_or_var_type_arg_count = decl_node->data.fn_proto.inline_or_var_type_arg_count;
-    assert(inline_or_var_type_arg_count > 0);
-
-    BlockContext *child_context = decl_node->owner->block_context;
-    size_t next_generic_param_index = 0;
-
-    GenericFnTypeId *generic_fn_type_id = allocate<GenericFnTypeId>(1);
-    generic_fn_type_id->decl_node = decl_node;
-    generic_fn_type_id->generic_param_count = inline_or_var_type_arg_count;
-    generic_fn_type_id->generic_params = allocate<GenericParamValue>(inline_or_var_type_arg_count);
-
-    size_t next_impl_i = 0;
-    for (size_t call_i = 0; call_i < call_param_count; call_i += 1) {
-        size_t proto_i = call_i + struct_node_1_or_0;
-        AstNode *generic_param_decl_node = decl_node->data.fn_proto.params.at(proto_i);
-        assert(generic_param_decl_node->type == NodeTypeParamDecl);
-
-        AstNode **generic_param_type_node = &generic_param_decl_node->data.param_decl.type;
-        TypeTableEntry *expected_param_type = analyze_type_expr(g, decl_node->owner, child_context,
-                *generic_param_type_node);
-        if (expected_param_type->id == TypeTableEntryIdInvalid) {
-            return expected_param_type;
-        }
-
-        bool is_var_type = (expected_param_type->id == TypeTableEntryIdVar);
-        bool is_inline = generic_param_decl_node->data.param_decl.is_inline;
-        if (!is_inline && !is_var_type) {
-            next_impl_i += 1;
-            continue;
-        }
+        bool is_var_type = (expected_param_type->id == TypeTableEntryIdVar);
+        bool is_inline = generic_param_decl_node->data.param_decl.is_inline;
+        if (!is_inline && !is_var_type) {
+            next_impl_i += 1;
+            continue;
+        }
 
 
         AstNode **param_node = &call_node->data.fn_call_expr.params.at(call_i);
@@ -5817,7 +4730,7 @@ static TypeTableEntry *analyze_fn_call_expr(CodeGen *g, ImportTableEntry *import
     AstNode *fn_ref_expr = node->data.fn_call_expr.fn_ref_expr;
 
     if (node->data.fn_call_expr.is_builtin) {
-        return analyze_builtin_fn_call_expr(g, import, context, expected_type, node);
+        zig_panic("moved builtin fn call code to ir.cpp");
     }
 
     TypeTableEntry *invoke_type_entry = analyze_expression(g, import, context, nullptr, fn_ref_expr);
@@ -5885,222 +4798,6 @@ static TypeTableEntry *analyze_fn_call_expr(CodeGen *g, ImportTableEntry *import
     }
 }
 
-static TypeTableEntry *analyze_prefix_op_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
-        TypeTableEntry *expected_type, AstNode *node)
-{
-    PrefixOp prefix_op = node->data.prefix_op_expr.prefix_op;
-    AstNode **expr_node = &node->data.prefix_op_expr.primary_expr;
-    switch (prefix_op) {
-        case PrefixOpInvalid:
-            zig_unreachable();
-        case PrefixOpBoolNot:
-            {
-                TypeTableEntry *type_entry = analyze_expression(g, import, context, g->builtin_types.entry_bool,
-                        *expr_node);
-                if (type_entry->id == TypeTableEntryIdInvalid) {
-                    return g->builtin_types.entry_bool;
-                }
-
-                ConstExprValue *target_const_val = &get_resolved_expr(*expr_node)->const_val;
-                if (!target_const_val->ok) {
-                    return g->builtin_types.entry_bool;
-                }
-
-                bool answer = !target_const_val->data.x_bool;
-                return resolve_expr_const_val_as_bool(g, node, answer, target_const_val->depends_on_compile_var);
-            }
-        case PrefixOpBinNot:
-            {
-                TypeTableEntry *expr_type = analyze_expression(g, import, context, expected_type,
-                        *expr_node);
-                if (expr_type->id == TypeTableEntryIdInvalid) {
-                    return expr_type;
-                } else if (expr_type->id == TypeTableEntryIdInt) {
-                    return expr_type;
-                } else {
-                    add_node_error(g, node, buf_sprintf("unable to perform binary not operation on type '%s'",
-                            buf_ptr(&expr_type->name)));
-                    return g->builtin_types.entry_invalid;
-                }
-                // TODO const expr eval
-            }
-        case PrefixOpNegation:
-        case PrefixOpNegationWrap:
-            {
-                TypeTableEntry *expr_type = analyze_expression(g, import, context, nullptr, *expr_node);
-                if (expr_type->id == TypeTableEntryIdInvalid) {
-                    return expr_type;
-                } else if ((expr_type->id == TypeTableEntryIdInt &&
-                            expr_type->data.integral.is_signed) ||
-                            expr_type->id == TypeTableEntryIdNumLitInt ||
-                            ((expr_type->id == TypeTableEntryIdFloat ||
-                            expr_type->id == TypeTableEntryIdNumLitFloat) &&
-                            prefix_op != PrefixOpNegationWrap))
-                {
-                    ConstExprValue *target_const_val = &get_resolved_expr(*expr_node)->const_val;
-                    if (!target_const_val->ok) {
-                        return expr_type;
-                    }
-                    ConstExprValue *const_val = &get_resolved_expr(node)->const_val;
-                    const_val->ok = true;
-                    const_val->depends_on_compile_var = target_const_val->depends_on_compile_var;
-                    bignum_negate(&const_val->data.x_bignum, &target_const_val->data.x_bignum);
-                    if (expr_type->id == TypeTableEntryIdFloat ||
-                        expr_type->id == TypeTableEntryIdNumLitFloat ||
-                        expr_type->id == TypeTableEntryIdNumLitInt)
-                    {
-                        return expr_type;
-                    }
-
-                    bool overflow = !bignum_fits_in_bits(&const_val->data.x_bignum,
-                            expr_type->data.integral.bit_count, expr_type->data.integral.is_signed);
-                    if (prefix_op == PrefixOpNegationWrap) {
-                        if (overflow) {
-                            const_val->data.x_bignum.is_negative = true;
-                        }
-                    } else if (overflow) {
-                        add_node_error(g, *expr_node, buf_sprintf("negation caused overflow"));
-                        return g->builtin_types.entry_invalid;
-                    }
-                    return expr_type;
-                } else {
-                    const char *fmt = (prefix_op == PrefixOpNegationWrap) ?
-                        "invalid wrapping negation type: '%s'" : "invalid negation type: '%s'";
-                    add_node_error(g, node, buf_sprintf(fmt, buf_ptr(&expr_type->name)));
-                    return g->builtin_types.entry_invalid;
-                }
-            }
-        case PrefixOpAddressOf:
-        case PrefixOpConstAddressOf:
-            {
-                bool is_const = (prefix_op == PrefixOpConstAddressOf);
-
-                TypeTableEntry *child_type = analyze_lvalue(g, import, context,
-                        *expr_node, LValPurposeAddressOf, is_const);
-
-                if (child_type->id == TypeTableEntryIdInvalid) {
-                    return g->builtin_types.entry_invalid;
-                } else if (child_type->id == TypeTableEntryIdMetaType) {
-                    TypeTableEntry *meta_type = analyze_type_expr_pointer_only(g, import, context,
-                            *expr_node, true);
-                    if (meta_type->id == TypeTableEntryIdInvalid) {
-                        return g->builtin_types.entry_invalid;
-                    } else if (meta_type->id == TypeTableEntryIdUnreachable) {
-                        add_node_error(g, node, buf_create_from_str("pointer to unreachable not allowed"));
-                        return g->builtin_types.entry_invalid;
-                    } else {
-                        return resolve_expr_const_val_as_type(g, node,
-                                get_pointer_to_type(g, meta_type, is_const), false);
-                    }
-                } else if (child_type->id == TypeTableEntryIdNumLitInt ||
-                           child_type->id == TypeTableEntryIdNumLitFloat)
-                {
-                    add_node_error(g, *expr_node,
-                        buf_sprintf("unable to get address of type '%s'", buf_ptr(&child_type->name)));
-                    return g->builtin_types.entry_invalid;
-                } else {
-                    return get_pointer_to_type(g, child_type, is_const);
-                }
-            }
-        case PrefixOpDereference:
-            {
-                TypeTableEntry *type_entry = analyze_expression(g, import, context, nullptr, *expr_node);
-                if (type_entry->id == TypeTableEntryIdInvalid) {
-                    return type_entry;
-                } else if (type_entry->id == TypeTableEntryIdPointer) {
-                    return type_entry->data.pointer.child_type;
-                } else {
-                    add_node_error(g, *expr_node,
-                        buf_sprintf("indirection requires pointer operand ('%s' invalid)",
-                            buf_ptr(&type_entry->name)));
-                    return g->builtin_types.entry_invalid;
-                }
-            }
-        case PrefixOpMaybe:
-            {
-                TypeTableEntry *type_entry = analyze_expression(g, import, context, nullptr, *expr_node);
-
-                if (type_entry->id == TypeTableEntryIdInvalid) {
-                    return type_entry;
-                } else if (type_entry->id == TypeTableEntryIdMetaType) {
-                    TypeTableEntry *meta_type = resolve_type(g, *expr_node);
-                    if (meta_type->id == TypeTableEntryIdInvalid) {
-                        return g->builtin_types.entry_invalid;
-                    } else if (meta_type->id == TypeTableEntryIdUnreachable) {
-                        add_node_error(g, node, buf_create_from_str("unable to wrap unreachable in maybe type"));
-                        return g->builtin_types.entry_invalid;
-                    } else {
-                        return resolve_expr_const_val_as_type(g, node, get_maybe_type(g, meta_type), false);
-                    }
-                } else if (type_entry->id == TypeTableEntryIdUnreachable) {
-                    add_node_error(g, *expr_node, buf_sprintf("unable to wrap unreachable in maybe type"));
-                    return g->builtin_types.entry_invalid;
-                } else {
-                    ConstExprValue *target_const_val = &get_resolved_expr(*expr_node)->const_val;
-                    TypeTableEntry *maybe_type = get_maybe_type(g, type_entry);
-                    if (!target_const_val->ok) {
-                        return maybe_type;
-                    }
-                    return resolve_expr_const_val_as_non_null(g, node, maybe_type, target_const_val);
-                }
-            }
-        case PrefixOpError:
-            {
-                TypeTableEntry *type_entry = analyze_expression(g, import, context, nullptr, *expr_node);
-
-                if (type_entry->id == TypeTableEntryIdInvalid) {
-                    return type_entry;
-                } else if (type_entry->id == TypeTableEntryIdMetaType) {
-                    TypeTableEntry *meta_type = resolve_type(g, *expr_node);
-                    if (meta_type->id == TypeTableEntryIdInvalid) {
-                        return meta_type;
-                    } else if (meta_type->id == TypeTableEntryIdUnreachable) {
-                        add_node_error(g, node, buf_create_from_str("unable to wrap unreachable in error type"));
-                        return g->builtin_types.entry_invalid;
-                    } else {
-                        return resolve_expr_const_val_as_type(g, node, get_error_type(g, meta_type), false);
-                    }
-                } else if (type_entry->id == TypeTableEntryIdUnreachable) {
-                    add_node_error(g, *expr_node, buf_sprintf("unable to wrap unreachable in error type"));
-                    return g->builtin_types.entry_invalid;
-                } else {
-                    // TODO eval const expr
-                    return get_error_type(g, type_entry);
-                }
-
-            }
-        case PrefixOpUnwrapError:
-            {
-                TypeTableEntry *type_entry = analyze_expression(g, import, context, nullptr, *expr_node);
-
-                if (type_entry->id == TypeTableEntryIdInvalid) {
-                    return type_entry;
-                } else if (type_entry->id == TypeTableEntryIdErrorUnion) {
-                    return type_entry->data.error.child_type;
-                } else {
-                    add_node_error(g, *expr_node,
-                        buf_sprintf("expected error type, got '%s'", buf_ptr(&type_entry->name)));
-                    return g->builtin_types.entry_invalid;
-                }
-            }
-        case PrefixOpUnwrapMaybe:
-            {
-                TypeTableEntry *type_entry = analyze_expression(g, import, context, nullptr, *expr_node);
-
-                if (type_entry->id == TypeTableEntryIdInvalid) {
-                    return type_entry;
-                } else if (type_entry->id == TypeTableEntryIdMaybe) {
-                    return type_entry->data.maybe.child_type;
-                } else {
-                    add_node_error(g, *expr_node,
-                        buf_sprintf("expected maybe type, got '%s'", buf_ptr(&type_entry->name)));
-                    return g->builtin_types.entry_invalid;
-                }
-            }
-    }
-    zig_unreachable();
-}
-
 static TypeTableEntry *analyze_switch_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
         TypeTableEntry *expected_type, AstNode *node)
 {
@@ -6665,7 +5362,7 @@ static TypeTableEntry *analyze_expression_pointer_only(CodeGen *g, ImportTableEn
             return_type = analyze_symbol_expr(g, import, context, expected_type, node, pointer_only);
             break;
         case NodeTypePrefixOpExpr:
-            return_type = analyze_prefix_op_expr(g, import, context, expected_type, node);
+            zig_panic("moved to ir.cpp");
             break;
         case NodeTypeIfBoolExpr:
             return_type = analyze_if_bool_expr(g, import, context, expected_type, node);
src/codegen.cpp
@@ -2808,11 +2808,13 @@ static LLVMValueRef ir_render_instruction(CodeGen *g, IrExecutable *executable,
         case IrInstructionIdCast:
             return ir_render_cast(g, executable, (IrInstructionCast *)instruction);
         case IrInstructionIdCondBr:
+        case IrInstructionIdBr:
         case IrInstructionIdSwitchBr:
         case IrInstructionIdPhi:
         case IrInstructionIdStoreVar:
         case IrInstructionIdCall:
         case IrInstructionIdBuiltinCall:
+        case IrInstructionIdUnOp:
             zig_panic("TODO render more IR instructions to LLVM");
     }
     zig_unreachable();
src/ir.cpp
@@ -40,6 +40,14 @@ static size_t exec_next_debug_id(IrExecutable *exec) {
     return result;
 }
 
+static IrBasicBlock *ir_build_basic_block(IrBuilder *irb, const char *name_hint) {
+    IrBasicBlock *result = allocate<IrBasicBlock>(1);
+    result->name_hint = name_hint;
+    result->debug_id = exec_next_debug_id(irb->exec);
+    irb->exec->basic_block_list.append(result);
+    return result;
+}
+
 static constexpr IrInstructionId ir_instruction_id(IrInstructionCondBr *) {
     return IrInstructionIdCondBr;
 }
@@ -52,6 +60,10 @@ static constexpr IrInstructionId ir_instruction_id(IrInstructionPhi *) {
     return IrInstructionIdPhi;
 }
 
+static constexpr IrInstructionId ir_instruction_id(IrInstructionUnOp *) {
+    return IrInstructionIdUnOp;
+}
+
 static constexpr IrInstructionId ir_instruction_id(IrInstructionBinOp *) {
     return IrInstructionIdBinOp;
 }
@@ -84,6 +96,10 @@ static constexpr IrInstructionId ir_instruction_id(IrInstructionCast *) {
     return IrInstructionIdCast;
 }
 
+static constexpr IrInstructionId ir_instruction_id(IrInstructionBr *) {
+    return IrInstructionIdBr;
+}
+
 template<typename T>
 static T *ir_create_instruction(IrExecutable *exec, AstNode *source_node) {
     T *special_instruction = allocate<T>(1);
@@ -110,6 +126,18 @@ static IrInstruction *ir_build_cast(IrBuilder *irb, AstNode *source_node, IrInst
     return &cast_instruction->base;
 }
 
+static IrInstruction *ir_build_cond_br(IrBuilder *irb, AstNode *source_node, IrInstruction *condition,
+        IrBasicBlock *then_block, IrBasicBlock *else_block)
+{
+    IrInstructionCondBr *cond_br_instruction = ir_build_instruction<IrInstructionCondBr>(irb, source_node);
+    cond_br_instruction->base.type_entry = irb->codegen->builtin_types.entry_unreachable;
+    cond_br_instruction->base.static_value.ok = true;
+    cond_br_instruction->condition = condition;
+    cond_br_instruction->then_block = then_block;
+    cond_br_instruction->else_block = else_block;
+    return &cond_br_instruction->base;
+}
+
 static IrInstruction *ir_build_return(IrBuilder *irb, AstNode *source_node, IrInstruction *return_value) {
     IrInstructionReturn *return_instruction = ir_build_instruction<IrInstructionReturn>(irb, source_node);
     return_instruction->base.type_entry = irb->codegen->builtin_types.entry_unreachable;
@@ -198,6 +226,38 @@ static IrInstruction *ir_build_call(IrBuilder *irb, AstNode *source_node,
     return &call_instruction->base;
 }
 
+static IrInstruction *ir_build_builtin_call(IrBuilder *irb, AstNode *source_node,
+        BuiltinFnEntry *fn, IrInstruction **args)
+{
+    IrInstructionBuiltinCall *call_instruction = ir_build_instruction<IrInstructionBuiltinCall>(irb, source_node);
+    call_instruction->fn = fn;
+    call_instruction->args = args;
+    return &call_instruction->base;
+}
+
+static IrInstruction *ir_build_phi(IrBuilder *irb, AstNode *source_node,
+        size_t incoming_count, IrBasicBlock **incoming_blocks, IrInstruction **incoming_values)
+{
+    IrInstructionPhi *phi_instruction = ir_build_instruction<IrInstructionPhi>(irb, source_node);
+    phi_instruction->incoming_count = incoming_count;
+    phi_instruction->incoming_blocks = incoming_blocks;
+    phi_instruction->incoming_values = incoming_values;
+    return &phi_instruction->base;
+}
+
+static IrInstruction *ir_build_br(IrBuilder *irb, AstNode *source_node, IrBasicBlock *dest_block) {
+    IrInstructionBr *br_instruction = ir_build_instruction<IrInstructionBr>(irb, source_node);
+    br_instruction->dest_block = dest_block;
+    return &br_instruction->base;
+}
+
+static IrInstruction *ir_build_un_op(IrBuilder *irb, AstNode *source_node, IrUnOp op_id, IrInstruction *value) {
+    IrInstructionUnOp *br_instruction = ir_build_instruction<IrInstructionUnOp>(irb, source_node);
+    br_instruction->op_id = op_id;
+    br_instruction->value = value;
+    return &br_instruction->base;
+}
+
 
 //static size_t get_conditional_defer_count(BlockContext *inner_block, BlockContext *outer_block) {
 //    size_t result = 0;
@@ -246,6 +306,12 @@ static void ir_gen_defers_for_block(IrBuilder *irb, BlockContext *inner_block, B
 //    return ir_build_return(irb, source_node, value);
 //}
 
+static void ir_set_cursor_at_end(IrBuilder *irb, IrBasicBlock *basic_block) {
+    assert(basic_block);
+
+    irb->current_basic_block = basic_block;
+}
+
 static IrInstruction *ir_gen_block(IrBuilder *irb, AstNode *block_node) {
     assert(block_node->type == NodeTypeBlock);
 
@@ -433,15 +499,53 @@ static IrInstruction *ir_gen_symbol(IrBuilder *irb, AstNode *node, bool pointer_
     return irb->codegen->invalid_instruction;
 }
 
-static IrInstruction *ir_gen_fn_call(IrBuilder *irb, AstNode *node) {
+static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, AstNode *node) {
     assert(node->type == NodeTypeFnCallExpr);
 
-    if (node->data.fn_call_expr.is_builtin) {
-        zig_panic("TODO ir gen builtin fn");
+    AstNode *fn_ref_expr = node->data.fn_call_expr.fn_ref_expr;
+    Buf *name = fn_ref_expr->data.symbol_expr.symbol;
+    auto entry = irb->codegen->builtin_fn_table.maybe_get(name);
+
+    if (!entry) {
+        add_node_error(irb->codegen, node,
+                buf_sprintf("invalid builtin function: '%s'", buf_ptr(name)));
+        return irb->codegen->invalid_instruction;
+    }
+
+    BuiltinFnEntry *builtin_fn = entry->value;
+    size_t actual_param_count = node->data.fn_call_expr.params.length;
+
+    if (builtin_fn->param_count != actual_param_count) {
+        add_node_error(irb->codegen, node,
+                buf_sprintf("expected %zu arguments, got %zu",
+                    builtin_fn->param_count, actual_param_count));
+        return irb->codegen->invalid_instruction;
+    }
+
+    builtin_fn->ref_count += 1;
+
+    IrInstruction **args = allocate<IrInstruction *>(actual_param_count);
+    for (size_t i = 0; i < actual_param_count; i += 1) {
+        AstNode *arg_node = node->data.fn_call_expr.params.at(i);
+        IrInstruction *arg = ir_gen_node(irb, arg_node, node->block_context);
+        if (arg == irb->codegen->invalid_instruction)
+            return arg;
+        args[i] = arg;
     }
 
+    return ir_build_builtin_call(irb, node, builtin_fn, args);
+}
+
+static IrInstruction *ir_gen_fn_call(IrBuilder *irb, AstNode *node) {
+    assert(node->type == NodeTypeFnCallExpr);
+
+    if (node->data.fn_call_expr.is_builtin)
+        return ir_gen_builtin_fn_call(irb, node);
+
     AstNode *fn_ref_node = node->data.fn_call_expr.fn_ref_expr;
     IrInstruction *fn = ir_gen_node(irb, fn_ref_node, node->block_context);
+    if (fn == irb->codegen->invalid_instruction)
+        return fn;
 
     size_t arg_count = node->data.fn_call_expr.params.length;
     IrInstruction **args = allocate<IrInstruction*>(arg_count);
@@ -453,9 +557,102 @@ static IrInstruction *ir_gen_fn_call(IrBuilder *irb, AstNode *node) {
     return ir_build_call(irb, node, fn, arg_count, args);
 }
 
+static IrInstruction *ir_gen_if_bool_expr(IrBuilder *irb, AstNode *node) {
+    assert(node->type == NodeTypeIfBoolExpr);
+
+    IrInstruction *condition = ir_gen_node(irb, node->data.if_bool_expr.condition, node->block_context);
+    if (condition == irb->codegen->invalid_instruction)
+        return condition;
+
+    AstNode *then_node = node->data.if_bool_expr.then_block;
+    AstNode *else_node = node->data.if_bool_expr.else_node;
+
+    IrBasicBlock *then_block = ir_build_basic_block(irb, "Then");
+    IrBasicBlock *else_block = ir_build_basic_block(irb, "Else");
+    IrBasicBlock *endif_block = ir_build_basic_block(irb, "EndIf");
+
+    ir_build_cond_br(irb, condition->source_node, condition, then_block, else_block);
+
+    ir_set_cursor_at_end(irb, then_block);
+    IrInstruction *then_expr_result = ir_gen_node(irb, then_node, node->block_context);
+    if (then_expr_result == irb->codegen->invalid_instruction)
+        return then_expr_result;
+    IrBasicBlock *after_then_block = irb->current_basic_block;
+    ir_build_br(irb, node, endif_block);
+
+    ir_set_cursor_at_end(irb, else_block);
+    IrInstruction *else_expr_result;
+    if (else_node) {
+        else_expr_result = ir_gen_node(irb, else_node, node->block_context);
+        if (else_expr_result == irb->codegen->invalid_instruction)
+            return else_expr_result;
+    } else {
+        else_expr_result = ir_build_const_void(irb, node);
+    }
+    IrBasicBlock *after_else_block = irb->current_basic_block;
+    ir_build_br(irb, node, endif_block);
+
+    ir_set_cursor_at_end(irb, endif_block);
+    IrInstruction **incoming_values = allocate<IrInstruction *>(2);
+    incoming_values[0] = then_expr_result;
+    incoming_values[1] = else_expr_result;
+    IrBasicBlock **incoming_blocks = allocate<IrBasicBlock *>(2);
+    incoming_blocks[0] = after_then_block;
+    incoming_blocks[1] = after_else_block;
+
+    return ir_build_phi(irb, node, 2, incoming_blocks, incoming_values);
+}
+
+static IrInstruction *ir_gen_prefix_op_id(IrBuilder *irb, AstNode *node, IrUnOp op_id) {
+    assert(node->type == NodeTypePrefixOpExpr);
+    AstNode *expr_node = node->data.prefix_op_expr.primary_expr;
+
+    IrInstruction *value = ir_gen_node(irb, expr_node, node->block_context);
+    if (value == irb->codegen->invalid_instruction)
+        return value;
+
+    return ir_build_un_op(irb, node, op_id, value);
+}
+
+static IrInstruction *ir_gen_prefix_op_expr(IrBuilder *irb, AstNode *node) {
+    assert(node->type == NodeTypePrefixOpExpr);
+
+    PrefixOp prefix_op = node->data.prefix_op_expr.prefix_op;
+    //AstNode *expr_node = node->data.prefix_op_expr.primary_expr;
+
+    switch (prefix_op) {
+        case PrefixOpInvalid:
+            zig_unreachable();
+        case PrefixOpBoolNot:
+            return ir_gen_prefix_op_id(irb, node, IrUnOpBoolNot);
+        case PrefixOpBinNot:
+            return ir_gen_prefix_op_id(irb, node, IrUnOpBinNot);
+        case PrefixOpNegation:
+            return ir_gen_prefix_op_id(irb, node, IrUnOpNegation);
+        case PrefixOpNegationWrap:
+            return ir_gen_prefix_op_id(irb, node, IrUnOpNegationWrap);
+        case PrefixOpAddressOf:
+            return ir_gen_prefix_op_id(irb, node, IrUnOpAddressOf);
+        case PrefixOpConstAddressOf:
+            return ir_gen_prefix_op_id(irb, node, IrUnOpConstAddressOf);
+        case PrefixOpDereference:
+            return ir_gen_prefix_op_id(irb, node, IrUnOpDereference);
+        case PrefixOpMaybe:
+            return ir_gen_prefix_op_id(irb, node, IrUnOpMaybe);
+        case PrefixOpError:
+            return ir_gen_prefix_op_id(irb, node, IrUnOpError);
+        case PrefixOpUnwrapError:
+            return ir_gen_prefix_op_id(irb, node, IrUnOpUnwrapError);
+        case PrefixOpUnwrapMaybe:
+            return ir_gen_prefix_op_id(irb, node, IrUnOpUnwrapMaybe);
+    }
+    zig_unreachable();
+}
+
 static IrInstruction *ir_gen_node_extra(IrBuilder *irb, AstNode *node, BlockContext *block_context,
         bool pointer_only)
 {
+    assert(block_context);
     node->block_context = block_context;
 
     switch (node->type) {
@@ -469,15 +666,17 @@ static IrInstruction *ir_gen_node_extra(IrBuilder *irb, AstNode *node, BlockCont
             return ir_gen_symbol(irb, node, pointer_only);
         case NodeTypeFnCallExpr:
             return ir_gen_fn_call(irb, node);
+        case NodeTypeIfBoolExpr:
+            return ir_gen_if_bool_expr(irb, node);
+        case NodeTypePrefixOpExpr:
+            return ir_gen_prefix_op_expr(irb, node);
         case NodeTypeUnwrapErrorExpr:
         case NodeTypeReturnExpr:
         case NodeTypeDefer:
         case NodeTypeVariableDeclaration:
-        case NodeTypePrefixOpExpr:
         case NodeTypeArrayAccessExpr:
         case NodeTypeSliceExpr:
         case NodeTypeFieldAccessExpr:
-        case NodeTypeIfBoolExpr:
         case NodeTypeIfVarExpr:
         case NodeTypeWhileExpr:
         case NodeTypeForExpr:
@@ -533,8 +732,7 @@ static IrInstruction *ir_gen_add_return(CodeGen *g, AstNode *node, BlockContext
     irb->codegen = g;
     irb->exec = ir_executable;
 
-    irb->current_basic_block = allocate<IrBasicBlock>(1);
-    irb->exec->basic_block_list.append(irb->current_basic_block);
+    irb->current_basic_block = ir_build_basic_block(irb, "Entry");
 
     IrInstruction *result = ir_gen_node_extra(irb, node, scope, pointer_only);
     assert(result);
@@ -648,7 +846,7 @@ static bool ir_num_lit_fits_in_other_type(IrAnalyze *ira, IrInstruction *instruc
 
     const char *num_lit_str = (const_val->data.x_bignum.kind == BigNumKindFloat) ? "float" : "integer";
 
-    add_node_error(ira->old_irb.codegen, instruction->source_node,
+    add_node_error(ira->codegen, instruction->source_node,
         buf_sprintf("%s value %s cannot be implicitly casted to type '%s'",
             num_lit_str,
             buf_ptr(bignum_to_buf(&const_val->data.x_bignum)),
@@ -662,7 +860,7 @@ static TypeTableEntry *ir_determine_peer_types(IrAnalyze *ira, IrInstruction *pa
     assert(instruction_count >= 1);
     IrInstruction *prev_inst = instructions[0];
     if (prev_inst->type_entry->id == TypeTableEntryIdInvalid) {
-        return ira->old_irb.codegen->builtin_types.entry_invalid;
+        return ira->codegen->builtin_types.entry_invalid;
     }
     for (size_t i = 1; i < instruction_count; i += 1) {
         IrInstruction *cur_inst = instructions[i];
@@ -709,7 +907,7 @@ static TypeTableEntry *ir_determine_peer_types(IrAnalyze *ira, IrInstruction *pa
                 prev_inst = cur_inst;
                 continue;
             } else {
-                return ira->old_irb.codegen->builtin_types.entry_invalid;
+                return ira->codegen->builtin_types.entry_invalid;
             }
         } else if (cur_type->id == TypeTableEntryIdNumLitInt ||
                    cur_type->id == TypeTableEntryIdNumLitFloat)
@@ -717,14 +915,14 @@ static TypeTableEntry *ir_determine_peer_types(IrAnalyze *ira, IrInstruction *pa
             if (ir_num_lit_fits_in_other_type(ira, cur_inst, prev_type)) {
                 continue;
             } else {
-                return ira->old_irb.codegen->builtin_types.entry_invalid;
+                return ira->codegen->builtin_types.entry_invalid;
             }
         } else {
-            add_node_error(ira->old_irb.codegen, parent_instruction->source_node,
+            add_node_error(ira->codegen, parent_instruction->source_node,
                 buf_sprintf("incompatible types: '%s' and '%s'",
                     buf_ptr(&prev_type->name), buf_ptr(&cur_type->name)));
 
-            return ira->old_irb.codegen->builtin_types.entry_invalid;
+            return ira->codegen->builtin_types.entry_invalid;
         }
     }
     return prev_inst->type_entry;
@@ -1119,7 +1317,7 @@ static IrInstruction *ir_analyze_cast(IrAnalyze *ira, IrInstruction *source_inst
 
 static IrInstruction *ir_get_casted_value(IrAnalyze *ira, IrInstruction *value, TypeTableEntry *expected_type) {
     assert(value);
-    assert(value != ira->old_irb.codegen->invalid_instruction);
+    assert(value != ira->codegen->invalid_instruction);
     assert(!expected_type || expected_type->id != TypeTableEntryIdInvalid);
     assert(value->type_entry);
     assert(value->type_entry->id != TypeTableEntryIdInvalid);
@@ -1133,11 +1331,11 @@ static IrInstruction *ir_get_casted_value(IrAnalyze *ira, IrInstruction *value,
     ImplicitCastMatchResult result = ir_types_match_with_implicit_cast(ira, expected_type, value->type_entry, value);
     switch (result) {
         case ImplicitCastMatchResultNo:
-            add_node_error(ira->old_irb.codegen, first_executing_node(value->source_node),
+            add_node_error(ira->codegen, first_executing_node(value->source_node),
                 buf_sprintf("expected type '%s', got '%s'",
                     buf_ptr(&expected_type->name),
                     buf_ptr(&value->type_entry->name)));
-            return ira->old_irb.codegen->invalid_instruction;
+            return ira->codegen->invalid_instruction;
 
         case ImplicitCastMatchResultYes:
             {
@@ -1146,7 +1344,7 @@ static IrInstruction *ir_get_casted_value(IrAnalyze *ira, IrInstruction *value,
                 return cast_instruction;
             }
         case ImplicitCastMatchResultReportedError:
-            return ira->old_irb.codegen->invalid_instruction;
+            return ira->codegen->invalid_instruction;
     }
 
     zig_unreachable();
@@ -1182,15 +1380,15 @@ static TypeTableEntry *ir_analyze_bin_op_bool(IrAnalyze *ira, IrInstructionBinOp
     IrInstruction *op1 = bin_op_instruction->op1;
     IrInstruction *op2 = bin_op_instruction->op2;
 
-    TypeTableEntry *bool_type = ira->old_irb.codegen->builtin_types.entry_bool;
+    TypeTableEntry *bool_type = ira->codegen->builtin_types.entry_bool;
 
     IrInstruction *casted_op1 = ir_get_casted_value(ira, op1->other, bool_type);
-    if (casted_op1 == ira->old_irb.codegen->invalid_instruction)
-        return ira->old_irb.codegen->builtin_types.entry_invalid;
+    if (casted_op1 == ira->codegen->invalid_instruction)
+        return ira->codegen->builtin_types.entry_invalid;
 
     IrInstruction *casted_op2 = ir_get_casted_value(ira, op2->other, bool_type);
-    if (casted_op2 == ira->old_irb.codegen->invalid_instruction)
-        return ira->old_irb.codegen->builtin_types.entry_invalid;
+    if (casted_op2 == ira->codegen->invalid_instruction)
+        return ira->codegen->builtin_types.entry_invalid;
 
     ConstExprValue *op1_val = &casted_op1->static_value;
     ConstExprValue *op2_val = &casted_op2->static_value;
@@ -1232,7 +1430,7 @@ static TypeTableEntry *ir_analyze_bin_op_cmp(IrAnalyze *ira, IrInstructionBinOp
     AstNode *source_node = bin_op_instruction->base.source_node;
     switch (resolved_type->id) {
         case TypeTableEntryIdInvalid:
-            return ira->old_irb.codegen->builtin_types.entry_invalid;
+            return ira->codegen->builtin_types.entry_invalid;
 
         case TypeTableEntryIdNumLitFloat:
         case TypeTableEntryIdNumLitInt:
@@ -1251,17 +1449,17 @@ static TypeTableEntry *ir_analyze_bin_op_cmp(IrAnalyze *ira, IrInstructionBinOp
         case TypeTableEntryIdBlock:
         case TypeTableEntryIdGenericFn:
             if (!is_equality_cmp) {
-                add_node_error(ira->old_irb.codegen, source_node,
+                add_node_error(ira->codegen, source_node,
                     buf_sprintf("operator not allowed for type '%s'", buf_ptr(&resolved_type->name)));
-                return ira->old_irb.codegen->builtin_types.entry_invalid;
+                return ira->codegen->builtin_types.entry_invalid;
             }
             break;
 
         case TypeTableEntryIdEnum:
             if (!is_equality_cmp || resolved_type->data.enumeration.gen_field_count != 0) {
-                add_node_error(ira->old_irb.codegen, source_node,
+                add_node_error(ira->codegen, source_node,
                     buf_sprintf("operator not allowed for type '%s'", buf_ptr(&resolved_type->name)));
-                return ira->old_irb.codegen->builtin_types.entry_invalid;
+                return ira->codegen->builtin_types.entry_invalid;
             }
             break;
 
@@ -1273,9 +1471,9 @@ static TypeTableEntry *ir_analyze_bin_op_cmp(IrAnalyze *ira, IrInstructionBinOp
         case TypeTableEntryIdMaybe:
         case TypeTableEntryIdErrorUnion:
         case TypeTableEntryIdUnion:
-            add_node_error(ira->old_irb.codegen, source_node,
+            add_node_error(ira->codegen, source_node,
                 buf_sprintf("operator not allowed for type '%s'", buf_ptr(&resolved_type->name)));
-            return ira->old_irb.codegen->builtin_types.entry_invalid;
+            return ira->codegen->builtin_types.entry_invalid;
 
         case TypeTableEntryIdVar:
             zig_unreachable();
@@ -1286,7 +1484,7 @@ static TypeTableEntry *ir_analyze_bin_op_cmp(IrAnalyze *ira, IrInstructionBinOp
     ir_link_new(ir_build_bin_op(&ira->new_irb, bin_op_instruction->base.source_node,
                 op_id, op1->other, op2->other), &bin_op_instruction->base);
 
-    return ira->old_irb.codegen->builtin_types.entry_bool;
+    return ira->codegen->builtin_types.entry_bool;
 }
 
 static uint64_t max_unsigned_val(TypeTableEntry *type_entry) {
@@ -1408,11 +1606,11 @@ static TypeTableEntry *ir_analyze_bin_op_math(IrAnalyze *ira, IrInstructionBinOp
         // float
     } else {
         AstNode *source_node = bin_op_instruction->base.source_node;
-        add_node_error(ira->old_irb.codegen, source_node,
+        add_node_error(ira->codegen, source_node,
             buf_sprintf("invalid operands to binary expression: '%s' and '%s'",
                 buf_ptr(&op1->type_entry->name),
                 buf_ptr(&op2->type_entry->name)));
-        return ira->old_irb.codegen->builtin_types.entry_invalid;
+        return ira->codegen->builtin_types.entry_invalid;
     }
 
     if (op1->static_value.ok && op2->static_value.ok) {
@@ -1525,6 +1723,1224 @@ static TypeTableEntry *ir_analyze_instruction_call(IrAnalyze *ira, IrInstruction
     }
 }
 
+static TypeTableEntry *ir_analyze_unary_bool_not(IrAnalyze *ira, IrInstructionUnOp *un_op_instruction) {
+    TypeTableEntry *bool_type = ira->codegen->builtin_types.entry_bool;
+
+    IrInstruction *casted_value = ir_get_casted_value(ira, un_op_instruction->value, bool_type);
+    if (casted_value == ira->codegen->invalid_instruction)
+        return ira->codegen->builtin_types.entry_invalid;
+
+    ConstExprValue *operand_val = &casted_value->static_value;
+    if (operand_val->ok) {
+        ConstExprValue *result_val = &un_op_instruction->base.static_value;
+        result_val->ok = true;
+        result_val->depends_on_compile_var = operand_val->depends_on_compile_var;
+        result_val->data.x_bool = !operand_val->data.x_bool;
+        return bool_type;
+    }
+
+    IrInstruction *new_instruction = ir_build_un_op(&ira->new_irb, un_op_instruction->base.source_node,
+            IrUnOpBoolNot, casted_value);
+    ir_link_new(new_instruction, &un_op_instruction->base);
+
+    return bool_type;
+}
+
+static TypeTableEntry *ir_analyze_instruction_un_op(IrAnalyze *ira, IrInstructionUnOp *un_op_instruction) {
+    IrUnOp op_id = un_op_instruction->op_id;
+    switch (op_id) {
+        case IrUnOpInvalid:
+            zig_unreachable();
+        case IrUnOpBoolNot:
+            return ir_analyze_unary_bool_not(ira, un_op_instruction);
+            zig_panic("TODO analyze PrefixOpBoolNot");
+        case IrUnOpBinNot:
+            zig_panic("TODO analyze PrefixOpBinNot");
+            //{
+            //    TypeTableEntry *expr_type = analyze_expression(g, import, context, expected_type,
+            //            *expr_node);
+            //    if (expr_type->id == TypeTableEntryIdInvalid) {
+            //        return expr_type;
+            //    } else if (expr_type->id == TypeTableEntryIdInt) {
+            //        return expr_type;
+            //    } else {
+            //        add_node_error(g, node, buf_sprintf("unable to perform binary not operation on type '%s'",
+            //                buf_ptr(&expr_type->name)));
+            //        return g->builtin_types.entry_invalid;
+            //    }
+            //    // TODO const expr eval
+            //}
+        case IrUnOpNegation:
+        case IrUnOpNegationWrap:
+            zig_panic("TODO analyze PrefixOpNegation[Wrap]");
+            //{
+            //    TypeTableEntry *expr_type = analyze_expression(g, import, context, nullptr, *expr_node);
+            //    if (expr_type->id == TypeTableEntryIdInvalid) {
+            //        return expr_type;
+            //    } else if ((expr_type->id == TypeTableEntryIdInt &&
+            //                expr_type->data.integral.is_signed) ||
+            //                expr_type->id == TypeTableEntryIdNumLitInt ||
+            //                ((expr_type->id == TypeTableEntryIdFloat ||
+            //                expr_type->id == TypeTableEntryIdNumLitFloat) &&
+            //                prefix_op != PrefixOpNegationWrap))
+            //    {
+            //        ConstExprValue *target_const_val = &get_resolved_expr(*expr_node)->const_val;
+            //        if (!target_const_val->ok) {
+            //            return expr_type;
+            //        }
+            //        ConstExprValue *const_val = &get_resolved_expr(node)->const_val;
+            //        const_val->ok = true;
+            //        const_val->depends_on_compile_var = target_const_val->depends_on_compile_var;
+            //        bignum_negate(&const_val->data.x_bignum, &target_const_val->data.x_bignum);
+            //        if (expr_type->id == TypeTableEntryIdFloat ||
+            //            expr_type->id == TypeTableEntryIdNumLitFloat ||
+            //            expr_type->id == TypeTableEntryIdNumLitInt)
+            //        {
+            //            return expr_type;
+            //        }
+
+            //        bool overflow = !bignum_fits_in_bits(&const_val->data.x_bignum,
+            //                expr_type->data.integral.bit_count, expr_type->data.integral.is_signed);
+            //        if (prefix_op == PrefixOpNegationWrap) {
+            //            if (overflow) {
+            //                const_val->data.x_bignum.is_negative = true;
+            //            }
+            //        } else if (overflow) {
+            //            add_node_error(g, *expr_node, buf_sprintf("negation caused overflow"));
+            //            return g->builtin_types.entry_invalid;
+            //        }
+            //        return expr_type;
+            //    } else {
+            //        const char *fmt = (prefix_op == PrefixOpNegationWrap) ?
+            //            "invalid wrapping negation type: '%s'" : "invalid negation type: '%s'";
+            //        add_node_error(g, node, buf_sprintf(fmt, buf_ptr(&expr_type->name)));
+            //        return g->builtin_types.entry_invalid;
+            //    }
+            //}
+        case IrUnOpAddressOf:
+        case IrUnOpConstAddressOf:
+            zig_panic("TODO analyze PrefixOpAddressOf and PrefixOpConstAddressOf");
+            //{
+            //    bool is_const = (prefix_op == PrefixOpConstAddressOf);
+
+            //    TypeTableEntry *child_type = analyze_lvalue(g, import, context,
+            //            *expr_node, LValPurposeAddressOf, is_const);
+
+            //    if (child_type->id == TypeTableEntryIdInvalid) {
+            //        return g->builtin_types.entry_invalid;
+            //    } else if (child_type->id == TypeTableEntryIdMetaType) {
+            //        TypeTableEntry *meta_type = analyze_type_expr_pointer_only(g, import, context,
+            //                *expr_node, true);
+            //        if (meta_type->id == TypeTableEntryIdInvalid) {
+            //            return g->builtin_types.entry_invalid;
+            //        } else if (meta_type->id == TypeTableEntryIdUnreachable) {
+            //            add_node_error(g, node, buf_create_from_str("pointer to unreachable not allowed"));
+            //            return g->builtin_types.entry_invalid;
+            //        } else {
+            //            return resolve_expr_const_val_as_type(g, node,
+            //                    get_pointer_to_type(g, meta_type, is_const), false);
+            //        }
+            //    } else if (child_type->id == TypeTableEntryIdNumLitInt ||
+            //               child_type->id == TypeTableEntryIdNumLitFloat)
+            //    {
+            //        add_node_error(g, *expr_node,
+            //            buf_sprintf("unable to get address of type '%s'", buf_ptr(&child_type->name)));
+            //        return g->builtin_types.entry_invalid;
+            //    } else {
+            //        return get_pointer_to_type(g, child_type, is_const);
+            //    }
+            //}
+        case IrUnOpDereference:
+            zig_panic("TODO analyze PrefixOpDereference");
+            //{
+            //    TypeTableEntry *type_entry = analyze_expression(g, import, context, nullptr, *expr_node);
+            //    if (type_entry->id == TypeTableEntryIdInvalid) {
+            //        return type_entry;
+            //    } else if (type_entry->id == TypeTableEntryIdPointer) {
+            //        return type_entry->data.pointer.child_type;
+            //    } else {
+            //        add_node_error(g, *expr_node,
+            //            buf_sprintf("indirection requires pointer operand ('%s' invalid)",
+            //                buf_ptr(&type_entry->name)));
+            //        return g->builtin_types.entry_invalid;
+            //    }
+            //}
+        case IrUnOpMaybe:
+            zig_panic("TODO analyze PrefixOpMaybe");
+            //{
+            //    TypeTableEntry *type_entry = analyze_expression(g, import, context, nullptr, *expr_node);
+
+            //    if (type_entry->id == TypeTableEntryIdInvalid) {
+            //        return type_entry;
+            //    } else if (type_entry->id == TypeTableEntryIdMetaType) {
+            //        TypeTableEntry *meta_type = resolve_type(g, *expr_node);
+            //        if (meta_type->id == TypeTableEntryIdInvalid) {
+            //            return g->builtin_types.entry_invalid;
+            //        } else if (meta_type->id == TypeTableEntryIdUnreachable) {
+            //            add_node_error(g, node, buf_create_from_str("unable to wrap unreachable in maybe type"));
+            //            return g->builtin_types.entry_invalid;
+            //        } else {
+            //            return resolve_expr_const_val_as_type(g, node, get_maybe_type(g, meta_type), false);
+            //        }
+            //    } else if (type_entry->id == TypeTableEntryIdUnreachable) {
+            //        add_node_error(g, *expr_node, buf_sprintf("unable to wrap unreachable in maybe type"));
+            //        return g->builtin_types.entry_invalid;
+            //    } else {
+            //        ConstExprValue *target_const_val = &get_resolved_expr(*expr_node)->const_val;
+            //        TypeTableEntry *maybe_type = get_maybe_type(g, type_entry);
+            //        if (!target_const_val->ok) {
+            //            return maybe_type;
+            //        }
+            //        return resolve_expr_const_val_as_non_null(g, node, maybe_type, target_const_val);
+            //    }
+            //}
+        case IrUnOpError:
+            zig_panic("TODO analyze PrefixOpError");
+            //{
+            //    TypeTableEntry *type_entry = analyze_expression(g, import, context, nullptr, *expr_node);
+
+            //    if (type_entry->id == TypeTableEntryIdInvalid) {
+            //        return type_entry;
+            //    } else if (type_entry->id == TypeTableEntryIdMetaType) {
+            //        TypeTableEntry *meta_type = resolve_type(g, *expr_node);
+            //        if (meta_type->id == TypeTableEntryIdInvalid) {
+            //            return meta_type;
+            //        } else if (meta_type->id == TypeTableEntryIdUnreachable) {
+            //            add_node_error(g, node, buf_create_from_str("unable to wrap unreachable in error type"));
+            //            return g->builtin_types.entry_invalid;
+            //        } else {
+            //            return resolve_expr_const_val_as_type(g, node, get_error_type(g, meta_type), false);
+            //        }
+            //    } else if (type_entry->id == TypeTableEntryIdUnreachable) {
+            //        add_node_error(g, *expr_node, buf_sprintf("unable to wrap unreachable in error type"));
+            //        return g->builtin_types.entry_invalid;
+            //    } else {
+            //        // TODO eval const expr
+            //        return get_error_type(g, type_entry);
+            //    }
+
+            //}
+        case IrUnOpUnwrapError:
+            zig_panic("TODO analyze PrefixOpUnwrapError");
+            //{
+            //    TypeTableEntry *type_entry = analyze_expression(g, import, context, nullptr, *expr_node);
+
+            //    if (type_entry->id == TypeTableEntryIdInvalid) {
+            //        return type_entry;
+            //    } else if (type_entry->id == TypeTableEntryIdErrorUnion) {
+            //        return type_entry->data.error.child_type;
+            //    } else {
+            //        add_node_error(g, *expr_node,
+            //            buf_sprintf("expected error type, got '%s'", buf_ptr(&type_entry->name)));
+            //        return g->builtin_types.entry_invalid;
+            //    }
+            //}
+        case IrUnOpUnwrapMaybe:
+            zig_panic("TODO analyze PrefixOpUnwrapMaybe");
+            //{
+            //    TypeTableEntry *type_entry = analyze_expression(g, import, context, nullptr, *expr_node);
+
+            //    if (type_entry->id == TypeTableEntryIdInvalid) {
+            //        return type_entry;
+            //    } else if (type_entry->id == TypeTableEntryIdMaybe) {
+            //        return type_entry->data.maybe.child_type;
+            //    } else {
+            //        add_node_error(g, *expr_node,
+            //            buf_sprintf("expected maybe type, got '%s'", buf_ptr(&type_entry->name)));
+            //        return g->builtin_types.entry_invalid;
+            //    }
+            //}
+        case IrUnOpErrorReturn:
+            zig_panic("TODO analyze IrUnOpErrorReturn");
+        case IrUnOpMaybeReturn:
+            zig_panic("TODO analyze IrUnOpMaybeReturn");
+    }
+    zig_unreachable();
+}
+
+//static TypeTableEntry *analyze_min_max_value(CodeGen *g, ImportTableEntry *import, BlockContext *context,
+//        AstNode *node, const char *err_format, bool is_max)
+//{
+//    assert(node->type == NodeTypeFnCallExpr);
+//    assert(node->data.fn_call_expr.params.length == 1);
+//
+//    AstNode *type_node = node->data.fn_call_expr.params.at(0);
+//    TypeTableEntry *type_entry = analyze_type_expr(g, import, context, type_node);
+//
+//    if (type_entry->id == TypeTableEntryIdInvalid) {
+//        return g->builtin_types.entry_invalid;
+//    } else if (type_entry->id == TypeTableEntryIdInt) {
+//        eval_min_max_value(g, type_entry, &get_resolved_expr(node)->const_val, is_max);
+//        return g->builtin_types.entry_num_lit_int;
+//    } else if (type_entry->id == TypeTableEntryIdFloat) {
+//        eval_min_max_value(g, type_entry, &get_resolved_expr(node)->const_val, is_max);
+//        return g->builtin_types.entry_num_lit_float;
+//    } else if (type_entry->id == TypeTableEntryIdBool) {
+//        eval_min_max_value(g, type_entry, &get_resolved_expr(node)->const_val, is_max);
+//        return type_entry;
+//    } else {
+//        add_node_error(g, node,
+//                buf_sprintf(err_format, buf_ptr(&type_entry->name)));
+//        return g->builtin_types.entry_invalid;
+//    }
+//}
+
+//static TypeTableEntry *analyze_import(CodeGen *g, ImportTableEntry *import, BlockContext *context,
+//        AstNode *node)
+//{
+//    assert(node->type == NodeTypeFnCallExpr);
+//
+//    if (context->fn_entry) {
+//        add_node_error(g, node, buf_sprintf("@import invalid inside function bodies"));
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    AstNode *first_param_node = node->data.fn_call_expr.params.at(0);
+//    Buf *import_target_str = resolve_const_expr_str(g, import, context, first_param_node->parent_field);
+//    if (!import_target_str) {
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    Buf *import_target_path;
+//    Buf *search_dir;
+//    assert(import->package);
+//    PackageTableEntry *target_package;
+//    auto package_entry = import->package->package_table.maybe_get(import_target_str);
+//    if (package_entry) {
+//        target_package = package_entry->value;
+//        import_target_path = &target_package->root_src_path;
+//        search_dir = &target_package->root_src_dir;
+//    } else {
+//        // try it as a filename
+//        target_package = import->package;
+//        import_target_path = import_target_str;
+//        search_dir = &import->package->root_src_dir;
+//    }
+//
+//    Buf full_path = BUF_INIT;
+//    os_path_join(search_dir, import_target_path, &full_path);
+//
+//    Buf *import_code = buf_alloc();
+//    Buf *abs_full_path = buf_alloc();
+//    int err;
+//    if ((err = os_path_real(&full_path, abs_full_path))) {
+//        if (err == ErrorFileNotFound) {
+//            add_node_error(g, node,
+//                    buf_sprintf("unable to find '%s'", buf_ptr(import_target_path)));
+//            return g->builtin_types.entry_invalid;
+//        } else {
+//            g->error_during_imports = true;
+//            add_node_error(g, node,
+//                    buf_sprintf("unable to open '%s': %s", buf_ptr(&full_path), err_str(err)));
+//            return g->builtin_types.entry_invalid;
+//        }
+//    }
+//
+//    auto import_entry = g->import_table.maybe_get(abs_full_path);
+//    if (import_entry) {
+//        return resolve_expr_const_val_as_import(g, node, import_entry->value);
+//    }
+//
+//    if ((err = os_fetch_file_path(abs_full_path, import_code))) {
+//        if (err == ErrorFileNotFound) {
+//            add_node_error(g, node,
+//                    buf_sprintf("unable to find '%s'", buf_ptr(import_target_path)));
+//            return g->builtin_types.entry_invalid;
+//        } else {
+//            add_node_error(g, node,
+//                    buf_sprintf("unable to open '%s': %s", buf_ptr(&full_path), err_str(err)));
+//            return g->builtin_types.entry_invalid;
+//        }
+//    }
+//    ImportTableEntry *target_import = add_source_file(g, target_package,
+//            abs_full_path, search_dir, import_target_path, import_code);
+//
+//    scan_decls(g, target_import, target_import->block_context, target_import->root);
+//
+//    return resolve_expr_const_val_as_import(g, node, target_import);
+//}
+//
+//static TypeTableEntry *analyze_c_import(CodeGen *g, ImportTableEntry *parent_import,
+//        BlockContext *parent_context, AstNode *node)
+//{
+//    assert(node->type == NodeTypeFnCallExpr);
+//
+//    if (parent_context->fn_entry) {
+//        add_node_error(g, node, buf_sprintf("@c_import invalid inside function bodies"));
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    AstNode *block_node = node->data.fn_call_expr.params.at(0);
+//
+//    BlockContext *child_context = new_block_context(node, parent_context);
+//    child_context->c_import_buf = buf_alloc();
+//
+//    TypeTableEntry *resolved_type = analyze_expression(g, parent_import, child_context,
+//            g->builtin_types.entry_void, block_node);
+//
+//    if (resolved_type->id == TypeTableEntryIdInvalid) {
+//        return resolved_type;
+//    }
+//
+//    find_libc_include_path(g);
+//
+//    ImportTableEntry *child_import = allocate<ImportTableEntry>(1);
+//    child_import->c_import_node = node;
+//
+//    ZigList<ErrorMsg *> errors = {0};
+//
+//    int err;
+//    if ((err = parse_h_buf(child_import, &errors, child_context->c_import_buf, g, node))) {
+//        zig_panic("unable to parse h file: %s\n", err_str(err));
+//    }
+//
+//    if (errors.length > 0) {
+//        ErrorMsg *parent_err_msg = add_node_error(g, node, buf_sprintf("C import failed"));
+//        for (size_t i = 0; i < errors.length; i += 1) {
+//            ErrorMsg *err_msg = errors.at(i);
+//            err_msg_add_note(parent_err_msg, err_msg);
+//        }
+//
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    if (g->verbose) {
+//        fprintf(stderr, "\nc_import:\n");
+//        fprintf(stderr, "-----------\n");
+//        ast_render(stderr, child_import->root, 4);
+//    }
+//
+//    child_import->di_file = parent_import->di_file;
+//    child_import->block_context = new_block_context(child_import->root, nullptr);
+//
+//    scan_decls(g, child_import, child_import->block_context, child_import->root);
+//    return resolve_expr_const_val_as_import(g, node, child_import);
+//}
+//
+//static TypeTableEntry *analyze_err_name(CodeGen *g, ImportTableEntry *import,
+//        BlockContext *context, AstNode *node)
+//{
+//    assert(node->type == NodeTypeFnCallExpr);
+//
+//    AstNode *err_value = node->data.fn_call_expr.params.at(0);
+//    TypeTableEntry *resolved_type = analyze_expression(g, import, context,
+//            g->builtin_types.entry_pure_error, err_value);
+//
+//    if (resolved_type->id == TypeTableEntryIdInvalid) {
+//        return resolved_type;
+//    }
+//
+//    g->generate_error_name_table = true;
+//
+//    TypeTableEntry *str_type = get_slice_type(g, g->builtin_types.entry_u8, true);
+//    return str_type;
+//}
+//
+//static TypeTableEntry *analyze_embed_file(CodeGen *g, ImportTableEntry *import,
+//        BlockContext *context, AstNode *node)
+//{
+//    assert(node->type == NodeTypeFnCallExpr);
+//
+//    AstNode **first_param_node = &node->data.fn_call_expr.params.at(0);
+//    Buf *rel_file_path = resolve_const_expr_str(g, import, context, first_param_node);
+//    if (!rel_file_path) {
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    // figure out absolute path to resource
+//    Buf source_dir_path = BUF_INIT;
+//    os_path_dirname(import->path, &source_dir_path);
+//
+//    Buf file_path = BUF_INIT;
+//    os_path_resolve(&source_dir_path, rel_file_path, &file_path);
+//
+//    // load from file system into const expr
+//    Buf file_contents = BUF_INIT;
+//    int err;
+//    if ((err = os_fetch_file_path(&file_path, &file_contents))) {
+//        if (err == ErrorFileNotFound) {
+//            add_node_error(g, node,
+//                    buf_sprintf("unable to find '%s'", buf_ptr(&file_path)));
+//            return g->builtin_types.entry_invalid;
+//        } else {
+//            add_node_error(g, node,
+//                    buf_sprintf("unable to open '%s': %s", buf_ptr(&file_path), err_str(err)));
+//            return g->builtin_types.entry_invalid;
+//        }
+//    }
+//
+//    // TODO add dependency on the file we embedded so that we know if it changes
+//    // we'll have to invalidate the cache
+//
+//    return resolve_expr_const_val_as_string_lit(g, node, &file_contents);
+//}
+//
+//static TypeTableEntry *analyze_cmpxchg(CodeGen *g, ImportTableEntry *import,
+//        BlockContext *context, AstNode *node)
+//{
+//    assert(node->type == NodeTypeFnCallExpr);
+//
+//    AstNode **ptr_arg = &node->data.fn_call_expr.params.at(0);
+//    AstNode **cmp_arg = &node->data.fn_call_expr.params.at(1);
+//    AstNode **new_arg = &node->data.fn_call_expr.params.at(2);
+//    AstNode **success_order_arg = &node->data.fn_call_expr.params.at(3);
+//    AstNode **failure_order_arg = &node->data.fn_call_expr.params.at(4);
+//
+//    TypeTableEntry *ptr_type = analyze_expression(g, import, context, nullptr, *ptr_arg);
+//    if (ptr_type->id == TypeTableEntryIdInvalid) {
+//        return g->builtin_types.entry_invalid;
+//    } else if (ptr_type->id != TypeTableEntryIdPointer) {
+//        add_node_error(g, *ptr_arg,
+//            buf_sprintf("expected pointer argument, got '%s'", buf_ptr(&ptr_type->name)));
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    TypeTableEntry *child_type = ptr_type->data.pointer.child_type;
+//    TypeTableEntry *cmp_type = analyze_expression(g, import, context, child_type, *cmp_arg);
+//    TypeTableEntry *new_type = analyze_expression(g, import, context, child_type, *new_arg);
+//
+//    TypeTableEntry *success_order_type = analyze_expression(g, import, context,
+//            g->builtin_types.entry_atomic_order_enum, *success_order_arg);
+//    TypeTableEntry *failure_order_type = analyze_expression(g, import, context,
+//            g->builtin_types.entry_atomic_order_enum, *failure_order_arg);
+//
+//    if (cmp_type->id == TypeTableEntryIdInvalid ||
+//        new_type->id == TypeTableEntryIdInvalid ||
+//        success_order_type->id == TypeTableEntryIdInvalid ||
+//        failure_order_type->id == TypeTableEntryIdInvalid)
+//    {
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    ConstExprValue *success_order_val = &get_resolved_expr(*success_order_arg)->const_val;
+//    ConstExprValue *failure_order_val = &get_resolved_expr(*failure_order_arg)->const_val;
+//    if (!success_order_val->ok) {
+//        add_node_error(g, *success_order_arg, buf_sprintf("unable to evaluate constant expression"));
+//        return g->builtin_types.entry_invalid;
+//    } else if (!failure_order_val->ok) {
+//        add_node_error(g, *failure_order_arg, buf_sprintf("unable to evaluate constant expression"));
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    if (success_order_val->data.x_enum.tag < AtomicOrderMonotonic) {
+//        add_node_error(g, *success_order_arg,
+//                buf_sprintf("success atomic ordering must be Monotonic or stricter"));
+//        return g->builtin_types.entry_invalid;
+//    }
+//    if (failure_order_val->data.x_enum.tag < AtomicOrderMonotonic) {
+//        add_node_error(g, *failure_order_arg,
+//                buf_sprintf("failure atomic ordering must be Monotonic or stricter"));
+//        return g->builtin_types.entry_invalid;
+//    }
+//    if (failure_order_val->data.x_enum.tag > success_order_val->data.x_enum.tag) {
+//        add_node_error(g, *failure_order_arg,
+//                buf_sprintf("failure atomic ordering must be no stricter than success"));
+//        return g->builtin_types.entry_invalid;
+//    }
+//    if (failure_order_val->data.x_enum.tag == AtomicOrderRelease ||
+//        failure_order_val->data.x_enum.tag == AtomicOrderAcqRel)
+//    {
+//        add_node_error(g, *failure_order_arg,
+//                buf_sprintf("failure atomic ordering must not be Release or AcqRel"));
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    return g->builtin_types.entry_bool;
+//}
+//
+//static TypeTableEntry *analyze_fence(CodeGen *g, ImportTableEntry *import,
+//        BlockContext *context, AstNode *node)
+//{
+//    assert(node->type == NodeTypeFnCallExpr);
+//
+//    AstNode **atomic_order_arg = &node->data.fn_call_expr.params.at(0);
+//    TypeTableEntry *atomic_order_type = analyze_expression(g, import, context,
+//            g->builtin_types.entry_atomic_order_enum, *atomic_order_arg);
+//
+//    if (atomic_order_type->id == TypeTableEntryIdInvalid) {
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    ConstExprValue *atomic_order_val = &get_resolved_expr(*atomic_order_arg)->const_val;
+//
+//    if (!atomic_order_val->ok) {
+//        add_node_error(g, *atomic_order_arg, buf_sprintf("unable to evaluate constant expression"));
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    return g->builtin_types.entry_void;
+//}
+//
+//static TypeTableEntry *analyze_div_exact(CodeGen *g, ImportTableEntry *import,
+//        BlockContext *context, AstNode *node)
+//{
+//    assert(node->type == NodeTypeFnCallExpr);
+//
+//    AstNode **op1 = &node->data.fn_call_expr.params.at(0);
+//    AstNode **op2 = &node->data.fn_call_expr.params.at(1);
+//
+//    TypeTableEntry *op1_type = analyze_expression(g, import, context, nullptr, *op1);
+//    TypeTableEntry *op2_type = analyze_expression(g, import, context, nullptr, *op2);
+//
+//    AstNode *op_nodes[] = {*op1, *op2};
+//    TypeTableEntry *op_types[] = {op1_type, op2_type};
+//    TypeTableEntry *result_type = resolve_peer_type_compatibility(g, import, context, node,
+//            op_nodes, op_types, 2);
+//
+//    if (result_type->id == TypeTableEntryIdInvalid) {
+//        return g->builtin_types.entry_invalid;
+//    } else if (result_type->id == TypeTableEntryIdInt) {
+//        return result_type;
+//    } else if (result_type->id == TypeTableEntryIdNumLitInt) {
+//        // check for division by zero
+//        // check for non exact division
+//        zig_panic("TODO");
+//    } else {
+//        add_node_error(g, node,
+//                buf_sprintf("expected integer type, got '%s'", buf_ptr(&result_type->name)));
+//        return g->builtin_types.entry_invalid;
+//    }
+//}
+//
+//static TypeTableEntry *analyze_truncate(CodeGen *g, ImportTableEntry *import,
+//        BlockContext *context, AstNode *node)
+//{
+//    assert(node->type == NodeTypeFnCallExpr);
+//
+//    AstNode **op1 = &node->data.fn_call_expr.params.at(0);
+//    AstNode **op2 = &node->data.fn_call_expr.params.at(1);
+//
+//    TypeTableEntry *dest_type = analyze_type_expr(g, import, context, *op1);
+//    TypeTableEntry *src_type = analyze_expression(g, import, context, nullptr, *op2);
+//
+//    if (dest_type->id == TypeTableEntryIdInvalid || src_type->id == TypeTableEntryIdInvalid) {
+//        return g->builtin_types.entry_invalid;
+//    } else if (dest_type->id != TypeTableEntryIdInt) {
+//        add_node_error(g, *op1,
+//                buf_sprintf("expected integer type, got '%s'", buf_ptr(&dest_type->name)));
+//        return g->builtin_types.entry_invalid;
+//    } else if (src_type->id != TypeTableEntryIdInt) {
+//        add_node_error(g, *op2,
+//                buf_sprintf("expected integer type, got '%s'", buf_ptr(&src_type->name)));
+//        return g->builtin_types.entry_invalid;
+//    } else if (src_type->data.integral.is_signed != dest_type->data.integral.is_signed) {
+//        const char *sign_str = dest_type->data.integral.is_signed ? "signed" : "unsigned";
+//        add_node_error(g, *op2,
+//                buf_sprintf("expected %s integer type, got '%s'", sign_str, buf_ptr(&src_type->name)));
+//        return g->builtin_types.entry_invalid;
+//    } else if (src_type->data.integral.bit_count <= dest_type->data.integral.bit_count) {
+//        add_node_error(g, *op2,
+//                buf_sprintf("type '%s' has same or fewer bits than destination type '%s'",
+//                    buf_ptr(&src_type->name), buf_ptr(&dest_type->name)));
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    // TODO const expr eval
+//
+//    return dest_type;
+//}
+//
+//static TypeTableEntry *analyze_compile_err(CodeGen *g, ImportTableEntry *import,
+//        BlockContext *context, AstNode *node)
+//{
+//    AstNode *first_param_node = node->data.fn_call_expr.params.at(0);
+//    Buf *err_msg = resolve_const_expr_str(g, import, context, first_param_node->parent_field);
+//    if (!err_msg) {
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    add_node_error(g, node, err_msg);
+//
+//    return g->builtin_types.entry_invalid;
+//}
+//
+//static TypeTableEntry *analyze_int_type(CodeGen *g, ImportTableEntry *import,
+//        BlockContext *context, AstNode *node)
+//{
+//    AstNode **is_signed_node = &node->data.fn_call_expr.params.at(0);
+//    AstNode **bit_count_node = &node->data.fn_call_expr.params.at(1);
+//
+//    TypeTableEntry *bool_type = g->builtin_types.entry_bool;
+//    TypeTableEntry *usize_type = g->builtin_types.entry_usize;
+//    TypeTableEntry *is_signed_type = analyze_expression(g, import, context, bool_type, *is_signed_node);
+//    TypeTableEntry *bit_count_type = analyze_expression(g, import, context, usize_type, *bit_count_node);
+//
+//    if (is_signed_type->id == TypeTableEntryIdInvalid ||
+//        bit_count_type->id == TypeTableEntryIdInvalid)
+//    {
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    ConstExprValue *is_signed_val = &get_resolved_expr(*is_signed_node)->const_val;
+//    ConstExprValue *bit_count_val = &get_resolved_expr(*bit_count_node)->const_val;
+//
+//    AstNode *bad_node = nullptr;
+//    if (!is_signed_val->ok) {
+//        bad_node = *is_signed_node;
+//    } else if (!bit_count_val->ok) {
+//        bad_node = *bit_count_node;
+//    }
+//    if (bad_node) {
+//        add_node_error(g, bad_node, buf_sprintf("unable to evaluate constant expression"));
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    bool depends_on_compile_var = is_signed_val->depends_on_compile_var || bit_count_val->depends_on_compile_var;
+//
+//    TypeTableEntry *int_type = get_int_type(g, is_signed_val->data.x_bool,
+//            bit_count_val->data.x_bignum.data.x_uint);
+//    return resolve_expr_const_val_as_type(g, node, int_type, depends_on_compile_var);
+//
+//}
+//
+//static TypeTableEntry *analyze_set_fn_test(CodeGen *g, ImportTableEntry *import,
+//        BlockContext *context, AstNode *node)
+//{
+//    AstNode **fn_node = &node->data.fn_call_expr.params.at(0);
+//    AstNode **value_node = &node->data.fn_call_expr.params.at(1);
+//
+//    FnTableEntry *fn_entry = resolve_const_expr_fn(g, import, context, fn_node);
+//    if (!fn_entry) {
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    bool ok = resolve_const_expr_bool(g, import, context, value_node, &fn_entry->is_test);
+//    if (!ok) {
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    if (fn_entry->fn_test_set_node) {
+//        ErrorMsg *msg = add_node_error(g, node, buf_sprintf("function test attribute set twice"));
+//        add_error_note(g, msg, fn_entry->fn_test_set_node, buf_sprintf("first set here"));
+//        return g->builtin_types.entry_invalid;
+//    }
+//    fn_entry->fn_test_set_node = node;
+//
+//    g->test_fn_count += 1;
+//    return g->builtin_types.entry_void;
+//}
+//
+//static TypeTableEntry *analyze_set_fn_no_inline(CodeGen *g, ImportTableEntry *import,
+//        BlockContext *context, AstNode *node)
+//{
+//    AstNode **fn_node = &node->data.fn_call_expr.params.at(0);
+//    AstNode **value_node = &node->data.fn_call_expr.params.at(1);
+//
+//    FnTableEntry *fn_entry = resolve_const_expr_fn(g, import, context, fn_node);
+//    if (!fn_entry) {
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    bool is_noinline;
+//    bool ok = resolve_const_expr_bool(g, import, context, value_node, &is_noinline);
+//    if (!ok) {
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    if (fn_entry->fn_no_inline_set_node) {
+//        ErrorMsg *msg = add_node_error(g, node, buf_sprintf("function no inline attribute set twice"));
+//        add_error_note(g, msg, fn_entry->fn_no_inline_set_node, buf_sprintf("first set here"));
+//        return g->builtin_types.entry_invalid;
+//    }
+//    fn_entry->fn_no_inline_set_node = node;
+//
+//    if (fn_entry->fn_inline == FnInlineAlways) {
+//        add_node_error(g, node, buf_sprintf("function is both inline and noinline"));
+//        fn_entry->proto_node->data.fn_proto.skip = true;
+//        return g->builtin_types.entry_invalid;
+//    } else if (is_noinline) {
+//        fn_entry->fn_inline = FnInlineNever;
+//    }
+//
+//    return g->builtin_types.entry_void;
+//}
+//
+//static TypeTableEntry *analyze_set_fn_static_eval(CodeGen *g, ImportTableEntry *import,
+//        BlockContext *context, AstNode *node)
+//{
+//    AstNode **fn_node = &node->data.fn_call_expr.params.at(0);
+//    AstNode **value_node = &node->data.fn_call_expr.params.at(1);
+//
+//    FnTableEntry *fn_entry = resolve_const_expr_fn(g, import, context, fn_node);
+//    if (!fn_entry) {
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    bool want_static_eval;
+//    bool ok = resolve_const_expr_bool(g, import, context, value_node, &want_static_eval);
+//    if (!ok) {
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    if (fn_entry->fn_static_eval_set_node) {
+//        ErrorMsg *msg = add_node_error(g, node, buf_sprintf("function static eval attribute set twice"));
+//        add_error_note(g, msg, fn_entry->fn_static_eval_set_node, buf_sprintf("first set here"));
+//        return g->builtin_types.entry_invalid;
+//    }
+//    fn_entry->fn_static_eval_set_node = node;
+//
+//    if (want_static_eval && !context->fn_entry->is_pure) {
+//        add_node_error(g, node, buf_sprintf("attribute appears too late within function"));
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    if (want_static_eval) {
+//        fn_entry->want_pure = WantPureTrue;
+//        fn_entry->want_pure_attr_node = node;
+//    } else {
+//        fn_entry->want_pure = WantPureFalse;
+//        fn_entry->is_pure = false;
+//    }
+//
+//    return g->builtin_types.entry_void;
+//}
+//
+//static TypeTableEntry *analyze_set_fn_visible(CodeGen *g, ImportTableEntry *import,
+//        BlockContext *context, AstNode *node)
+//{
+//    AstNode **fn_node = &node->data.fn_call_expr.params.at(0);
+//    AstNode **value_node = &node->data.fn_call_expr.params.at(1);
+//
+//    FnTableEntry *fn_entry = resolve_const_expr_fn(g, import, context, fn_node);
+//    if (!fn_entry) {
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    bool want_export;
+//    bool ok = resolve_const_expr_bool(g, import, context, value_node, &want_export);
+//    if (!ok) {
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    if (fn_entry->fn_export_set_node) {
+//        ErrorMsg *msg = add_node_error(g, node, buf_sprintf("function visibility set twice"));
+//        add_error_note(g, msg, fn_entry->fn_export_set_node, buf_sprintf("first set here"));
+//        return g->builtin_types.entry_invalid;
+//    }
+//    fn_entry->fn_export_set_node = node;
+//
+//    AstNodeFnProto *fn_proto = &fn_entry->proto_node->data.fn_proto;
+//    if (fn_proto->top_level_decl.visib_mod != VisibModExport) {
+//        ErrorMsg *msg = add_node_error(g, node,
+//            buf_sprintf("function must be marked export to set function visibility"));
+//        add_error_note(g, msg, fn_entry->proto_node, buf_sprintf("function declared here"));
+//        return g->builtin_types.entry_void;
+//    }
+//    if (!want_export) {
+//        fn_proto->top_level_decl.visib_mod = VisibModPub;
+//    }
+//
+//    return g->builtin_types.entry_void;
+//}
+//
+//static TypeTableEntry *analyze_set_debug_safety(CodeGen *g, ImportTableEntry *import,
+//        BlockContext *parent_context, AstNode *node)
+//{
+//    AstNode **target_node = &node->data.fn_call_expr.params.at(0);
+//    AstNode **value_node = &node->data.fn_call_expr.params.at(1);
+//
+//    TypeTableEntry *target_type = analyze_expression(g, import, parent_context, nullptr, *target_node);
+//    BlockContext *target_context;
+//    ConstExprValue *const_val = &get_resolved_expr(*target_node)->const_val;
+//    if (target_type->id == TypeTableEntryIdInvalid) {
+//        return g->builtin_types.entry_invalid;
+//    }
+//    if (!const_val->ok) {
+//        add_node_error(g, *target_node, buf_sprintf("unable to evaluate constant expression"));
+//        return g->builtin_types.entry_invalid;
+//    }
+//    if (target_type->id == TypeTableEntryIdBlock) {
+//        target_context = const_val->data.x_block;
+//    } else if (target_type->id == TypeTableEntryIdFn) {
+//        target_context = const_val->data.x_fn->fn_def_node->data.fn_def.block_context;
+//    } else if (target_type->id == TypeTableEntryIdMetaType) {
+//        TypeTableEntry *type_arg = const_val->data.x_type;
+//        if (type_arg->id == TypeTableEntryIdStruct) {
+//            target_context = type_arg->data.structure.block_context;
+//        } else if (type_arg->id == TypeTableEntryIdEnum) {
+//            target_context = type_arg->data.enumeration.block_context;
+//        } else if (type_arg->id == TypeTableEntryIdUnion) {
+//            target_context = type_arg->data.unionation.block_context;
+//        } else {
+//            add_node_error(g, *target_node,
+//                buf_sprintf("expected scope reference, got type '%s'", buf_ptr(&type_arg->name)));
+//            return g->builtin_types.entry_invalid;
+//        }
+//    } else {
+//        add_node_error(g, *target_node,
+//            buf_sprintf("expected scope reference, got type '%s'", buf_ptr(&target_type->name)));
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    bool want_debug_safety;
+//    bool ok = resolve_const_expr_bool(g, import, parent_context, value_node, &want_debug_safety);
+//    if (!ok) {
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    if (target_context->safety_set_node) {
+//        ErrorMsg *msg = add_node_error(g, node, buf_sprintf("debug safety for scope set twice"));
+//        add_error_note(g, msg, target_context->safety_set_node, buf_sprintf("first set here"));
+//        return g->builtin_types.entry_invalid;
+//    }
+//    target_context->safety_set_node = node;
+//
+//    target_context->safety_off = !want_debug_safety;
+//
+//    return g->builtin_types.entry_void;
+//}
+
+
+//static TypeTableEntry *analyze_builtin_fn_call_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
+//        TypeTableEntry *expected_type, AstNode *node)
+//{
+//
+//    switch (builtin_fn->id) {
+//        case BuiltinFnIdInvalid:
+//            zig_unreachable();
+//        case BuiltinFnIdAddWithOverflow:
+//        case BuiltinFnIdSubWithOverflow:
+//        case BuiltinFnIdMulWithOverflow:
+//        case BuiltinFnIdShlWithOverflow:
+//            {
+//                AstNode *type_node = node->data.fn_call_expr.params.at(0);
+//                TypeTableEntry *int_type = analyze_type_expr(g, import, context, type_node);
+//                if (int_type->id == TypeTableEntryIdInvalid) {
+//                    return g->builtin_types.entry_bool;
+//                } else if (int_type->id == TypeTableEntryIdInt) {
+//                    AstNode *op1_node = node->data.fn_call_expr.params.at(1);
+//                    AstNode *op2_node = node->data.fn_call_expr.params.at(2);
+//                    AstNode *result_node = node->data.fn_call_expr.params.at(3);
+//
+//                    analyze_expression(g, import, context, int_type, op1_node);
+//                    analyze_expression(g, import, context, int_type, op2_node);
+//                    analyze_expression(g, import, context, get_pointer_to_type(g, int_type, false),
+//                            result_node);
+//                } else {
+//                    add_node_error(g, type_node,
+//                        buf_sprintf("expected integer type, got '%s'", buf_ptr(&int_type->name)));
+//                }
+//
+//                // TODO constant expression evaluation
+//
+//                return g->builtin_types.entry_bool;
+//            }
+//        case BuiltinFnIdMemcpy:
+//            {
+//                AstNode *dest_node = node->data.fn_call_expr.params.at(0);
+//                AstNode *src_node = node->data.fn_call_expr.params.at(1);
+//                AstNode *len_node = node->data.fn_call_expr.params.at(2);
+//                TypeTableEntry *dest_type = analyze_expression(g, import, context, nullptr, dest_node);
+//                TypeTableEntry *src_type = analyze_expression(g, import, context, nullptr, src_node);
+//                analyze_expression(g, import, context, builtin_fn->param_types[2], len_node);
+//
+//                if (dest_type->id != TypeTableEntryIdInvalid &&
+//                    dest_type->id != TypeTableEntryIdPointer)
+//                {
+//                    add_node_error(g, dest_node,
+//                            buf_sprintf("expected pointer argument, got '%s'", buf_ptr(&dest_type->name)));
+//                }
+//
+//                if (src_type->id != TypeTableEntryIdInvalid &&
+//                    src_type->id != TypeTableEntryIdPointer)
+//                {
+//                    add_node_error(g, src_node,
+//                            buf_sprintf("expected pointer argument, got '%s'", buf_ptr(&src_type->name)));
+//                }
+//
+//                if (dest_type->id == TypeTableEntryIdPointer &&
+//                    src_type->id == TypeTableEntryIdPointer)
+//                {
+//                    uint64_t dest_align = get_memcpy_align(g, dest_type->data.pointer.child_type);
+//                    uint64_t src_align = get_memcpy_align(g, src_type->data.pointer.child_type);
+//                    if (dest_align != src_align) {
+//                        add_node_error(g, dest_node, buf_sprintf(
+//                            "misaligned memcpy, '%s' has alignment '%" PRIu64 ", '%s' has alignment %" PRIu64,
+//                                    buf_ptr(&dest_type->name), dest_align,
+//                                    buf_ptr(&src_type->name), src_align));
+//                    }
+//                }
+//
+//                return builtin_fn->return_type;
+//            }
+//        case BuiltinFnIdMemset:
+//            {
+//                AstNode *dest_node = node->data.fn_call_expr.params.at(0);
+//                AstNode *char_node = node->data.fn_call_expr.params.at(1);
+//                AstNode *len_node = node->data.fn_call_expr.params.at(2);
+//                TypeTableEntry *dest_type = analyze_expression(g, import, context, nullptr, dest_node);
+//                analyze_expression(g, import, context, builtin_fn->param_types[1], char_node);
+//                analyze_expression(g, import, context, builtin_fn->param_types[2], len_node);
+//
+//                if (dest_type->id != TypeTableEntryIdInvalid &&
+//                    dest_type->id != TypeTableEntryIdPointer)
+//                {
+//                    add_node_error(g, dest_node,
+//                            buf_sprintf("expected pointer argument, got '%s'", buf_ptr(&dest_type->name)));
+//                }
+//
+//                return builtin_fn->return_type;
+//            }
+//        case BuiltinFnIdSizeof:
+//            {
+//                AstNode *type_node = node->data.fn_call_expr.params.at(0);
+//                TypeTableEntry *type_entry = analyze_type_expr(g, import, context, type_node);
+//                if (type_entry->id == TypeTableEntryIdInvalid) {
+//                    return g->builtin_types.entry_invalid;
+//                } else if (type_entry->id == TypeTableEntryIdUnreachable) {
+//                    add_node_error(g, first_executing_node(type_node),
+//                            buf_sprintf("no size available for type '%s'", buf_ptr(&type_entry->name)));
+//                    return g->builtin_types.entry_invalid;
+//                } else {
+//                    uint64_t size_in_bytes = type_size(g, type_entry);
+//                    bool depends_on_compile_var = (type_entry == g->builtin_types.entry_usize ||
+//                            type_entry == g->builtin_types.entry_isize);
+//                    return resolve_expr_const_val_as_unsigned_num_lit(g, node, expected_type,
+//                            size_in_bytes, depends_on_compile_var);
+//                }
+//            }
+//        case BuiltinFnIdAlignof:
+//            {
+//                AstNode *type_node = node->data.fn_call_expr.params.at(0);
+//                TypeTableEntry *type_entry = analyze_type_expr(g, import, context, type_node);
+//                if (type_entry->id == TypeTableEntryIdInvalid) {
+//                    return g->builtin_types.entry_invalid;
+//                } else if (type_entry->id == TypeTableEntryIdUnreachable) {
+//                    add_node_error(g, first_executing_node(type_node),
+//                            buf_sprintf("no align available for type '%s'", buf_ptr(&type_entry->name)));
+//                    return g->builtin_types.entry_invalid;
+//                } else {
+//                    uint64_t align_in_bytes = LLVMABISizeOfType(g->target_data_ref, type_entry->type_ref);
+//                    return resolve_expr_const_val_as_unsigned_num_lit(g, node, expected_type,
+//                            align_in_bytes, false);
+//                }
+//            }
+//        case BuiltinFnIdMaxValue:
+//            return analyze_min_max_value(g, import, context, node,
+//                    "no max value available for type '%s'", true);
+//        case BuiltinFnIdMinValue:
+//            return analyze_min_max_value(g, import, context, node,
+//                    "no min value available for type '%s'", false);
+//        case BuiltinFnIdMemberCount:
+//            {
+//                AstNode *type_node = node->data.fn_call_expr.params.at(0);
+//                TypeTableEntry *type_entry = analyze_type_expr(g, import, context, type_node);
+//
+//                if (type_entry->id == TypeTableEntryIdInvalid) {
+//                    return type_entry;
+//                } else if (type_entry->id == TypeTableEntryIdEnum) {
+//                    uint64_t value_count = type_entry->data.enumeration.src_field_count;
+//                    return resolve_expr_const_val_as_unsigned_num_lit(g, node, expected_type,
+//                            value_count, false);
+//                } else {
+//                    add_node_error(g, node,
+//                            buf_sprintf("no value count available for type '%s'", buf_ptr(&type_entry->name)));
+//                    return g->builtin_types.entry_invalid;
+//                }
+//            }
+//        case BuiltinFnIdTypeof:
+//            {
+//                AstNode *expr_node = node->data.fn_call_expr.params.at(0);
+//                TypeTableEntry *type_entry = analyze_expression(g, import, context, nullptr, expr_node);
+//
+//                switch (type_entry->id) {
+//                    case TypeTableEntryIdInvalid:
+//                        return type_entry;
+//                    case TypeTableEntryIdNumLitFloat:
+//                    case TypeTableEntryIdNumLitInt:
+//                    case TypeTableEntryIdUndefLit:
+//                    case TypeTableEntryIdNullLit:
+//                    case TypeTableEntryIdNamespace:
+//                    case TypeTableEntryIdBlock:
+//                    case TypeTableEntryIdGenericFn:
+//                    case TypeTableEntryIdVar:
+//                        add_node_error(g, expr_node,
+//                                buf_sprintf("type '%s' not eligible for @typeOf", buf_ptr(&type_entry->name)));
+//                        return g->builtin_types.entry_invalid;
+//                    case TypeTableEntryIdMetaType:
+//                    case TypeTableEntryIdVoid:
+//                    case TypeTableEntryIdBool:
+//                    case TypeTableEntryIdUnreachable:
+//                    case TypeTableEntryIdInt:
+//                    case TypeTableEntryIdFloat:
+//                    case TypeTableEntryIdPointer:
+//                    case TypeTableEntryIdArray:
+//                    case TypeTableEntryIdStruct:
+//                    case TypeTableEntryIdMaybe:
+//                    case TypeTableEntryIdErrorUnion:
+//                    case TypeTableEntryIdPureError:
+//                    case TypeTableEntryIdEnum:
+//                    case TypeTableEntryIdUnion:
+//                    case TypeTableEntryIdFn:
+//                    case TypeTableEntryIdTypeDecl:
+//                        return resolve_expr_const_val_as_type(g, node, type_entry, false);
+//                }
+//            }
+//        case BuiltinFnIdCInclude:
+//            {
+//                if (!context->c_import_buf) {
+//                    add_node_error(g, node, buf_sprintf("@c_include valid only in c_import blocks"));
+//                    return g->builtin_types.entry_invalid;
+//                }
+//
+//                AstNode **str_node = node->data.fn_call_expr.params.at(0)->parent_field;
+//                TypeTableEntry *str_type = get_slice_type(g, g->builtin_types.entry_u8, true);
+//                TypeTableEntry *resolved_type = analyze_expression(g, import, context, str_type, *str_node);
+//
+//                if (resolved_type->id == TypeTableEntryIdInvalid) {
+//                    return resolved_type;
+//                }
+//
+//                ConstExprValue *const_str_val = &get_resolved_expr(*str_node)->const_val;
+//
+//                if (!const_str_val->ok) {
+//                    add_node_error(g, *str_node, buf_sprintf("@c_include requires constant expression"));
+//                    return g->builtin_types.entry_void;
+//                }
+//
+//                buf_appendf(context->c_import_buf, "#include <");
+//                ConstExprValue *ptr_field = const_str_val->data.x_struct.fields[0];
+//                uint64_t len = ptr_field->data.x_ptr.len;
+//                for (uint64_t i = 0; i < len; i += 1) {
+//                    ConstExprValue *char_val = ptr_field->data.x_ptr.ptr[i];
+//                    uint64_t big_c = char_val->data.x_bignum.data.x_uint;
+//                    assert(big_c <= UINT8_MAX);
+//                    uint8_t c = big_c;
+//                    buf_append_char(context->c_import_buf, c);
+//                }
+//                buf_appendf(context->c_import_buf, ">\n");
+//
+//                return g->builtin_types.entry_void;
+//            }
+//        case BuiltinFnIdCDefine:
+//            zig_panic("TODO");
+//        case BuiltinFnIdCUndef:
+//            zig_panic("TODO");
+//
+//        case BuiltinFnIdCompileVar:
+//            {
+//                AstNode **str_node = node->data.fn_call_expr.params.at(0)->parent_field;
+//
+//                Buf *var_name = resolve_const_expr_str(g, import, context, str_node);
+//                if (!var_name) {
+//                    return g->builtin_types.entry_invalid;
+//                }
+//
+//                ConstExprValue *const_val = &get_resolved_expr(node)->const_val;
+//                const_val->ok = true;
+//                const_val->depends_on_compile_var = true;
+//
+//                if (buf_eql_str(var_name, "is_big_endian")) {
+//                    return resolve_expr_const_val_as_bool(g, node, g->is_big_endian, true);
+//                } else if (buf_eql_str(var_name, "is_release")) {
+//                    return resolve_expr_const_val_as_bool(g, node, g->is_release_build, true);
+//                } else if (buf_eql_str(var_name, "is_test")) {
+//                    return resolve_expr_const_val_as_bool(g, node, g->is_test_build, true);
+//                } else if (buf_eql_str(var_name, "os")) {
+//                    const_val->data.x_enum.tag = g->target_os_index;
+//                    return g->builtin_types.entry_os_enum;
+//                } else if (buf_eql_str(var_name, "arch")) {
+//                    const_val->data.x_enum.tag = g->target_arch_index;
+//                    return g->builtin_types.entry_arch_enum;
+//                } else if (buf_eql_str(var_name, "environ")) {
+//                    const_val->data.x_enum.tag = g->target_environ_index;
+//                    return g->builtin_types.entry_environ_enum;
+//                } else if (buf_eql_str(var_name, "object_format")) {
+//                    const_val->data.x_enum.tag = g->target_oformat_index;
+//                    return g->builtin_types.entry_oformat_enum;
+//                } else {
+//                    add_node_error(g, *str_node,
+//                        buf_sprintf("unrecognized compile variable: '%s'", buf_ptr(var_name)));
+//                    return g->builtin_types.entry_invalid;
+//                }
+//            }
+//        case BuiltinFnIdConstEval:
+//            {
+//                AstNode **expr_node = node->data.fn_call_expr.params.at(0)->parent_field;
+//                TypeTableEntry *resolved_type = analyze_expression(g, import, context, expected_type, *expr_node);
+//                if (resolved_type->id == TypeTableEntryIdInvalid) {
+//                    return resolved_type;
+//                }
+//
+//                ConstExprValue *const_expr_val = &get_resolved_expr(*expr_node)->const_val;
+//
+//                if (!const_expr_val->ok) {
+//                    add_node_error(g, *expr_node, buf_sprintf("unable to evaluate constant expression"));
+//                    return g->builtin_types.entry_invalid;
+//                }
+//
+//                ConstExprValue *const_val = &get_resolved_expr(node)->const_val;
+//                *const_val = *const_expr_val;
+//
+//                return resolved_type;
+//            }
+//        case BuiltinFnIdCtz:
+//        case BuiltinFnIdClz:
+//            {
+//                AstNode *type_node = node->data.fn_call_expr.params.at(0);
+//                TypeTableEntry *int_type = analyze_type_expr(g, import, context, type_node);
+//                if (int_type->id == TypeTableEntryIdInvalid) {
+//                    return int_type;
+//                } else if (int_type->id == TypeTableEntryIdInt) {
+//                    AstNode **expr_node = node->data.fn_call_expr.params.at(1)->parent_field;
+//                    TypeTableEntry *resolved_type = analyze_expression(g, import, context, int_type, *expr_node);
+//                    if (resolved_type->id == TypeTableEntryIdInvalid) {
+//                        return resolved_type;
+//                    }
+//
+//                    // TODO const expr eval
+//
+//                    return resolved_type;
+//                } else {
+//                    add_node_error(g, type_node,
+//                        buf_sprintf("expected integer type, got '%s'", buf_ptr(&int_type->name)));
+//                    return g->builtin_types.entry_invalid;
+//                }
+//            }
+//        case BuiltinFnIdImport:
+//            return analyze_import(g, import, context, node);
+//        case BuiltinFnIdCImport:
+//            return analyze_c_import(g, import, context, node);
+//        case BuiltinFnIdErrName:
+//            return analyze_err_name(g, import, context, node);
+//        case BuiltinFnIdBreakpoint:
+//            mark_impure_fn(g, context, node);
+//            return g->builtin_types.entry_void;
+//        case BuiltinFnIdReturnAddress:
+//        case BuiltinFnIdFrameAddress:
+//            mark_impure_fn(g, context, node);
+//            return builtin_fn->return_type;
+//        case BuiltinFnIdEmbedFile:
+//            return analyze_embed_file(g, import, context, node);
+//        case BuiltinFnIdCmpExchange:
+//            return analyze_cmpxchg(g, import, context, node);
+//        case BuiltinFnIdFence:
+//            return analyze_fence(g, import, context, node);
+//        case BuiltinFnIdDivExact:
+//            return analyze_div_exact(g, import, context, node);
+//        case BuiltinFnIdTruncate:
+//            return analyze_truncate(g, import, context, node);
+//        case BuiltinFnIdCompileErr:
+//            return analyze_compile_err(g, import, context, node);
+//        case BuiltinFnIdIntType:
+//            return analyze_int_type(g, import, context, node);
+//        case BuiltinFnIdUnreachable:
+//            return g->builtin_types.entry_unreachable;
+//        case BuiltinFnIdSetFnTest:
+//            return analyze_set_fn_test(g, import, context, node);
+//        case BuiltinFnIdSetFnNoInline:
+//            return analyze_set_fn_no_inline(g, import, context, node);
+//        case BuiltinFnIdSetFnStaticEval:
+//            return analyze_set_fn_static_eval(g, import, context, node);
+//        case BuiltinFnIdSetFnVisible:
+//            return analyze_set_fn_visible(g, import, context, node);
+//        case BuiltinFnIdSetDebugSafety:
+//            return analyze_set_debug_safety(g, import, context, node);
+//    }
+//    zig_unreachable();
+//}
+
+
 static TypeTableEntry *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstruction *instruction) {
     switch (instruction->id) {
         case IrInstructionIdInvalid:
@@ -1533,6 +2949,8 @@ static TypeTableEntry *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstructi
             return ir_analyze_instruction_return(ira, (IrInstructionReturn *)instruction);
         case IrInstructionIdConst:
             return ir_analyze_instruction_const(ira, (IrInstructionConst *)instruction);
+        case IrInstructionIdUnOp:
+            return ir_analyze_instruction_un_op(ira, (IrInstructionUnOp *)instruction);
         case IrInstructionIdBinOp:
             return ir_analyze_instruction_bin_op(ira, (IrInstructionBinOp *)instruction);
         case IrInstructionIdLoadVar:
@@ -1540,6 +2958,7 @@ static TypeTableEntry *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstructi
         case IrInstructionIdCall:
             return ir_analyze_instruction_call(ira, (IrInstructionCall *)instruction);
         case IrInstructionIdCondBr:
+        case IrInstructionIdBr:
         case IrInstructionIdSwitchBr:
         case IrInstructionIdPhi:
         case IrInstructionIdStoreVar:
@@ -1582,9 +3001,7 @@ TypeTableEntry *ir_analyze(CodeGen *codegen, IrExecutable *old_exec, IrExecutabl
 
     TypeTableEntry *return_type = ira->codegen->builtin_types.entry_void;
 
-    ira->new_irb.current_basic_block = allocate<IrBasicBlock>(1);
-    ira->new_irb.exec->basic_block_list.append(ira->new_irb.current_basic_block);
-
+    ira->new_irb.current_basic_block = ir_build_basic_block(&ira->new_irb, "Entry");
     ira->old_irb.current_basic_block = ira->old_irb.exec->basic_block_list.at(0);
 
     ira->new_irb.current_basic_block->other = ira->old_irb.current_basic_block;
src/ir_print.cpp
@@ -46,10 +46,17 @@ static void ir_print_const(IrPrint *irp, IrInstructionConst *const_instruction)
         case TypeTableEntryIdMetaType:
             fprintf(irp->f, "%s\n", buf_ptr(&const_instruction->base.static_value.data.x_type->name));
             break;
+        case TypeTableEntryIdInt:
+            {
+                BigNum *bignum = &const_instruction->base.static_value.data.x_bignum;
+                assert(bignum->kind == BigNumKindInt);
+                const char *negative_str = bignum->is_negative ? "-" : "";
+                fprintf(irp->f, "%s%llu\n", negative_str, bignum->data.x_uint);
+            }
+            break;
         case TypeTableEntryIdVar:
         case TypeTableEntryIdBool:
         case TypeTableEntryIdUnreachable:
-        case TypeTableEntryIdInt:
         case TypeTableEntryIdFloat:
         case TypeTableEntryIdPointer:
         case TypeTableEntryIdArray:
@@ -127,6 +134,47 @@ static const char *ir_bin_op_id_str(IrBinOp op_id) {
     zig_unreachable();
 }
 
+static const char *ir_un_op_id_str(IrUnOp op_id) {
+    switch (op_id) {
+        case IrUnOpInvalid:
+            zig_unreachable();
+        case IrUnOpBoolNot:
+            return "!";
+        case IrUnOpBinNot:
+            return "~";
+        case IrUnOpNegation:
+            return "-";
+        case IrUnOpNegationWrap:
+            return "-%";
+        case IrUnOpAddressOf:
+            return "&";
+        case IrUnOpConstAddressOf:
+            return "&const";
+        case IrUnOpDereference:
+            return "*";
+        case IrUnOpMaybe:
+            return "?";
+        case IrUnOpError:
+            return "%";
+        case IrUnOpUnwrapError:
+            return "%%";
+        case IrUnOpUnwrapMaybe:
+            return "??";
+        case IrUnOpMaybeReturn:
+            return "?return";
+        case IrUnOpErrorReturn:
+            return "%return";
+    }
+    zig_unreachable();
+}
+
+static void ir_print_un_op(IrPrint *irp, IrInstructionUnOp *un_op_instruction) {
+    ir_print_prefix(irp, &un_op_instruction->base);
+    fprintf(irp->f, "%s #%zu\n",
+            ir_un_op_id_str(un_op_instruction->op_id),
+            un_op_instruction->value->debug_id);
+}
+
 static void ir_print_bin_op(IrPrint *irp, IrInstructionBinOp *bin_op_instruction) {
     ir_print_prefix(irp, &bin_op_instruction->base);
     fprintf(irp->f, "#%zu %s #%zu\n",
@@ -160,6 +208,47 @@ static void ir_print_call(IrPrint *irp, IrInstructionCall *call_instruction) {
     fprintf(irp->f, ")\n");
 }
 
+static void ir_print_builtin_call(IrPrint *irp, IrInstructionBuiltinCall *call_instruction) {
+    ir_print_prefix(irp, &call_instruction->base);
+    fprintf(irp->f, "@%s(", buf_ptr(&call_instruction->fn->name));
+    for (size_t i = 0; i < call_instruction->fn->param_count; i += 1) {
+        IrInstruction *arg = call_instruction->args[i];
+        if (i != 0)
+            fprintf(irp->f, ", ");
+        fprintf(irp->f, "#%zu", arg->debug_id);
+    }
+    fprintf(irp->f, ")\n");
+}
+
+
+static void ir_print_cond_br(IrPrint *irp, IrInstructionCondBr *cond_br_instruction) {
+    ir_print_prefix(irp, &cond_br_instruction->base);
+    fprintf(irp->f, "if #%zu then $%s_%zu else $%s_%zu\n",
+            cond_br_instruction->condition->debug_id,
+            cond_br_instruction->then_block->name_hint, cond_br_instruction->then_block->debug_id,
+            cond_br_instruction->else_block->name_hint, cond_br_instruction->else_block->debug_id);
+}
+
+static void ir_print_br(IrPrint *irp, IrInstructionBr *br_instruction) {
+    ir_print_prefix(irp, &br_instruction->base);
+    fprintf(irp->f, "goto $%s_%zu\n",
+            br_instruction->dest_block->name_hint, br_instruction->dest_block->debug_id);
+}
+
+static void ir_print_phi(IrPrint *irp, IrInstructionPhi *phi_instruction) {
+    ir_print_prefix(irp, &phi_instruction->base);
+    for (size_t i = 0; i < phi_instruction->incoming_count; i += 1) {
+        IrBasicBlock *incoming_block = phi_instruction->incoming_blocks[i];
+        IrInstruction *incoming_value = phi_instruction->incoming_values[i];
+        if (i != 0)
+            fprintf(irp->f, " ");
+        fprintf(irp->f, "$%s_%zu:#%zu",
+                incoming_block->name_hint, incoming_block->debug_id,
+                incoming_value->debug_id);
+    }
+    fprintf(irp->f, "\n");
+}
+
 static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) {
     switch (instruction->id) {
         case IrInstructionIdInvalid:
@@ -182,11 +271,23 @@ static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) {
         case IrInstructionIdCall:
             ir_print_call(irp, (IrInstructionCall *)instruction);
             break;
+        case IrInstructionIdUnOp:
+            ir_print_un_op(irp, (IrInstructionUnOp *)instruction);
+            break;
         case IrInstructionIdCondBr:
-        case IrInstructionIdSwitchBr:
+            ir_print_cond_br(irp, (IrInstructionCondBr *)instruction);
+            break;
+        case IrInstructionIdBr:
+            ir_print_br(irp, (IrInstructionBr *)instruction);
+            break;
+        case IrInstructionIdBuiltinCall:
+            ir_print_builtin_call(irp, (IrInstructionBuiltinCall *)instruction);
+            break;
         case IrInstructionIdPhi:
+            ir_print_phi(irp, (IrInstructionPhi *)instruction);
+            break;
+        case IrInstructionIdSwitchBr:
         case IrInstructionIdStoreVar:
-        case IrInstructionIdBuiltinCall:
             zig_panic("TODO print more IR instructions");
     }
 }
@@ -200,6 +301,7 @@ void ir_print(FILE *f, IrExecutable *executable, int indent_size) {
 
     for (size_t bb_i = 0; bb_i < executable->basic_block_list.length; bb_i += 1) {
         IrBasicBlock *current_block = executable->basic_block_list.at(bb_i);
+        fprintf(irp->f, "%s_%zu:\n", current_block->name_hint, current_block->debug_id);
         for (size_t instr_i = 0; instr_i < current_block->instruction_list.length; instr_i += 1) {
             IrInstruction *instruction = current_block->instruction_list.at(instr_i);
             ir_print_instruction(irp, instruction);