Commit cd1bd78aa9

Andrew Kelley <superjoe30@gmail.com>
2016-10-03 05:48:48
simple add function works with IR
1 parent 633781e
src/all_types.hpp
@@ -1420,6 +1420,7 @@ enum IrInstructionId {
     IrInstructionIdBuiltinCall,
     IrInstructionIdConst,
     IrInstructionIdReturn,
+    IrInstructionIdCast,
 };
 
 struct IrInstruction {
@@ -1539,5 +1540,12 @@ struct IrInstructionReturn {
     IrInstruction *value;
 };
 
+struct IrInstructionCast {
+    IrInstruction base;
+
+    IrInstruction *value;
+    IrInstruction *dest_type;
+    bool is_implicit;
+};
 
 #endif
src/analyze.cpp
@@ -42,10 +42,8 @@ static TypeTableEntry *resolve_expr_const_val_as_unsigned_num_lit(CodeGen *g, As
         TypeTableEntry *expected_type, uint64_t x, bool depends_on_compile_var);
 static TypeTableEntry *resolve_expr_const_val_as_bool(CodeGen *g, AstNode *node, bool value,
         bool depends_on_compile_var);
-static AstNode *find_decl(BlockContext *context, Buf *name);
 static TypeTableEntry *analyze_decl_ref(CodeGen *g, AstNode *source_node, AstNode *decl_node,
         bool pointer_only, BlockContext *block_context, bool depends_on_compile_var);
-static TopLevelDecl *get_as_top_level_decl(AstNode *node);
 static VariableTableEntry *analyze_variable_declaration_raw(CodeGen *g, ImportTableEntry *import,
         BlockContext *context, AstNode *source_node,
         AstNodeVariableDeclaration *variable_declaration,
@@ -1757,7 +1755,7 @@ static void preview_error_value_decl(CodeGen *g, AstNode *node) {
     node->data.error_value_decl.top_level_decl.resolution = TldResolutionOk;
 }
 
-static void resolve_top_level_decl(CodeGen *g, AstNode *node, bool pointer_only) {
+void resolve_top_level_decl(CodeGen *g, AstNode *node, bool pointer_only) {
     TopLevelDecl *tld = get_as_top_level_decl(node);
     if (tld->resolution != TldResolutionUnresolved) {
         return;
@@ -1978,7 +1976,7 @@ static bool num_lit_fits_in_other_type(CodeGen *g, AstNode *literal_node, TypeTa
     return false;
 }
 
-static bool types_match_const_cast_only(TypeTableEntry *expected_type, TypeTableEntry *actual_type) {
+bool types_match_const_cast_only(TypeTableEntry *expected_type, TypeTableEntry *actual_type) {
     if (expected_type == actual_type)
         return true;
 
@@ -2302,7 +2300,7 @@ static TypeTableEntry *resolve_type_compatibility(CodeGen *g, ImportTableEntry *
     return g->builtin_types.entry_invalid;
 }
 
-static TypeTableEntry *resolve_peer_type_compatibility(CodeGen *g, ImportTableEntry *import,
+TypeTableEntry *resolve_peer_type_compatibility(CodeGen *g, ImportTableEntry *import,
         BlockContext *block_context, AstNode *parent_source_node,
         AstNode **child_nodes, TypeTableEntry **child_types, size_t child_count)
 {
@@ -2358,7 +2356,7 @@ BlockContext *new_block_context(AstNode *node, BlockContext *parent) {
     return context;
 }
 
-static AstNode *find_decl(BlockContext *context, Buf *name) {
+AstNode *find_decl(BlockContext *context, Buf *name) {
     while (context) {
         auto entry = context->decl_table.maybe_get(name);
         if (entry) {
@@ -2369,7 +2367,7 @@ static AstNode *find_decl(BlockContext *context, Buf *name) {
     return nullptr;
 }
 
-static VariableTableEntry *find_variable(CodeGen *g, BlockContext *orig_context, Buf *name) {
+VariableTableEntry *find_variable(CodeGen *g, BlockContext *orig_context, Buf *name) {
     BlockContext *context = orig_context;
     while (context) {
         auto entry = context->var_table.maybe_get(name);
@@ -3248,10 +3246,6 @@ static TypeTableEntry *analyze_decl_ref(CodeGen *g, AstNode *source_node, AstNod
 static TypeTableEntry *analyze_symbol_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
         TypeTableEntry *expected_type, AstNode *node, bool pointer_only)
 {
-    if (node->data.symbol_expr.override_type_entry) {
-        return resolve_expr_const_val_as_type(g, node, node->data.symbol_expr.override_type_entry, false);
-    }
-
     Buf *variable_name = node->data.symbol_expr.symbol;
 
     auto primitive_table_entry = g->primitive_type_table.maybe_get(variable_name);
@@ -4091,11 +4085,6 @@ static TypeTableEntry *analyze_this_literal_expr(CodeGen *g, ImportTableEntry *i
 static TypeTableEntry *analyze_number_literal_expr(CodeGen *g, ImportTableEntry *import,
         BlockContext *block_context, TypeTableEntry *expected_type, AstNode *node)
 {
-    if (node->data.number_literal.overflow) {
-        add_node_error(g, node, buf_sprintf("number literal too large to be represented in any type"));
-        return g->builtin_types.entry_invalid;
-    }
-
     return resolve_expr_const_val_as_bignum(g, node, expected_type, node->data.number_literal.bignum, false);
 }
 
@@ -7536,7 +7525,7 @@ Expr *get_resolved_expr(AstNode *node) {
     zig_unreachable();
 }
 
-static TopLevelDecl *get_as_top_level_decl(AstNode *node) {
+TopLevelDecl *get_as_top_level_decl(AstNode *node) {
     switch (node->type) {
         case NodeTypeVariableDeclaration:
             return &node->data.variable_declaration.top_level_decl;
src/analyze.hpp
@@ -45,4 +45,14 @@ ImportTableEntry *add_source_file(CodeGen *g, PackageTableEntry *package,
 
 AstNode *first_executing_node(AstNode *node);
 
+TypeTableEntry *resolve_peer_type_compatibility(CodeGen *g, ImportTableEntry *import,
+        BlockContext *block_context, AstNode *parent_source_node,
+        AstNode **child_nodes, TypeTableEntry **child_types, size_t child_count);
+
+bool types_match_const_cast_only(TypeTableEntry *expected_type, TypeTableEntry *actual_type);
+VariableTableEntry *find_variable(CodeGen *g, BlockContext *orig_context, Buf *name);
+AstNode *find_decl(BlockContext *context, Buf *name);
+void resolve_top_level_decl(CodeGen *g, AstNode *node, bool pointer_only);
+TopLevelDecl *get_as_top_level_decl(AstNode *node);
+
 #endif
src/codegen.cpp
@@ -65,8 +65,6 @@ CodeGen *codegen_create(Buf *root_source_dir, const ZigTarget *target) {
     g->is_test_build = false;
     g->want_h_file = true;
 
-    g->invalid_instruction = allocate<IrInstruction>(1);
-
     // the error.Ok value
     g->error_decls.append(nullptr);
 
@@ -252,10 +250,6 @@ static void set_debug_source_node(CodeGen *g, AstNode *node) {
     ZigLLVMSetCurrentDebugLocation(g->builder, node->line + 1, node->column + 1, node->block_context->di_scope);
 }
 
-static void ir_set_debug(CodeGen *g, IrInstruction *instruction) {
-    set_debug_source_node(g, instruction->source_node);
-}
-
 static void clear_debug_source_node(CodeGen *g) {
     ZigLLVMClearCurrentDebugLocation(g->builder);
 }
@@ -375,6 +369,10 @@ static bool want_debug_safety(CodeGen *g, AstNode *node) {
     return want_debug_safety_recursive(g, node->block_context);
 }
 
+static bool ir_want_debug_safety(CodeGen *g, IrInstruction *instruction) {
+    return want_debug_safety(g, instruction->source_node);
+}
+
 static void gen_debug_safety_crash(CodeGen *g) {
     LLVMBuildCall(g->builder, g->trap_fn_val, nullptr, 0, "");
     LLVMBuildUnreachable(g->builder);
@@ -2800,12 +2798,104 @@ static LLVMValueRef gen_if_var_expr(CodeGen *g, AstNode *node) {
 }
 
 static LLVMValueRef ir_render_return(CodeGen *g, IrExecutable *executable, IrInstructionReturn *return_instruction) {
-    ir_set_debug(g, &return_instruction->base);
     LLVMBuildRet(g->builder, return_instruction->value->llvm_value);
     return nullptr;
 }
 
+static LLVMValueRef ir_render_load_var(CodeGen *g, IrExecutable *executable,
+        IrInstructionLoadVar *load_var_instruction)
+{
+    VariableTableEntry *var = load_var_instruction->var;
+    if (!type_has_bits(var->type))
+        return nullptr;
+
+    assert(var->value_ref);
+    return get_handle_value(g, load_var_instruction->base.source_node, var->value_ref, var->type);
+}
+
+static LLVMValueRef ir_render_bin_op_bool(CodeGen *g, IrExecutable *executable,
+        IrInstructionBinOp *bin_op_instruction)
+{
+    IrBinOp op_id = bin_op_instruction->op_id;
+    LLVMValueRef op1 = bin_op_instruction->op1->llvm_value;
+    LLVMValueRef op2 = bin_op_instruction->op2->llvm_value;
+    if (op_id == IrBinOpBoolOr) {
+        return LLVMBuildOr(g->builder, op1, op2, "");
+    } else if (op_id == IrBinOpBoolAnd) {
+        return LLVMBuildAnd(g->builder, op1, op2, "");
+    } else {
+        zig_unreachable();
+    }
+}
+
+static LLVMValueRef ir_render_bin_op_add(CodeGen *g, IrExecutable *executable,
+        IrInstructionBinOp *bin_op_instruction)
+{
+    IrBinOp op_id = bin_op_instruction->op_id;
+    IrInstruction *op1 = bin_op_instruction->op1;
+    IrInstruction *op2 = bin_op_instruction->op2;
+
+    assert(op1->type_entry == op2->type_entry);
+
+    if (op1->type_entry->id == TypeTableEntryIdFloat) {
+        return LLVMBuildFAdd(g->builder, op1->llvm_value, op2->llvm_value, "");
+    } else if (op1->type_entry->id == TypeTableEntryIdInt) {
+        bool is_wrapping = (op_id == IrBinOpAddWrap);
+        if (is_wrapping) {
+            return LLVMBuildAdd(g->builder, op1->llvm_value, op2->llvm_value, "");
+        } else if (ir_want_debug_safety(g, &bin_op_instruction->base)) {
+            return gen_overflow_op(g, op1->type_entry, AddSubMulAdd, op1->llvm_value, op2->llvm_value);
+        } else if (op1->type_entry->data.integral.is_signed) {
+            return LLVMBuildNSWAdd(g->builder, op1->llvm_value, op2->llvm_value, "");
+        } else {
+            return LLVMBuildNUWAdd(g->builder, op1->llvm_value, op2->llvm_value, "");
+        }
+    } else {
+        zig_unreachable();
+    }
+}
+
+static LLVMValueRef ir_render_bin_op(CodeGen *g, IrExecutable *executable,
+        IrInstructionBinOp *bin_op_instruction)
+{
+    IrBinOp op_id = bin_op_instruction->op_id;
+    switch (op_id) {
+        case IrBinOpInvalid:
+        case IrBinOpArrayCat:
+        case IrBinOpArrayMult:
+            zig_unreachable();
+        case IrBinOpBoolOr:
+        case IrBinOpBoolAnd:
+            return ir_render_bin_op_bool(g, executable, bin_op_instruction);
+        case IrBinOpCmpEq:
+        case IrBinOpCmpNotEq:
+        case IrBinOpCmpLessThan:
+        case IrBinOpCmpGreaterThan:
+        case IrBinOpCmpLessOrEq:
+        case IrBinOpCmpGreaterOrEq:
+            zig_panic("TODO bin op cmp");
+        case IrBinOpAdd:
+        case IrBinOpAddWrap:
+            return ir_render_bin_op_add(g, executable, bin_op_instruction);
+        case IrBinOpBinOr:
+        case IrBinOpBinXor:
+        case IrBinOpBinAnd:
+        case IrBinOpBitShiftLeft:
+        case IrBinOpBitShiftLeftWrap:
+        case IrBinOpBitShiftRight:
+        case IrBinOpSub:
+        case IrBinOpSubWrap:
+        case IrBinOpMult:
+        case IrBinOpMultWrap:
+        case IrBinOpDiv:
+        case IrBinOpMod:
+            zig_panic("TODO render more bin ops to LLVM");
+    }
+    zig_unreachable();
+}
+
 static LLVMValueRef ir_render_instruction(CodeGen *g, IrExecutable *executable, IrInstruction *instruction) {
+    set_debug_source_node(g, instruction->source_node);
     switch (instruction->id) {
         case IrInstructionIdInvalid:
             zig_unreachable();
@@ -2813,14 +2903,17 @@ static LLVMValueRef ir_render_instruction(CodeGen *g, IrExecutable *executable,
             return gen_const_val(g, instruction->type_entry, &instruction->static_value);
         case IrInstructionIdReturn:
             return ir_render_return(g, executable, (IrInstructionReturn *)instruction);
+        case IrInstructionIdLoadVar:
+            return ir_render_load_var(g, executable, (IrInstructionLoadVar *)instruction);
+        case IrInstructionIdBinOp:
+            return ir_render_bin_op(g, executable, (IrInstructionBinOp *)instruction);
         case IrInstructionIdCondBr:
         case IrInstructionIdSwitchBr:
         case IrInstructionIdPhi:
-        case IrInstructionIdBinOp:
-        case IrInstructionIdLoadVar:
         case IrInstructionIdStoreVar:
         case IrInstructionIdCall:
         case IrInstructionIdBuiltinCall:
+        case IrInstructionIdCast:
             zig_panic("TODO render more IR instructions to LLVM");
     }
     zig_unreachable();
@@ -5013,6 +5106,9 @@ static void init(CodeGen *g, Buf *source_path) {
 
     define_builtin_types(g);
     define_builtin_fns(g);
+
+    g->invalid_instruction = allocate<IrInstruction>(1);
+    g->invalid_instruction->type_entry = g->builtin_types.entry_invalid;
 }
 
 void codegen_parseh(CodeGen *g, Buf *src_dirname, Buf *src_basename, Buf *source_code) {
src/ir.cpp
@@ -9,7 +9,13 @@ struct IrGen {
     IrExecutable *exec;
 };
 
-static IrInstruction *ir_gen_node(IrGen *ir, AstNode *node, BlockContext *block_context);
+struct IrAnalyze {
+    CodeGen *codegen;
+    IrExecutable *exec;
+    IrBasicBlock *current_basic_block;
+};
+
+static IrInstruction *ir_gen_node(IrGen *irg, AstNode *node, BlockContext *scope);
 
 static void ir_instruction_append(IrBasicBlock *basic_block, IrInstruction *instruction) {
     if (!basic_block->last) {
@@ -25,9 +31,33 @@ static void ir_instruction_append(IrBasicBlock *basic_block, IrInstruction *inst
     }
 }
 
-static size_t exec_next_debug_id(IrGen *ir) {
-    size_t result = ir->exec->next_debug_id;
-    ir->exec->next_debug_id += 1;
+static void ir_instruction_insert(IrBasicBlock *basic_block,
+        IrInstruction *before_instruction, IrInstruction *after_instruction,
+        IrInstruction *new_instruction)
+{
+    assert(before_instruction || after_instruction);
+    assert(!before_instruction || !after_instruction);
+
+    if (before_instruction) {
+        IrInstruction *displaced_instruction = before_instruction->prev;
+        before_instruction->prev = new_instruction;
+        new_instruction->prev = displaced_instruction;
+        new_instruction->next = before_instruction;
+        if (basic_block->first == before_instruction)
+            basic_block->first = new_instruction;
+    } else {
+        IrInstruction *displaced_instruction = after_instruction->next;
+        after_instruction->next = new_instruction;
+        new_instruction->prev = after_instruction;
+        new_instruction->next = displaced_instruction;
+        if (basic_block->last == after_instruction)
+            basic_block->last = new_instruction;
+    }
+}
+
+static size_t exec_next_debug_id(IrExecutable *exec) {
+    size_t result = exec->next_debug_id;
+    exec->next_debug_id += 1;
     return result;
 }
 
@@ -71,32 +101,117 @@ static constexpr IrInstructionId ir_instruction_id(IrInstructionReturn *) {
     return IrInstructionIdReturn;
 }
 
+static constexpr IrInstructionId ir_instruction_id(IrInstructionCast *) {
+    return IrInstructionIdCast;
+}
+
 template<typename T>
-static T *ir_build_instruction(IrGen *ir, AstNode *source_node) {
+static T *ir_create_instruction(IrExecutable *exec, AstNode *source_node) {
     T *special_instruction = allocate<T>(1);
     special_instruction->base.id = ir_instruction_id(special_instruction);
     special_instruction->base.source_node = source_node;
-    special_instruction->base.type_entry = ir->codegen->builtin_types.entry_unreachable;
-    special_instruction->base.debug_id = exec_next_debug_id(ir);
-    ir_instruction_append(ir->current_basic_block, &special_instruction->base);
+    special_instruction->base.debug_id = exec_next_debug_id(exec);
+    return special_instruction;
+}
+
+template<typename T>
+static T *ir_build_instruction(IrGen *irg, AstNode *source_node) {
+    T *special_instruction = ir_create_instruction<T>(irg->exec, source_node);
+    ir_instruction_append(irg->current_basic_block, &special_instruction->base);
     return special_instruction;
 }
 
-static IrInstruction *ir_build_return(IrGen *ir, AstNode *source_node, IrInstruction *return_value) {
-    IrInstructionReturn *return_instruction = ir_build_instruction<IrInstructionReturn>(ir, source_node);
-    return_instruction->base.type_entry = ir->codegen->builtin_types.entry_unreachable;
+static IrInstruction *ir_insert_const_type(IrAnalyze *ira, IrInstruction *before_instruction,
+        IrInstruction *after_instruction, TypeTableEntry *type_entry)
+{
+    IrInstructionConst *const_instruction = ir_create_instruction<IrInstructionConst>(ira->exec,
+            before_instruction->source_node);
+    const_instruction->base.type_entry = ira->codegen->builtin_types.entry_type;
+    const_instruction->base.static_value.ok = true;
+    const_instruction->base.static_value.data.x_type = type_entry;
+    ir_instruction_insert(ira->current_basic_block, before_instruction, after_instruction, &const_instruction->base);
+    return &const_instruction->base;
+}
+
+static IrInstruction *ir_insert_cast(IrAnalyze *ira,
+        IrInstruction *before_instruction, IrInstruction *after_instruction,
+        IrInstruction *dest_type, IrInstruction *value, bool is_implicit)
+{
+    IrInstructionCast *cast_instruction = ir_create_instruction<IrInstructionCast>(ira->exec,
+            before_instruction->source_node);
+    cast_instruction->dest_type = dest_type;
+    cast_instruction->value = value;
+    cast_instruction->is_implicit = is_implicit;
+    ir_instruction_insert(ira->current_basic_block, before_instruction, after_instruction, &cast_instruction->base);
+    return &cast_instruction->base;
+}
+
+static IrInstruction *ir_build_return(IrGen *irg, AstNode *source_node, IrInstruction *return_value) {
+    IrInstructionReturn *return_instruction = ir_build_instruction<IrInstructionReturn>(irg, source_node);
+    return_instruction->base.type_entry = irg->codegen->builtin_types.entry_unreachable;
     return_instruction->base.static_value.ok = true;
     return_instruction->value = return_value;
     return &return_instruction->base;
 }
 
-static IrInstruction *ir_build_void(IrGen *ir, AstNode *source_node) {
-    IrInstructionConst *const_instruction = ir_build_instruction<IrInstructionConst>(ir, source_node);
-    const_instruction->base.type_entry = ir->codegen->builtin_types.entry_void;
+static IrInstruction *ir_build_const_void(IrGen *irg, AstNode *source_node) {
+    IrInstructionConst *const_instruction = ir_build_instruction<IrInstructionConst>(irg, source_node);
+    const_instruction->base.type_entry = irg->codegen->builtin_types.entry_void;
+    const_instruction->base.static_value.ok = true;
+    return &const_instruction->base;
+}
+
+static IrInstruction *ir_build_const_bignum(IrGen *irg, AstNode *source_node, BigNum *bignum) {
+    IrInstructionConst *const_instruction = ir_build_instruction<IrInstructionConst>(irg, source_node);
+    const_instruction->base.type_entry = (bignum->kind == BigNumKindInt) ?
+        irg->codegen->builtin_types.entry_num_lit_int : irg->codegen->builtin_types.entry_num_lit_float;
     const_instruction->base.static_value.ok = true;
+    const_instruction->base.static_value.data.x_bignum = *bignum;
     return &const_instruction->base;
 }
 
+static IrInstruction *ir_build_const_type(IrGen *irg, AstNode *source_node, TypeTableEntry *type_entry) {
+    IrInstructionConst *const_instruction = ir_build_instruction<IrInstructionConst>(irg, source_node);
+    const_instruction->base.type_entry = irg->codegen->builtin_types.entry_type;
+    const_instruction->base.static_value.ok = true;
+    const_instruction->base.static_value.data.x_type = type_entry;
+    return &const_instruction->base;
+}
+
+static IrInstruction *ir_build_const_fn(IrGen *irg, AstNode *source_node, FnTableEntry *fn_entry) {
+    IrInstructionConst *const_instruction = ir_build_instruction<IrInstructionConst>(irg, source_node);
+    const_instruction->base.type_entry = fn_entry->type_entry;
+    const_instruction->base.static_value.ok = true;
+    const_instruction->base.static_value.data.x_fn = fn_entry;
+    return &const_instruction->base;
+}
+
+static IrInstruction *ir_build_const_generic_fn(IrGen *irg, AstNode *source_node, TypeTableEntry *fn_type) {
+    IrInstructionConst *const_instruction = ir_build_instruction<IrInstructionConst>(irg, source_node);
+    const_instruction->base.type_entry = fn_type;
+    const_instruction->base.static_value.ok = true;
+    const_instruction->base.static_value.data.x_type = fn_type;
+    return &const_instruction->base;
+}
+
+static IrInstruction *ir_build_bin_op(IrGen *irg, AstNode *source_node, IrBinOp op_id,
+        IrInstruction *op1, IrInstruction *op2)
+{
+    IrInstructionBinOp *bin_op_instruction = ir_build_instruction<IrInstructionBinOp>(irg, source_node);
+    bin_op_instruction->op_id = op_id;
+    bin_op_instruction->op1 = op1;
+    bin_op_instruction->op2 = op2;
+    return &bin_op_instruction->base;
+}
+
+static IrInstruction *ir_build_load_var(IrGen *irg, AstNode *source_node, VariableTableEntry *var) {
+    IrInstructionLoadVar *load_var_instruction = ir_build_instruction<IrInstructionLoadVar>(irg, source_node);
+    load_var_instruction->base.type_entry = var->type;
+    load_var_instruction->var = var;
+    return &load_var_instruction->base;
+}
+
+
 //static size_t get_conditional_defer_count(BlockContext *inner_block, BlockContext *outer_block) {
 //    size_t result = 0;
 //    while (inner_block != outer_block) {
@@ -111,7 +226,7 @@ static IrInstruction *ir_build_void(IrGen *ir, AstNode *source_node) {
 //    return result;
 //}
 
-static void ir_gen_defers_for_block(IrGen *ir, BlockContext *inner_block, BlockContext *outer_block,
+static void ir_gen_defers_for_block(IrGen *irg, BlockContext *inner_block, BlockContext *outer_block,
         bool gen_error_defers, bool gen_maybe_defers)
 {
     while (inner_block != outer_block) {
@@ -121,15 +236,15 @@ static void ir_gen_defers_for_block(IrGen *ir, BlockContext *inner_block, BlockC
             (gen_maybe_defers && inner_block->node->data.defer.kind == ReturnKindMaybe)))
         {
             AstNode *defer_expr_node = inner_block->node->data.defer.expr;
-            ir_gen_node(ir, defer_expr_node, defer_expr_node->block_context);
+            ir_gen_node(irg, defer_expr_node, defer_expr_node->block_context);
         }
         inner_block = inner_block->parent;
     }
 }
 
-//static IrInstruction *ir_gen_return(IrGen *ir, AstNode *source_node, IrInstruction *value, ReturnKnowledge rk) {
+//static IrInstruction *ir_gen_return(IrGen *irg, AstNode *source_node, IrInstruction *value, ReturnKnowledge rk) {
 //    BlockContext *defer_inner_block = source_node->block_context;
-//    BlockContext *defer_outer_block = ir->node->block_context;
+//    BlockContext *defer_outer_block = irg->node->block_context;
 //    if (rk == ReturnKnowledgeUnknown) {
 //        if (get_conditional_defer_count(defer_inner_block, defer_outer_block) > 0) {
 //            // generate branching code that checks the return value and generates defers
@@ -137,14 +252,14 @@ static void ir_gen_defers_for_block(IrGen *ir, BlockContext *inner_block, BlockC
 //            zig_panic("TODO");
 //        }
 //    } else if (rk != ReturnKnowledgeSkipDefers) {
-//        ir_gen_defers_for_block(ir, defer_inner_block, defer_outer_block,
+//        ir_gen_defers_for_block(irg, defer_inner_block, defer_outer_block,
 //                rk == ReturnKnowledgeKnownError, rk == ReturnKnowledgeKnownNull);
 //    }
 //
-//    return ir_build_return(ir, source_node, value);
+//    return ir_build_return(irg, source_node, value);
 //}
 
-static IrInstruction *ir_gen_block(IrGen *ir, AstNode *block_node) {
+static IrInstruction *ir_gen_block(IrGen *irg, AstNode *block_node) {
     assert(block_node->type == NodeTypeBlock);
 
     BlockContext *parent_context = block_node->block_context;
@@ -154,8 +269,8 @@ static IrInstruction *ir_gen_block(IrGen *ir, AstNode *block_node) {
     IrInstruction *return_value = nullptr;
     for (size_t i = 0; i < block_node->data.block.statements.length; i += 1) {
         AstNode *statement_node = block_node->data.block.statements.at(i);
-        return_value = ir_gen_node(ir, statement_node, child_context);
-        if (statement_node->type == NodeTypeDefer && return_value != ir->codegen->invalid_instruction) {
+        return_value = ir_gen_node(irg, statement_node, child_context);
+        if (statement_node->type == NodeTypeDefer && return_value != irg->codegen->invalid_instruction) {
             // defer starts a new block context
             child_context = statement_node->data.defer.child_block;
             assert(child_context);
@@ -163,20 +278,188 @@ static IrInstruction *ir_gen_block(IrGen *ir, AstNode *block_node) {
     }
 
     if (!return_value)
-        return_value = ir_build_void(ir, block_node);
+        return_value = ir_build_const_void(irg, block_node);
 
-    ir_gen_defers_for_block(ir, child_context, outer_block_context, false, false);
+    ir_gen_defers_for_block(irg, child_context, outer_block_context, false, false);
 
     return return_value;
 }
 
-static IrInstruction *ir_gen_node(IrGen *ir, AstNode *node, BlockContext *block_context) {
+static IrInstruction *ir_gen_bin_op_id(IrGen *irg, AstNode *node, IrBinOp op_id) {
+    IrInstruction *op1 = ir_gen_node(irg, node->data.bin_op_expr.op1, node->block_context);
+    IrInstruction *op2 = ir_gen_node(irg, node->data.bin_op_expr.op2, node->block_context);
+    return ir_build_bin_op(irg, node, op_id, op1, op2);
+}
+
+static IrInstruction *ir_gen_bin_op(IrGen *irg, AstNode *node) {
+    assert(node->type == NodeTypeBinOpExpr);
+
+    BinOpType bin_op_type = node->data.bin_op_expr.bin_op;
+    switch (bin_op_type) {
+        case BinOpTypeInvalid:
+            zig_unreachable();
+        case BinOpTypeAssign:
+        case BinOpTypeAssignTimes:
+        case BinOpTypeAssignTimesWrap:
+        case BinOpTypeAssignDiv:
+        case BinOpTypeAssignMod:
+        case BinOpTypeAssignPlus:
+        case BinOpTypeAssignPlusWrap:
+        case BinOpTypeAssignMinus:
+        case BinOpTypeAssignMinusWrap:
+        case BinOpTypeAssignBitShiftLeft:
+        case BinOpTypeAssignBitShiftLeftWrap:
+        case BinOpTypeAssignBitShiftRight:
+        case BinOpTypeAssignBitAnd:
+        case BinOpTypeAssignBitXor:
+        case BinOpTypeAssignBitOr:
+        case BinOpTypeAssignBoolAnd:
+        case BinOpTypeAssignBoolOr:
+            zig_panic("TODO gen IR for assignment");
+        case BinOpTypeBoolOr:
+        case BinOpTypeBoolAnd:
+            // note: this is not a direct mapping to IrBinOpBoolOr/And
+            // because of the control flow
+            zig_panic("TODO gen IR for bool or/and");
+        case BinOpTypeCmpEq:
+            return ir_gen_bin_op_id(irg, node, IrBinOpCmpEq);
+        case BinOpTypeCmpNotEq:
+            return ir_gen_bin_op_id(irg, node, IrBinOpCmpNotEq);
+        case BinOpTypeCmpLessThan:
+            return ir_gen_bin_op_id(irg, node, IrBinOpCmpLessThan);
+        case BinOpTypeCmpGreaterThan:
+            return ir_gen_bin_op_id(irg, node, IrBinOpCmpGreaterThan);
+        case BinOpTypeCmpLessOrEq:
+            return ir_gen_bin_op_id(irg, node, IrBinOpCmpLessOrEq);
+        case BinOpTypeCmpGreaterOrEq:
+            return ir_gen_bin_op_id(irg, node, IrBinOpCmpGreaterOrEq);
+        case BinOpTypeBinOr:
+            return ir_gen_bin_op_id(irg, node, IrBinOpBinOr);
+        case BinOpTypeBinXor:
+            return ir_gen_bin_op_id(irg, node, IrBinOpBinXor);
+        case BinOpTypeBinAnd:
+            return ir_gen_bin_op_id(irg, node, IrBinOpBinAnd);
+        case BinOpTypeBitShiftLeft:
+            return ir_gen_bin_op_id(irg, node, IrBinOpBitShiftLeft);
+        case BinOpTypeBitShiftLeftWrap:
+            return ir_gen_bin_op_id(irg, node, IrBinOpBitShiftLeftWrap);
+        case BinOpTypeBitShiftRight:
+            return ir_gen_bin_op_id(irg, node, IrBinOpBitShiftRight);
+        case BinOpTypeAdd:
+            return ir_gen_bin_op_id(irg, node, IrBinOpAdd);
+        case BinOpTypeAddWrap:
+            return ir_gen_bin_op_id(irg, node, IrBinOpAddWrap);
+        case BinOpTypeSub:
+            return ir_gen_bin_op_id(irg, node, IrBinOpSub);
+        case BinOpTypeSubWrap:
+            return ir_gen_bin_op_id(irg, node, IrBinOpSubWrap);
+        case BinOpTypeMult:
+            return ir_gen_bin_op_id(irg, node, IrBinOpMult);
+        case BinOpTypeMultWrap:
+            return ir_gen_bin_op_id(irg, node, IrBinOpMultWrap);
+        case BinOpTypeDiv:
+            return ir_gen_bin_op_id(irg, node, IrBinOpDiv);
+        case BinOpTypeMod:
+            return ir_gen_bin_op_id(irg, node, IrBinOpMod);
+        case BinOpTypeArrayCat:
+            return ir_gen_bin_op_id(irg, node, IrBinOpArrayCat);
+        case BinOpTypeArrayMult:
+            return ir_gen_bin_op_id(irg, node, IrBinOpArrayMult);
+        case BinOpTypeUnwrapMaybe:
+            zig_panic("TODO gen IR for unwrap maybe");
+    }
+    zig_unreachable();
+}
+
+static IrInstruction *ir_gen_num_lit(IrGen *irg, AstNode *node) {
+    assert(node->type == NodeTypeNumberLiteral);
+
+    if (node->data.number_literal.overflow) {
+        add_node_error(irg->codegen, node, buf_sprintf("number literal too large to be represented in any type"));
+        return irg->codegen->invalid_instruction;
+    }
+
+    return ir_build_const_bignum(irg, node, node->data.number_literal.bignum);
+}
+
+static IrInstruction *ir_gen_decl_ref(IrGen *irg, AstNode *source_node, AstNode *decl_node,
+        bool pointer_only, BlockContext *scope)
+{
+    resolve_top_level_decl(irg->codegen, decl_node, pointer_only);
+    TopLevelDecl *tld = get_as_top_level_decl(decl_node);
+    if (tld->resolution == TldResolutionInvalid)
+        return irg->codegen->invalid_instruction;
+
+    if (decl_node->type == NodeTypeVariableDeclaration) {
+        VariableTableEntry *var = decl_node->data.variable_declaration.variable;
+        return ir_build_load_var(irg, source_node, var);
+    } else if (decl_node->type == NodeTypeFnProto) {
+        FnTableEntry *fn_entry = decl_node->data.fn_proto.fn_table_entry;
+        assert(fn_entry->type_entry);
+        if (fn_entry->type_entry->id == TypeTableEntryIdGenericFn) {
+            return ir_build_const_generic_fn(irg, source_node, fn_entry->type_entry);
+        } else {
+            return ir_build_const_fn(irg, source_node, fn_entry);
+        }
+    } else if (decl_node->type == NodeTypeContainerDecl) {
+        if (decl_node->data.struct_decl.generic_params.length > 0) {
+            TypeTableEntry *type_entry = decl_node->data.struct_decl.generic_fn_type;
+            assert(type_entry);
+            return ir_build_const_generic_fn(irg, source_node, type_entry);
+        } else {
+            return ir_build_const_type(irg, source_node, decl_node->data.struct_decl.type_entry);
+        }
+    } else if (decl_node->type == NodeTypeTypeDecl) {
+        return ir_build_const_type(irg, source_node, decl_node->data.type_decl.child_type_entry);
+    } else {
+        zig_unreachable();
+    }
+}
+
+static IrInstruction *ir_gen_symbol(IrGen *irg, AstNode *node, bool pointer_only) {
+    assert(node->type == NodeTypeSymbol);
+
+    if (node->data.symbol_expr.override_type_entry)
+        return ir_build_const_type(irg, node, node->data.symbol_expr.override_type_entry);
+
+    Buf *variable_name = node->data.symbol_expr.symbol;
+
+    auto primitive_table_entry = irg->codegen->primitive_type_table.maybe_get(variable_name);
+    if (primitive_table_entry)
+        return ir_build_const_type(irg, node, primitive_table_entry->value);
+
+    VariableTableEntry *var = find_variable(irg->codegen, node->block_context, variable_name);
+    if (var)
+        return ir_build_load_var(irg, node, var);
+
+    AstNode *decl_node = find_decl(node->block_context, variable_name);
+    if (decl_node)
+        return ir_gen_decl_ref(irg, node, decl_node, pointer_only, node->block_context);
+
+    if (node->owner->any_imports_failed) {
+        // skip the error message since we had a failing import in this file
+        // if an import breaks we don't need redundant undeclared identifier errors
+        return irg->codegen->invalid_instruction;
+    }
+
+    add_node_error(irg->codegen, node, buf_sprintf("use of undeclared identifier '%s'", buf_ptr(variable_name)));
+    return irg->codegen->invalid_instruction;
+}
+
+static IrInstruction *ir_gen_node_extra(IrGen *irg, AstNode *node, BlockContext *block_context,
+        bool pointer_only)
+{
     node->block_context = block_context;
 
     switch (node->type) {
         case NodeTypeBlock:
-            return ir_gen_block(ir, node);
+            return ir_gen_block(irg, node);
         case NodeTypeBinOpExpr:
+            return ir_gen_bin_op(irg, node);
+        case NodeTypeNumberLiteral:
+            return ir_gen_num_lit(irg, node);
+        case NodeTypeSymbol:
+            return ir_gen_symbol(irg, node, pointer_only);
         case NodeTypeUnwrapErrorExpr:
         case NodeTypeReturnExpr:
         case NodeTypeDefer:
@@ -191,14 +474,12 @@ static IrInstruction *ir_gen_node(IrGen *ir, AstNode *node, BlockContext *block_
         case NodeTypeWhileExpr:
         case NodeTypeForExpr:
         case NodeTypeAsmExpr:
-        case NodeTypeSymbol:
         case NodeTypeGoto:
         case NodeTypeBreak:
         case NodeTypeContinue:
         case NodeTypeLabel:
         case NodeTypeContainerInitExpr:
         case NodeTypeSwitchExpr:
-        case NodeTypeNumberLiteral:
         case NodeTypeBoolLiteral:
         case NodeTypeStringLiteral:
         case NodeTypeCharLiteral:
@@ -228,42 +509,49 @@ static IrInstruction *ir_gen_node(IrGen *ir, AstNode *node, BlockContext *block_
     zig_unreachable();
 }
 
+static IrInstruction *ir_gen_node(IrGen *irg, AstNode *node, BlockContext *scope) {
+    bool pointer_only_no = false;
+    return ir_gen_node_extra(irg, node, scope, pointer_only_no);
+}
+
 static IrInstruction *ir_gen_add_return(CodeGen *g, AstNode *node, BlockContext *scope,
-        IrExecutable *ir_executable, bool add_return)
+        IrExecutable *ir_executable, bool add_return, bool pointer_only)
 {
     assert(node->owner);
 
     IrGen ir_gen = {0};
-    IrGen *ir = &ir_gen;
+    IrGen *irg = &ir_gen;
 
-    ir->codegen = g;
-    ir->node = node;
-    ir->exec = ir_executable;
+    irg->codegen = g;
+    irg->node = node;
+    irg->exec = ir_executable;
 
-    ir->exec->basic_block_list = allocate<IrBasicBlock*>(1);
-    ir->exec->basic_block_count = 1;
+    irg->exec->basic_block_list = allocate<IrBasicBlock*>(1);
+    irg->exec->basic_block_count = 1;
 
     IrBasicBlock *entry_basic_block = allocate<IrBasicBlock>(1);
-    ir->current_basic_block = entry_basic_block;
-    ir->exec->basic_block_list[0] = entry_basic_block;
+    irg->current_basic_block = entry_basic_block;
+    irg->exec->basic_block_list[0] = entry_basic_block;
 
-    IrInstruction *result = ir_gen_node(ir, node, scope);
+    IrInstruction *result = ir_gen_node_extra(irg, node, scope, pointer_only);
     assert(result);
 
     if (result == g->invalid_instruction)
         return result;
 
     if (add_return)
-        return ir_build_return(ir, result->source_node, result);
+        return ir_build_return(irg, result->source_node, result);
 
     return result;
 }
 
-IrInstruction *ir_gen(CodeGen *g, AstNode *node, BlockContext *scope, IrExecutable *ir_executable) {
-    return ir_gen_add_return(g, node, scope, ir_executable, false);
+IrInstruction *ir_gen(CodeGen *codegen, AstNode *node, BlockContext *scope, IrExecutable *ir_executable) {
+    bool add_return_no = false;
+    bool pointer_only_no = false;
+    return ir_gen_add_return(codegen, node, scope, ir_executable, add_return_no, pointer_only_no);
 }
 
-IrInstruction *ir_gen_fn(CodeGen *g, FnTableEntry *fn_entry) {
+IrInstruction *ir_gen_fn(CodeGen *codegn, FnTableEntry *fn_entry) {
     assert(fn_entry);
 
     IrExecutable *ir_executable = &fn_entry->ir_executable;
@@ -274,7 +562,8 @@ IrInstruction *ir_gen_fn(CodeGen *g, FnTableEntry *fn_entry) {
     BlockContext *scope = fn_def_node->data.fn_def.block_context;
 
     bool add_return_yes = true;
-    return ir_gen_add_return(g, body_node, scope, ir_executable, add_return_yes);
+    bool pointer_only_no = false;
+    return ir_gen_add_return(codegn, body_node, scope, ir_executable, add_return_yes, pointer_only_no);
 }
 
 /*
@@ -322,96 +611,496 @@ static void analyze_goto_pass2(CodeGen *g, ImportTableEntry *import, AstNode *no
 //    return nullptr;
 //}
 
-static IrInstruction *ir_get_casted_instruction(CodeGen *g, IrInstruction *instruction,
-        TypeTableEntry *expected_type)
+static bool ir_num_lit_fits_in_other_type(IrAnalyze *ira, IrInstruction *instruction, TypeTableEntry *other_type) {
+    TypeTableEntry *other_type_underlying = get_underlying_type(other_type);
+
+    if (other_type_underlying->id == TypeTableEntryIdInvalid) {
+        return false;
+    }
+
+    ConstExprValue *const_val = &instruction->static_value;
+    assert(const_val->ok);
+    if (other_type_underlying->id == TypeTableEntryIdFloat) {
+        return true;
+    } else if (other_type_underlying->id == TypeTableEntryIdInt &&
+               const_val->data.x_bignum.kind == BigNumKindInt)
+    {
+        if (bignum_fits_in_bits(&const_val->data.x_bignum, other_type_underlying->data.integral.bit_count,
+                    other_type_underlying->data.integral.is_signed))
+        {
+            return true;
+        }
+    } else if ((other_type_underlying->id == TypeTableEntryIdNumLitFloat &&
+                const_val->data.x_bignum.kind == BigNumKindFloat) ||
+               (other_type_underlying->id == TypeTableEntryIdNumLitInt &&
+                const_val->data.x_bignum.kind == BigNumKindInt))
+    {
+        return true;
+    }
+
+    const char *num_lit_str = (const_val->data.x_bignum.kind == BigNumKindFloat) ? "float" : "integer";
+
+    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)),
+            buf_ptr(&other_type->name)));
+    return false;
+}
+
+static TypeTableEntry *ir_determine_peer_types(IrAnalyze *ira, IrInstruction *parent_instruction,
+        IrInstruction **instructions, size_t instruction_count)
+{
+    assert(instruction_count >= 1);
+    IrInstruction *prev_inst = instructions[0];
+    if (prev_inst->type_entry->id == TypeTableEntryIdInvalid) {
+        return ira->codegen->builtin_types.entry_invalid;
+    }
+    for (size_t i = 1; i < instruction_count; i += 1) {
+        IrInstruction *cur_inst = instructions[i];
+        TypeTableEntry *cur_type = cur_inst->type_entry;
+        TypeTableEntry *prev_type = prev_inst->type_entry;
+        if (cur_type->id == TypeTableEntryIdInvalid) {
+            return cur_type;
+        } else if (types_match_const_cast_only(prev_type, cur_type)) {
+            continue;
+        } else if (types_match_const_cast_only(cur_type, prev_type)) {
+            prev_inst = cur_inst;
+            continue;
+        } else if (prev_type->id == TypeTableEntryIdUnreachable) {
+            prev_inst = cur_inst;
+        } else if (cur_type->id == TypeTableEntryIdUnreachable) {
+            continue;
+        } else if (prev_type->id == TypeTableEntryIdInt &&
+                   cur_type->id == TypeTableEntryIdInt &&
+                   prev_type->data.integral.is_signed == cur_type->data.integral.is_signed)
+        {
+            if (cur_type->data.integral.bit_count > prev_type->data.integral.bit_count) {
+                prev_inst = cur_inst;
+            }
+            continue;
+        } else if (prev_type->id == TypeTableEntryIdFloat &&
+                   cur_type->id == TypeTableEntryIdFloat)
+        {
+            if (cur_type->data.floating.bit_count > prev_type->data.floating.bit_count) {
+                prev_inst = cur_inst;
+            }
+        } else if (prev_type->id == TypeTableEntryIdErrorUnion &&
+                   types_match_const_cast_only(prev_type->data.error.child_type, cur_type))
+        {
+            continue;
+        } else if (cur_type->id == TypeTableEntryIdErrorUnion &&
+                   types_match_const_cast_only(cur_type->data.error.child_type, prev_type))
+        {
+            prev_inst = cur_inst;
+            continue;
+        } else if (prev_type->id == TypeTableEntryIdNumLitInt ||
+                    prev_type->id == TypeTableEntryIdNumLitFloat)
+        {
+            if (ir_num_lit_fits_in_other_type(ira, prev_inst, cur_type)) {
+                prev_inst = cur_inst;
+                continue;
+            } else {
+                return ira->codegen->builtin_types.entry_invalid;
+            }
+        } else if (cur_type->id == TypeTableEntryIdNumLitInt ||
+                   cur_type->id == TypeTableEntryIdNumLitFloat)
+        {
+            if (ir_num_lit_fits_in_other_type(ira, cur_inst, prev_type)) {
+                continue;
+            } else {
+                return ira->codegen->builtin_types.entry_invalid;
+            }
+        } else {
+            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->codegen->builtin_types.entry_invalid;
+        }
+    }
+    return prev_inst->type_entry;
+}
+
+enum ImplicitCastMatchResult {
+    ImplicitCastMatchResultNo,
+    ImplicitCastMatchResultYes,
+    ImplicitCastMatchResultReportedError,
+};
+
+static ImplicitCastMatchResult ir_types_match_with_implicit_cast(IrAnalyze *ira, TypeTableEntry *expected_type,
+        TypeTableEntry *actual_type, IrInstruction *value)
+{
+    if (types_match_const_cast_only(expected_type, actual_type)) {
+        return ImplicitCastMatchResultYes;
+    }
+
+    // implicit conversion from non maybe type to maybe type
+    if (expected_type->id == TypeTableEntryIdMaybe &&
+        ir_types_match_with_implicit_cast(ira, expected_type->data.maybe.child_type, actual_type, value))
+    {
+        return ImplicitCastMatchResultYes;
+    }
+
+    // implicit conversion from null literal to maybe type
+    if (expected_type->id == TypeTableEntryIdMaybe &&
+        actual_type->id == TypeTableEntryIdNullLit)
+    {
+        return ImplicitCastMatchResultYes;
+    }
+
+    // implicit conversion from error child type to error type
+    if (expected_type->id == TypeTableEntryIdErrorUnion &&
+        ir_types_match_with_implicit_cast(ira, expected_type->data.error.child_type, actual_type, value))
+    {
+        return ImplicitCastMatchResultYes;
+    }
+
+    // implicit conversion from pure error to error union type
+    if (expected_type->id == TypeTableEntryIdErrorUnion &&
+        actual_type->id == TypeTableEntryIdPureError)
+    {
+        return ImplicitCastMatchResultYes;
+    }
+
+    // implicit widening conversion
+    if (expected_type->id == TypeTableEntryIdInt &&
+        actual_type->id == TypeTableEntryIdInt &&
+        expected_type->data.integral.is_signed == actual_type->data.integral.is_signed &&
+        expected_type->data.integral.bit_count >= actual_type->data.integral.bit_count)
+    {
+        return ImplicitCastMatchResultYes;
+    }
+
+    // small enough unsigned ints can get casted to large enough signed ints
+    if (expected_type->id == TypeTableEntryIdInt && expected_type->data.integral.is_signed &&
+        actual_type->id == TypeTableEntryIdInt && !actual_type->data.integral.is_signed &&
+        expected_type->data.integral.bit_count > actual_type->data.integral.bit_count)
+    {
+        return ImplicitCastMatchResultYes;
+    }
+
+    // implicit float widening conversion
+    if (expected_type->id == TypeTableEntryIdFloat &&
+        actual_type->id == TypeTableEntryIdFloat &&
+        expected_type->data.floating.bit_count >= actual_type->data.floating.bit_count)
+    {
+        return ImplicitCastMatchResultYes;
+    }
+
+    // implicit array to slice conversion
+    if (expected_type->id == TypeTableEntryIdStruct &&
+        expected_type->data.structure.is_slice &&
+        actual_type->id == TypeTableEntryIdArray &&
+        types_match_const_cast_only(
+            expected_type->data.structure.fields[0].type_entry->data.pointer.child_type,
+            actual_type->data.array.child_type))
+    {
+        return ImplicitCastMatchResultYes;
+    }
+
+    // implicit number literal to typed number
+    if ((actual_type->id == TypeTableEntryIdNumLitFloat ||
+         actual_type->id == TypeTableEntryIdNumLitInt))
+    {
+        if (ir_num_lit_fits_in_other_type(ira, value, expected_type)) {
+            return ImplicitCastMatchResultYes;
+        } else {
+            return ImplicitCastMatchResultReportedError;
+        }
+    }
+
+    return ImplicitCastMatchResultNo;
+}
+
+static TypeTableEntry *ir_resolve_peer_types(IrAnalyze *ira, IrInstruction *parent_instruction,
+        IrInstruction **instructions, size_t instruction_count)
+{
+    return ir_determine_peer_types(ira, parent_instruction, instructions, instruction_count);
+}
+
+static IrInstruction *ir_get_casted_value(IrAnalyze *ira, IrInstruction *value,
+        TypeTableEntry *expected_type,
+        IrInstruction *before_instruction, IrInstruction *after_instruction)
 {
-    assert(instruction);
-    assert(instruction != g->invalid_instruction);
+    assert(value);
+    assert(before_instruction || after_instruction);
+    assert(!before_instruction || !after_instruction);
+    assert(value != ira->codegen->invalid_instruction);
     assert(!expected_type || expected_type->id != TypeTableEntryIdInvalid);
-    assert(instruction->type_entry);
-    assert(instruction->type_entry->id != TypeTableEntryIdInvalid);
+    assert(value->type_entry);
+    assert(value->type_entry->id != TypeTableEntryIdInvalid);
     if (expected_type == nullptr)
-        return instruction; // anything will do
-    if (expected_type == instruction->type_entry)
-        return instruction; // match
-    if (instruction->type_entry->id == TypeTableEntryIdUnreachable)
-        return instruction;
+        return value; // anything will do
+    if (expected_type == value->type_entry)
+        return value; // match
+    if (value->type_entry->id == TypeTableEntryIdUnreachable)
+        return value;
+
+    ImplicitCastMatchResult result = ir_types_match_with_implicit_cast(ira, expected_type, value->type_entry, value);
+    switch (result) {
+        case ImplicitCastMatchResultNo:
+            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->codegen->invalid_instruction;
+
+        case ImplicitCastMatchResultYes:
+            {
+                IrInstruction *dest_type = ir_insert_const_type(ira, before_instruction,
+                        after_instruction, expected_type);
+                bool is_implicit = true;
+                IrInstruction *cast_instruction = ir_insert_cast(ira, nullptr, dest_type,
+                        dest_type, value, is_implicit);
+                return cast_instruction;
+            }
+        case ImplicitCastMatchResultReportedError:
+            return ira->codegen->invalid_instruction;
+    }
 
-    zig_panic("TODO implicit cast instruction");
+    zig_unreachable();
 }
 
-static TypeTableEntry *ir_analyze_instruction_return(CodeGen *g, IrInstructionReturn *return_instruction) {
+static TypeTableEntry *ir_analyze_instruction_return(IrAnalyze *ira, IrInstructionReturn *return_instruction) {
     AstNode *source_node = return_instruction->base.source_node;
     BlockContext *scope = source_node->block_context;
     if (!scope->fn_entry) {
-        add_node_error(g, source_node, buf_sprintf("return expression outside function definition"));
-        return g->builtin_types.entry_invalid;
+        add_node_error(ira->codegen, source_node, buf_sprintf("return expression outside function definition"));
+        return ira->codegen->builtin_types.entry_invalid;
     }
 
     TypeTableEntry *expected_return_type = scope->fn_entry->type_entry->data.fn.fn_type_id.return_type;
     if (expected_return_type->id == TypeTableEntryIdVoid && !return_instruction->value) {
-        return g->builtin_types.entry_unreachable;
+        return ira->codegen->builtin_types.entry_unreachable;
     }
 
-    return_instruction->value = ir_get_casted_instruction(g, return_instruction->value, expected_return_type);
-    if (return_instruction->value == g->invalid_instruction) {
-        return g->builtin_types.entry_invalid;
+    return_instruction->value = ir_get_casted_value(ira,
+            return_instruction->value, expected_return_type, &return_instruction->base, nullptr);
+    if (return_instruction->value == ira->codegen->invalid_instruction) {
+        return ira->codegen->builtin_types.entry_invalid;
     }
-    return g->builtin_types.entry_unreachable;
+    return ira->codegen->builtin_types.entry_unreachable;
 }
 
-static TypeTableEntry *ir_analyze_instruction_const(CodeGen *g, IrInstructionConst *const_instruction) {
+static TypeTableEntry *ir_analyze_instruction_const(IrAnalyze *ira, IrInstructionConst *const_instruction) {
     return const_instruction->base.type_entry;
 }
 
-static TypeTableEntry *ir_analyze_instruction_nocast(CodeGen *g, IrInstruction *instruction) {
+static TypeTableEntry *ir_analyze_bin_op_bool(IrAnalyze *ira, IrInstructionBinOp *bin_op_instruction) {
+    IrInstruction *op1 = bin_op_instruction->op1;
+    IrInstruction *op2 = bin_op_instruction->op2;
+
+    IrInstruction *casted_op1 = ir_get_casted_value(ira, op1, ira->codegen->builtin_types.entry_bool,
+            &bin_op_instruction->base, nullptr);
+    if (casted_op1 == ira->codegen->invalid_instruction)
+        return ira->codegen->builtin_types.entry_invalid;
+
+    IrInstruction *casted_op2 = ir_get_casted_value(ira, op2, ira->codegen->builtin_types.entry_bool,
+            &bin_op_instruction->base, nullptr);
+    if (casted_op2 == ira->codegen->invalid_instruction)
+        return ira->codegen->builtin_types.entry_invalid;
+
+    return ira->codegen->builtin_types.entry_bool;
+}
+
+static TypeTableEntry *ir_analyze_bin_op_cmp(IrAnalyze *ira, IrInstructionBinOp *bin_op_instruction) {
+    IrInstruction *op1 = bin_op_instruction->op1;
+    IrInstruction *op2 = bin_op_instruction->op2;
+    IrInstruction *instructions[] = {op1, op2};
+    TypeTableEntry *resolved_type = ir_resolve_peer_types(ira, &bin_op_instruction->base, instructions, 2);
+    if (resolved_type->id == TypeTableEntryIdInvalid)
+        return resolved_type;
+    IrBinOp op_id = bin_op_instruction->op_id;
+
+    bool is_equality_cmp = (op_id == IrBinOpCmpEq || op_id == IrBinOpCmpNotEq);
+    AstNode *source_node = bin_op_instruction->base.source_node;
+    switch (resolved_type->id) {
+        case TypeTableEntryIdInvalid:
+            return ira->codegen->builtin_types.entry_invalid;
+
+        case TypeTableEntryIdNumLitFloat:
+        case TypeTableEntryIdNumLitInt:
+        case TypeTableEntryIdInt:
+        case TypeTableEntryIdFloat:
+            break;
+
+        case TypeTableEntryIdBool:
+        case TypeTableEntryIdMetaType:
+        case TypeTableEntryIdVoid:
+        case TypeTableEntryIdPointer:
+        case TypeTableEntryIdPureError:
+        case TypeTableEntryIdFn:
+        case TypeTableEntryIdTypeDecl:
+        case TypeTableEntryIdNamespace:
+        case TypeTableEntryIdBlock:
+        case TypeTableEntryIdGenericFn:
+            if (!is_equality_cmp) {
+                add_node_error(ira->codegen, source_node,
+                    buf_sprintf("operator not allowed for type '%s'", buf_ptr(&resolved_type->name)));
+                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->codegen, source_node,
+                    buf_sprintf("operator not allowed for type '%s'", buf_ptr(&resolved_type->name)));
+                return ira->codegen->builtin_types.entry_invalid;
+            }
+            break;
+
+        case TypeTableEntryIdUnreachable:
+        case TypeTableEntryIdArray:
+        case TypeTableEntryIdStruct:
+        case TypeTableEntryIdUndefLit:
+        case TypeTableEntryIdNullLit:
+        case TypeTableEntryIdMaybe:
+        case TypeTableEntryIdErrorUnion:
+        case TypeTableEntryIdUnion:
+            add_node_error(ira->codegen, source_node,
+                buf_sprintf("operator not allowed for type '%s'", buf_ptr(&resolved_type->name)));
+            return ira->codegen->builtin_types.entry_invalid;
+
+        case TypeTableEntryIdVar:
+            zig_unreachable();
+    }
+
+    return ira->codegen->builtin_types.entry_bool;
+}
+
+static TypeTableEntry *ir_analyze_bin_op_math(IrAnalyze *ira, IrInstructionBinOp *bin_op_instruction) {
+    IrInstruction *op1 = bin_op_instruction->op1;
+    IrInstruction *op2 = bin_op_instruction->op2;
+    IrInstruction *instructions[] = {op1, op2};
+    TypeTableEntry *resolved_type = ir_resolve_peer_types(ira, &bin_op_instruction->base, instructions, 2);
+    if (resolved_type->id == TypeTableEntryIdInvalid)
+        return resolved_type;
+    IrBinOp op_id = bin_op_instruction->op_id;
+
+    if (resolved_type->id == TypeTableEntryIdInt ||
+        resolved_type->id == TypeTableEntryIdNumLitInt)
+    {
+        // int
+    } else if ((resolved_type->id == TypeTableEntryIdFloat ||
+                resolved_type->id == TypeTableEntryIdNumLitFloat) &&
+        (op_id == IrBinOpAdd ||
+            op_id == IrBinOpSub ||
+            op_id == IrBinOpMult ||
+            op_id == IrBinOpDiv ||
+            op_id == IrBinOpMod))
+    {
+        // float
+    } else {
+        AstNode *source_node = bin_op_instruction->base.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->codegen->builtin_types.entry_invalid;
+    }
+
+    return resolved_type;
+}
+
+
+static TypeTableEntry *ir_analyze_instruction_bin_op(IrAnalyze *ira, IrInstructionBinOp *bin_op_instruction) {
+    IrBinOp op_id = bin_op_instruction->op_id;
+    switch (op_id) {
+        case IrBinOpInvalid:
+            zig_unreachable();
+        case IrBinOpBoolOr:
+        case IrBinOpBoolAnd:
+            return ir_analyze_bin_op_bool(ira, bin_op_instruction);
+        case IrBinOpCmpEq:
+        case IrBinOpCmpNotEq:
+        case IrBinOpCmpLessThan:
+        case IrBinOpCmpGreaterThan:
+        case IrBinOpCmpLessOrEq:
+        case IrBinOpCmpGreaterOrEq:
+            return ir_analyze_bin_op_cmp(ira, bin_op_instruction);
+        case IrBinOpBinOr:
+        case IrBinOpBinXor:
+        case IrBinOpBinAnd:
+        case IrBinOpBitShiftLeft:
+        case IrBinOpBitShiftLeftWrap:
+        case IrBinOpBitShiftRight:
+        case IrBinOpAdd:
+        case IrBinOpAddWrap:
+        case IrBinOpSub:
+        case IrBinOpSubWrap:
+        case IrBinOpMult:
+        case IrBinOpMultWrap:
+        case IrBinOpDiv:
+        case IrBinOpMod:
+            return ir_analyze_bin_op_math(ira, bin_op_instruction);
+        case IrBinOpArrayCat:
+        case IrBinOpArrayMult:
+            zig_panic("TODO analyze more binary operations");
+    }
+    zig_unreachable();
+}
+
+static TypeTableEntry *ir_analyze_instruction_load_var(IrAnalyze *ira, IrInstructionLoadVar *load_var_instruction) {
+    return load_var_instruction->var->type;
+}
+
+static TypeTableEntry *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstruction *instruction) {
     switch (instruction->id) {
         case IrInstructionIdInvalid:
             zig_unreachable();
         case IrInstructionIdReturn:
-            return ir_analyze_instruction_return(g, (IrInstructionReturn *)instruction);
+            return ir_analyze_instruction_return(ira, (IrInstructionReturn *)instruction);
         case IrInstructionIdConst:
-            return ir_analyze_instruction_const(g, (IrInstructionConst *)instruction);
+            return ir_analyze_instruction_const(ira, (IrInstructionConst *)instruction);
+        case IrInstructionIdBinOp:
+            return ir_analyze_instruction_bin_op(ira, (IrInstructionBinOp *)instruction);
+        case IrInstructionIdLoadVar:
+            return ir_analyze_instruction_load_var(ira, (IrInstructionLoadVar *)instruction);
         case IrInstructionIdCondBr:
         case IrInstructionIdSwitchBr:
         case IrInstructionIdPhi:
-        case IrInstructionIdBinOp:
-        case IrInstructionIdLoadVar:
         case IrInstructionIdStoreVar:
         case IrInstructionIdCall:
         case IrInstructionIdBuiltinCall:
+        case IrInstructionIdCast:
             zig_panic("TODO analyze more instructions");
     }
     zig_unreachable();
 }
 
-static TypeTableEntry *ir_analyze_instruction(CodeGen *g, IrInstruction *instruction,
+static TypeTableEntry *ir_analyze_instruction(IrAnalyze *ira, IrInstruction *instruction,
         TypeTableEntry *expected_type)
 {
-    TypeTableEntry *instruction_type = ir_analyze_instruction_nocast(g, instruction);
+    TypeTableEntry *instruction_type = ir_analyze_instruction_nocast(ira, instruction);
     instruction->type_entry = instruction_type;
 
-    IrInstruction *casted_instruction = ir_get_casted_instruction(g, instruction, expected_type);
+    IrInstruction *casted_instruction = ir_get_casted_value(ira, instruction, expected_type,
+            nullptr, instruction);
     return casted_instruction->type_entry;
 }
 
-TypeTableEntry *ir_analyze(CodeGen *g, IrExecutable *executable, TypeTableEntry *expected_type) {
-    TypeTableEntry *return_type = g->builtin_types.entry_void;
+TypeTableEntry *ir_analyze(CodeGen *codegen, IrExecutable *executable, TypeTableEntry *expected_type) {
+    IrAnalyze ir_analyze_data = {};
+    IrAnalyze *ira = &ir_analyze_data;
+    ira->codegen = codegen;
+    ira->exec = executable;
+
+    TypeTableEntry *return_type = ira->codegen->builtin_types.entry_void;
 
     for (size_t i = 0; i < executable->basic_block_count; i += 1) {
-        IrBasicBlock *current_block = executable->basic_block_list[i];
+        ira->current_basic_block = executable->basic_block_list[i];
 
-        for (IrInstruction *instruction = current_block->first; instruction != nullptr;
+        for (IrInstruction *instruction = ira->current_basic_block->first; instruction != nullptr;
                 instruction = instruction->next)
         {
             if (return_type->id == TypeTableEntryIdUnreachable) {
-                add_node_error(g, first_executing_node(instruction->source_node),
+                add_node_error(ira->codegen, first_executing_node(instruction->source_node),
                         buf_sprintf("unreachable code"));
                 break;
             }
-            bool is_last = (instruction == current_block->last);
+            bool is_last = (instruction == ira->current_basic_block->last);
             TypeTableEntry *passed_expected_type = is_last ? expected_type : nullptr;
-            return_type = ir_analyze_instruction(g, instruction, passed_expected_type);
+            return_type = ir_analyze_instruction(ira, instruction, passed_expected_type);
         }
     }
 
src/ir_print.cpp
@@ -25,12 +25,24 @@ static void ir_print_return(IrPrint *irp, IrInstructionReturn *return_instructio
 
 static void ir_print_const(IrPrint *irp, IrInstructionConst *const_instruction) {
     ir_print_prefix(irp, &const_instruction->base);
-    switch (const_instruction->base.type_entry->id) {
+    TypeTableEntry *type_entry = const_instruction->base.type_entry;
+    fprintf(irp->f, "%s ", buf_ptr(&type_entry->name));
+    switch (type_entry->id) {
         case TypeTableEntryIdInvalid:
             zig_unreachable();
         case TypeTableEntryIdVoid:
-            fprintf(irp->f, "void\n");
+            fprintf(irp->f, "%s\n", "void");
             break;
+        case TypeTableEntryIdNumLitFloat:
+            fprintf(irp->f, "%f\n", const_instruction->base.static_value.data.x_bignum.data.x_float);
+            break;
+        case TypeTableEntryIdNumLitInt:
+            {
+                BigNum *bignum = &const_instruction->base.static_value.data.x_bignum;
+                const char *negative_str = bignum->is_negative ? "-" : "";
+                fprintf(irp->f, "%s%llu\n", negative_str, bignum->data.x_uint);
+                break;
+            }
         case TypeTableEntryIdVar:
         case TypeTableEntryIdMetaType:
         case TypeTableEntryIdBool:
@@ -40,8 +52,6 @@ static void ir_print_const(IrPrint *irp, IrInstructionConst *const_instruction)
         case TypeTableEntryIdPointer:
         case TypeTableEntryIdArray:
         case TypeTableEntryIdStruct:
-        case TypeTableEntryIdNumLitFloat:
-        case TypeTableEntryIdNumLitInt:
         case TypeTableEntryIdUndefLit:
         case TypeTableEntryIdNullLit:
         case TypeTableEntryIdMaybe:
@@ -58,6 +68,104 @@ static void ir_print_const(IrPrint *irp, IrInstructionConst *const_instruction)
     }
 }
 
+
+static const char *ir_bin_op_id_str(IrBinOp op_id) {
+    switch (op_id) {
+        case IrBinOpInvalid:
+            zig_unreachable();
+        case IrBinOpBoolOr:
+            return "BoolOr";
+        case IrBinOpBoolAnd:
+            return "BoolAnd";
+        case IrBinOpCmpEq:
+            return "==";
+        case IrBinOpCmpNotEq:
+            return "!=";
+        case IrBinOpCmpLessThan:
+            return "<";
+        case IrBinOpCmpGreaterThan:
+            return ">";
+        case IrBinOpCmpLessOrEq:
+            return "<=";
+        case IrBinOpCmpGreaterOrEq:
+            return ">=";
+        case IrBinOpBinOr:
+            return "|";
+        case IrBinOpBinXor:
+            return "^";
+        case IrBinOpBinAnd:
+            return "&";
+        case IrBinOpBitShiftLeft:
+            return "<<";
+        case IrBinOpBitShiftLeftWrap:
+            return "<<%";
+        case IrBinOpBitShiftRight:
+            return ">>";
+        case IrBinOpAdd:
+            return "+";
+        case IrBinOpAddWrap:
+            return "+%";
+        case IrBinOpSub:
+            return "-";
+        case IrBinOpSubWrap:
+            return "-%";
+        case IrBinOpMult:
+            return "*";
+        case IrBinOpMultWrap:
+            return "*%";
+        case IrBinOpDiv:
+            return "/";
+        case IrBinOpMod:
+            return "%";
+        case IrBinOpArrayCat:
+            return "++";
+        case IrBinOpArrayMult:
+            return "**";
+    }
+    zig_unreachable();
+}
+
+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",
+            bin_op_instruction->op1->debug_id,
+            ir_bin_op_id_str(bin_op_instruction->op_id),
+            bin_op_instruction->op2->debug_id);
+}
+
+static void ir_print_load_var(IrPrint *irp, IrInstructionLoadVar *load_var_instruction) {
+    ir_print_prefix(irp, &load_var_instruction->base);
+    fprintf(irp->f, "%s\n",
+            buf_ptr(&load_var_instruction->var->name));
+}
+
+static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) {
+    switch (instruction->id) {
+        case IrInstructionIdInvalid:
+            zig_unreachable();
+        case IrInstructionIdReturn:
+            ir_print_return(irp, (IrInstructionReturn *)instruction);
+            break;
+        case IrInstructionIdConst:
+            ir_print_const(irp, (IrInstructionConst *)instruction);
+            break;
+        case IrInstructionIdBinOp:
+            ir_print_bin_op(irp, (IrInstructionBinOp *)instruction);
+            break;
+        case IrInstructionIdLoadVar:
+            ir_print_load_var(irp, (IrInstructionLoadVar *)instruction);
+            break;
+        case IrInstructionIdCondBr:
+        case IrInstructionIdSwitchBr:
+        case IrInstructionIdPhi:
+        case IrInstructionIdStoreVar:
+        case IrInstructionIdCall:
+        case IrInstructionIdBuiltinCall:
+        case IrInstructionIdCast:
+            zig_panic("TODO print more IR instructions");
+    }
+}
+
 void ir_print(FILE *f, IrExecutable *executable, int indent_size) {
     IrPrint ir_print = {};
     IrPrint *irp = &ir_print;
@@ -70,25 +178,7 @@ void ir_print(FILE *f, IrExecutable *executable, int indent_size) {
         for (IrInstruction *instruction = current_block->first; instruction != nullptr;
                 instruction = instruction->next)
         {
-            switch (instruction->id) {
-                case IrInstructionIdInvalid:
-                    zig_unreachable();
-                case IrInstructionIdReturn:
-                    ir_print_return(irp, (IrInstructionReturn *)instruction);
-                    break;
-                case IrInstructionIdConst:
-                    ir_print_const(irp, (IrInstructionConst *)instruction);
-                    break;
-                case IrInstructionIdCondBr:
-                case IrInstructionIdSwitchBr:
-                case IrInstructionIdPhi:
-                case IrInstructionIdBinOp:
-                case IrInstructionIdLoadVar:
-                case IrInstructionIdStoreVar:
-                case IrInstructionIdCall:
-                case IrInstructionIdBuiltinCall:
-                    zig_panic("TODO print more IR instructions");
-            }
+            ir_print_instruction(irp, instruction);
         }
     }
 }