Commit a177e30534

Andrew Kelley <superjoe30@gmail.com>
2016-04-12 00:32:08
compile-time function evaluation of pure functions
1 parent 5a47972
src/all_types.hpp
@@ -65,6 +65,7 @@ struct ConstExprValue {
     bool ok; // true if constant expression evalution worked
     bool depends_on_compile_var;
     bool undef;
+    bool deep_const;
 
     union {
         BigNum x_bignum;
@@ -1005,6 +1006,13 @@ struct ImportTableEntry {
     ZigList<AstNode *> use_decls;
 };
 
+enum FnAnalState {
+    FnAnalStateReady,
+    FnAnalStateProbing,
+    FnAnalStateComplete,
+    FnAnalStateSkipped,
+};
+
 struct FnTableEntry {
     LLVMValueRef fn_value;
     AstNode *proto_node;
@@ -1019,7 +1027,9 @@ struct FnTableEntry {
     bool internal_linkage;
     bool is_extern;
     bool is_test;
+    bool is_pure;
     BlockContext *parent_block_context;
+    FnAnalState anal_state;
 
     ZigList<AstNode *> cast_alloca_list;
     ZigList<StructValExprCodeGen *> struct_val_expr_alloca_list;
@@ -1027,6 +1037,32 @@ struct FnTableEntry {
     ZigList<AstNode *> goto_list;
 };
 
+struct EvalVar {
+    Buf *name;
+    ConstExprValue value;
+};
+
+struct EvalScope {
+    BlockContext *block_context;
+    ZigList<EvalVar> vars;
+};
+
+struct EvalFnRoot {
+    CodeGen *codegen;
+    FnTableEntry *fn;
+    AstNode *call_node;
+    int branch_quota;
+    int branches_used;
+    AstNode *exceeded_quota_node;
+};
+
+struct EvalFn {
+    EvalFnRoot *root;
+    FnTableEntry *fn;
+    ConstExprValue *return_expr;
+    ZigList<EvalScope*> scope_stack;
+};
+
 enum BuiltinFnId {
     BuiltinFnIdInvalid,
     BuiltinFnIdMemcpy,
src/analyze.cpp
@@ -13,6 +13,7 @@
 #include "parseh.hpp"
 #include "config.h"
 #include "ast_render.hpp"
+#include "eval.hpp"
 
 static TypeTableEntry *analyze_expression(CodeGen *g, ImportTableEntry *import, BlockContext *context,
         TypeTableEntry *expected_type, AstNode *node);
@@ -1374,6 +1375,7 @@ static void preview_fn_proto_instance(CodeGen *g, ImportTableEntry *import, AstN
     fn_table_entry->proto_node = proto_node;
     fn_table_entry->fn_def_node = fn_def_node;
     fn_table_entry->is_extern = is_extern;
+    fn_table_entry->is_pure = !is_extern;
 
     get_fully_qualified_decl_name(&fn_table_entry->symbol_name, proto_node, '_');
 
@@ -2443,6 +2445,12 @@ static TypeTableEntry *analyze_slice_expr(CodeGen *g, ImportTableEntry *import,
     return return_type;
 }
 
+static void mark_impure_fn(BlockContext *context) {
+    if (context->fn_entry) {
+        context->fn_entry->is_pure = false;
+    }
+}
+
 static TypeTableEntry *analyze_array_access_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
         AstNode *node)
 {
@@ -2478,6 +2486,7 @@ static TypeTableEntry *analyze_array_access_expr(CodeGen *g, ImportTableEntry *i
 static TypeTableEntry *resolve_expr_const_val_as_void(CodeGen *g, AstNode *node) {
     Expr *expr = get_resolved_expr(node);
     expr->const_val.ok = true;
+    expr->const_val.deep_const = true;
     return g->builtin_types.entry_void;
 }
 
@@ -2485,6 +2494,7 @@ static TypeTableEntry *resolve_expr_const_val_as_type(CodeGen *g, AstNode *node,
     Expr *expr = get_resolved_expr(node);
     expr->const_val.ok = true;
     expr->const_val.data.x_type = type;
+    expr->const_val.deep_const = true;
     return g->builtin_types.entry_type;
 }
 
@@ -2499,6 +2509,7 @@ static TypeTableEntry *resolve_expr_const_val_as_fn(CodeGen *g, AstNode *node, F
     Expr *expr = get_resolved_expr(node);
     expr->const_val.ok = true;
     expr->const_val.data.x_fn = fn;
+    expr->const_val.deep_const = true;
     return fn->type_entry;
 }
 
@@ -2508,6 +2519,7 @@ static TypeTableEntry *resolve_expr_const_val_as_generic_fn(CodeGen *g, AstNode
     Expr *expr = get_resolved_expr(node);
     expr->const_val.ok = true;
     expr->const_val.data.x_type = type_entry;
+    expr->const_val.deep_const = true;
     return type_entry;
 }
 
@@ -2515,6 +2527,7 @@ static TypeTableEntry *resolve_expr_const_val_as_err(CodeGen *g, AstNode *node,
     Expr *expr = get_resolved_expr(node);
     expr->const_val.ok = true;
     expr->const_val.data.x_err.err = err;
+    expr->const_val.deep_const = true;
     return g->builtin_types.entry_pure_error;
 }
 
@@ -2525,6 +2538,7 @@ static TypeTableEntry *resolve_expr_const_val_as_bool(CodeGen *g, AstNode *node,
     expr->const_val.ok = true;
     expr->const_val.depends_on_compile_var = depends_on_compile_var;
     expr->const_val.data.x_bool = value;
+    expr->const_val.deep_const = true;
     return g->builtin_types.entry_bool;
 }
 
@@ -2532,6 +2546,7 @@ static TypeTableEntry *resolve_expr_const_val_as_null(CodeGen *g, AstNode *node,
     Expr *expr = get_resolved_expr(node);
     expr->const_val.ok = true;
     expr->const_val.data.x_maybe = nullptr;
+    expr->const_val.deep_const = true;
     return type;
 }
 
@@ -2542,12 +2557,14 @@ static TypeTableEntry *resolve_expr_const_val_as_non_null(CodeGen *g, AstNode *n
     Expr *expr = get_resolved_expr(node);
     expr->const_val.ok = true;
     expr->const_val.data.x_maybe = other_val;
+    expr->const_val.deep_const = other_val->deep_const;
     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;
+    expr->const_val.deep_const = true;
 
     int len_with_null = buf_len(str) + 1;
     expr->const_val.data.x_ptr.ptr = allocate<ConstExprValue*>(len_with_null);
@@ -2557,12 +2574,14 @@ static TypeTableEntry *resolve_expr_const_val_as_c_string_lit(CodeGen *g, AstNod
     for (int i = 0; i < buf_len(str); i += 1) {
         ConstExprValue *this_char = &all_chars[i];
         this_char->ok = true;
+        this_char->deep_const = true;
         bignum_init_unsigned(&this_char->data.x_bignum, buf_ptr(str)[i]);
         expr->const_val.data.x_ptr.ptr[i] = this_char;
     }
 
     ConstExprValue *null_char = &all_chars[len_with_null - 1];
     null_char->ok = true;
+    null_char->deep_const = true;
     bignum_init_unsigned(&null_char->data.x_bignum, 0);
     expr->const_val.data.x_ptr.ptr[len_with_null - 1] = null_char;
 
@@ -2572,12 +2591,14 @@ static TypeTableEntry *resolve_expr_const_val_as_c_string_lit(CodeGen *g, AstNod
 static TypeTableEntry *resolve_expr_const_val_as_string_lit(CodeGen *g, AstNode *node, Buf *str) {
     Expr *expr = get_resolved_expr(node);
     expr->const_val.ok = true;
+    expr->const_val.deep_const = true;
     expr->const_val.data.x_array.fields = allocate<ConstExprValue*>(buf_len(str));
 
     ConstExprValue *all_chars = allocate<ConstExprValue>(buf_len(str));
     for (int i = 0; i < buf_len(str); i += 1) {
         ConstExprValue *this_char = &all_chars[i];
         this_char->ok = true;
+        this_char->deep_const = true;
         bignum_init_unsigned(&this_char->data.x_bignum, buf_ptr(str)[i]);
         expr->const_val.data.x_array.fields[i] = this_char;
     }
@@ -2590,6 +2611,7 @@ static TypeTableEntry *resolve_expr_const_val_as_unsigned_num_lit(CodeGen *g, As
 {
     Expr *expr = get_resolved_expr(node);
     expr->const_val.ok = true;
+    expr->const_val.deep_const = true;
 
     bignum_init_unsigned(&expr->const_val.data.x_bignum, x);
 
@@ -2601,6 +2623,7 @@ static TypeTableEntry *resolve_expr_const_val_as_float_num_lit(CodeGen *g, AstNo
 {
     Expr *expr = get_resolved_expr(node);
     expr->const_val.ok = true;
+    expr->const_val.deep_const = true;
 
     bignum_init_float(&expr->const_val.data.x_bignum, x);
 
@@ -2616,6 +2639,7 @@ static TypeTableEntry *resolve_expr_const_val_as_bignum_op(CodeGen *g, AstNode *
     ConstExprValue *op2_val = &get_resolved_expr(op2)->const_val;
 
     const_val->ok = true;
+    const_val->deep_const = true;
 
     if (bignum_fn(&const_val->data.x_bignum, &op1_val->data.x_bignum, &op2_val->data.x_bignum)) {
         add_node_error(g, node,
@@ -2684,6 +2708,19 @@ static TypeTableEntry *analyze_decl_ref(CodeGen *g, AstNode *source_node, AstNod
     }
 }
 
+static bool var_is_pure(VariableTableEntry *var, TypeTableEntry *var_type, BlockContext *context) {
+    if (var->block_context->fn_entry == context->fn_entry) {
+        // variable was declared in the current function, so it's OK.
+        return true;
+    }
+    if (!var->is_const) {
+        return false;
+    }
+
+    ConstExprValue *const_val = &get_resolved_expr(var->val_node)->const_val;
+    return const_val->deep_const;
+}
+
 static TypeTableEntry *analyze_symbol_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
         TypeTableEntry *expected_type, AstNode *node, bool pointer_only)
 {
@@ -2700,7 +2737,11 @@ static TypeTableEntry *analyze_symbol_expr(CodeGen *g, ImportTableEntry *import,
 
     VariableTableEntry *var = find_variable(g, context, variable_name);
     if (var) {
-        return analyze_var_ref(g, node, var);
+        TypeTableEntry *var_type = analyze_var_ref(g, node, var);
+        if (!var_is_pure(var, var_type, context)) {
+            mark_impure_fn(context);
+        }
+        return var_type;
     }
 
     AstNode *decl_node = find_decl(context, variable_name);
@@ -2840,71 +2881,6 @@ static TypeTableEntry *analyze_lvalue(CodeGen *g, ImportTableEntry *import, Bloc
     return expected_rhs_type;
 }
 
-static bool eval_bool_bin_op_bool(bool a, BinOpType bin_op, bool b) {
-    if (bin_op == BinOpTypeBoolOr) {
-        return a || b;
-    } else if (bin_op == BinOpTypeBoolAnd) {
-        return a && b;
-    } else {
-        zig_unreachable();
-    }
-}
-
-static bool const_values_equal(ConstExprValue *a, ConstExprValue *b, TypeTableEntry *type_entry) {
-    switch (type_entry->id) {
-        case TypeTableEntryIdEnum:
-            {
-                ConstEnumValue *enum1 = &a->data.x_enum;
-                ConstEnumValue *enum2 = &b->data.x_enum;
-                if (enum1->tag == enum2->tag) {
-                    TypeEnumField *enum_field = &type_entry->data.enumeration.fields[enum1->tag];
-                    if (type_has_bits(enum_field->type_entry)) {
-                        zig_panic("TODO const expr analyze enum special value for equality");
-                    } else {
-                        return true;
-                    }
-                }
-                return false;
-            }
-        case TypeTableEntryIdMetaType:
-            return a->data.x_type == b->data.x_type;
-        case TypeTableEntryIdVoid:
-            return true;
-        case TypeTableEntryIdPureError:
-            return a->data.x_err.err == b->data.x_err.err;
-        case TypeTableEntryIdFn:
-            return a->data.x_fn == b->data.x_fn;
-        case TypeTableEntryIdBool:
-            return a->data.x_bool == b->data.x_bool;
-        case TypeTableEntryIdInt:
-        case TypeTableEntryIdFloat:
-        case TypeTableEntryIdNumLitFloat:
-        case TypeTableEntryIdNumLitInt:
-            return bignum_cmp_eq(&a->data.x_bignum, &b->data.x_bignum);
-        case TypeTableEntryIdPointer:
-            zig_panic("TODO");
-        case TypeTableEntryIdArray:
-            zig_panic("TODO");
-        case TypeTableEntryIdStruct:
-            zig_panic("TODO");
-        case TypeTableEntryIdUndefLit:
-            zig_panic("TODO");
-        case TypeTableEntryIdMaybe:
-            zig_panic("TODO");
-        case TypeTableEntryIdErrorUnion:
-            zig_panic("TODO");
-        case TypeTableEntryIdTypeDecl:
-            zig_panic("TODO");
-        case TypeTableEntryIdNamespace:
-            zig_panic("TODO");
-        case TypeTableEntryIdGenericFn:
-        case TypeTableEntryIdInvalid:
-        case TypeTableEntryIdUnreachable:
-            zig_unreachable();
-    }
-    zig_unreachable();
-}
-
 static TypeTableEntry *analyze_bool_bin_op_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
         AstNode *node)
 {
@@ -2944,39 +2920,11 @@ static TypeTableEntry *analyze_bool_bin_op_expr(CodeGen *g, ImportTableEntry *im
         return g->builtin_types.entry_bool;
     }
 
-    bool answer;
-    if (type_can_gt_lt_cmp) {
-        bool (*bignum_cmp)(BigNum *, BigNum *);
-        if (bin_op_type == BinOpTypeCmpEq) {
-            bignum_cmp = bignum_cmp_eq;
-        } else if (bin_op_type == BinOpTypeCmpNotEq) {
-            bignum_cmp = bignum_cmp_neq;
-        } else if (bin_op_type == BinOpTypeCmpLessThan) {
-            bignum_cmp = bignum_cmp_lt;
-        } else if (bin_op_type == BinOpTypeCmpGreaterThan) {
-            bignum_cmp = bignum_cmp_gt;
-        } else if (bin_op_type == BinOpTypeCmpLessOrEq) {
-            bignum_cmp = bignum_cmp_lte;
-        } else if (bin_op_type == BinOpTypeCmpGreaterOrEq) {
-            bignum_cmp = bignum_cmp_gte;
-        } else {
-            zig_unreachable();
-        }
 
-        answer = bignum_cmp(&op1_val->data.x_bignum, &op2_val->data.x_bignum);
-    } else {
-        bool are_equal = const_values_equal(op1_val, op2_val, resolved_type);
-        if (bin_op_type == BinOpTypeCmpEq) {
-            answer = are_equal;
-        } else if (bin_op_type == BinOpTypeCmpNotEq) {
-            answer = !are_equal;
-        } else {
-            zig_unreachable();
-        }
-    }
+    ConstExprValue *out_val = &get_resolved_expr(node)->const_val;
+    eval_const_expr_bin_op(op1_val, op1_type, bin_op_type, op2_val, op2_type, out_val);
+    return g->builtin_types.entry_bool;
 
-    bool depends_on_compile_var = op1_val->depends_on_compile_var || op2_val->depends_on_compile_var;
-    return resolve_expr_const_val_as_bool(g, node, answer, depends_on_compile_var);
 }
 
 static TypeTableEntry *analyze_logic_bin_op_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
@@ -3002,9 +2950,9 @@ static TypeTableEntry *analyze_logic_bin_op_expr(CodeGen *g, ImportTableEntry *i
         return g->builtin_types.entry_bool;
     }
 
-    bool answer = eval_bool_bin_op_bool(op1_val->data.x_bool, bin_op_type, op2_val->data.x_bool);
-    bool depends_on_compile_var = op1_val->depends_on_compile_var || op2_val->depends_on_compile_var;
-    return resolve_expr_const_val_as_bool(g, node, answer, depends_on_compile_var);
+    ConstExprValue *out_val = &get_resolved_expr(node)->const_val;
+    eval_const_expr_bin_op(op1_val, op1_type, bin_op_type, op2_val, op2_type, out_val);
+    return g->builtin_types.entry_bool;
 }
 
 static TypeTableEntry *analyze_bin_op_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
@@ -3041,7 +2989,7 @@ static TypeTableEntry *analyze_bin_op_expr(CodeGen *g, ImportTableEntry *import,
                 }
 
                 analyze_expression(g, import, context, expected_rhs_type, node->data.bin_op_expr.op2);
-                return g->builtin_types.entry_void;
+                return resolve_expr_const_val_as_void(g, node);
             }
         case BinOpTypeBoolOr:
         case BinOpTypeBoolAnd:
@@ -3720,6 +3668,10 @@ static TypeTableEntry *analyze_if_bool_expr(CodeGen *g, ImportTableEntry *import
     }
 
     ConstExprValue *cond_val = &get_resolved_expr(*cond)->const_val;
+    if (cond_val->undef) {
+        add_node_error(g, first_executing_node(*cond), buf_sprintf("branch on undefined value"));
+        return cond_type;
+    }
     if (cond_val->ok && !cond_val->depends_on_compile_var) {
         const char *str_val = cond_val->data.x_bool ? "true" : "false";
         add_node_error(g, first_executing_node(*cond),
@@ -3849,92 +3801,18 @@ static TypeTableEntry *analyze_min_max_value(CodeGen *g, ImportTableEntry *impor
     }
 }
 
-static void eval_const_expr_implicit_cast(CodeGen *g, AstNode *node, AstNode *expr_node) {
-    assert(node->type == NodeTypeFnCallExpr);
-    ConstExprValue *other_val = &get_resolved_expr(expr_node)->const_val;
-    ConstExprValue *const_val = &get_resolved_expr(node)->const_val;
-    if (!other_val->ok) {
-        return;
-    }
-    const_val->depends_on_compile_var = other_val->depends_on_compile_var;
-    const_val->undef = other_val->undef;
-
-    assert(other_val != const_val);
-    switch (node->data.fn_call_expr.cast_op) {
-        case CastOpNoCast:
-            zig_unreachable();
-        case CastOpNoop:
-        case CastOpWidenOrShorten:
-        case CastOpPointerReinterpret:
-            *const_val = *other_val;
-            break;
-        case CastOpPtrToInt:
-        case CastOpIntToPtr:
-            // can't do it
-            break;
-        case CastOpToUnknownSizeArray:
-            {
-                TypeTableEntry *other_type = get_resolved_expr(expr_node)->type_entry;
-                assert(other_type->id == TypeTableEntryIdArray);
-
-                ConstExprValue *all_fields = allocate<ConstExprValue>(2);
-                ConstExprValue *ptr_field = &all_fields[0];
-                ConstExprValue *len_field = &all_fields[1];
-
-                const_val->data.x_struct.fields = allocate<ConstExprValue*>(2);
-                const_val->data.x_struct.fields[0] = ptr_field;
-                const_val->data.x_struct.fields[1] = len_field;
-
-                ptr_field->ok = true;
-                ptr_field->data.x_ptr.ptr = other_val->data.x_array.fields;
-                ptr_field->data.x_ptr.len = other_type->data.array.len;
-
-                len_field->ok = true;
-                bignum_init_unsigned(&len_field->data.x_bignum, other_type->data.array.len);
-
-                const_val->ok = true;
-                break;
-            }
-        case CastOpMaybeWrap:
-            const_val->data.x_maybe = other_val;
-            const_val->ok = true;
-            break;
-        case CastOpErrorWrap:
-            const_val->data.x_err.err = nullptr;
-            const_val->data.x_err.payload = other_val;
-            const_val->ok = true;
-            break;
-        case CastOpPureErrorWrap:
-            const_val->data.x_err.err = other_val->data.x_err.err;
-            const_val->ok = true;
-            break;
-        case CastOpErrToInt:
-            {
-                uint64_t value = other_val->data.x_err.err ? other_val->data.x_err.err->value : 0;
-                bignum_init_unsigned(&const_val->data.x_bignum, value);
-                const_val->ok = true;
-                break;
-            }
-        case CastOpIntToFloat:
-            bignum_cast_to_float(&const_val->data.x_bignum, &other_val->data.x_bignum);
-            const_val->ok = true;
-            break;
-        case CastOpFloatToInt:
-            bignum_cast_to_int(&const_val->data.x_bignum, &other_val->data.x_bignum);
-            const_val->ok = true;
-            break;
-        case CastOpBoolToInt:
-            bignum_init_unsigned(&const_val->data.x_bignum, other_val->data.x_bool ? 1 : 0);
-            const_val->ok = true;
-            break;
-    }
-}
-
 static TypeTableEntry *resolve_cast(CodeGen *g, BlockContext *context, AstNode *node,
         AstNode *expr_node, TypeTableEntry *wanted_type, CastOp op, bool need_alloca)
 {
     node->data.fn_call_expr.cast_op = op;
-    eval_const_expr_implicit_cast(g, node, expr_node);
+
+    ConstExprValue *other_val = &get_resolved_expr(expr_node)->const_val;
+    TypeTableEntry *other_type = get_resolved_expr(expr_node)->type_entry;
+    ConstExprValue *const_val = &get_resolved_expr(node)->const_val;
+    if (other_val->ok) {
+        eval_const_expr_implicit_cast(node->data.fn_call_expr.cast_op, other_val, other_type, const_val);
+    }
+
     if (need_alloca) {
         if (context->fn_entry) {
             context->fn_entry->cast_alloca_list.append(node);
@@ -4360,6 +4238,8 @@ static TypeTableEntry *analyze_builtin_fn_call_expr(CodeGen *g, ImportTableEntry
             }
         case BuiltinFnIdMemcpy:
             {
+                mark_impure_fn(context);
+
                 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);
@@ -4398,6 +4278,8 @@ static TypeTableEntry *analyze_builtin_fn_call_expr(CodeGen *g, ImportTableEntry
             }
         case BuiltinFnIdMemset:
             {
+                mark_impure_fn(context);
+
                 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);
@@ -4630,13 +4512,15 @@ static TypeTableEntry *analyze_builtin_fn_call_expr(CodeGen *g, ImportTableEntry
         case BuiltinFnIdErrName:
             return analyze_err_name(g, import, context, node);
         case BuiltinFnIdBreakpoint:
+            mark_impure_fn(context);
             return g->builtin_types.entry_void;
     }
     zig_unreachable();
 }
 
 static TypeTableEntry *analyze_fn_call_ptr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
-        TypeTableEntry *expected_type, AstNode *node, TypeTableEntry *fn_type, TypeTableEntry *struct_type)
+        TypeTableEntry *expected_type, AstNode *node, TypeTableEntry *fn_type,
+        AstNode *struct_node)
 {
     assert(node->type == NodeTypeFnCallExpr);
 
@@ -4648,7 +4532,7 @@ static TypeTableEntry *analyze_fn_call_ptr(CodeGen *g, ImportTableEntry *import,
     int src_param_count = fn_type->data.fn.fn_type_id.param_count;
     int actual_param_count = node->data.fn_call_expr.params.length;
 
-    if (struct_type) {
+    if (struct_node) {
         actual_param_count += 1;
     }
 
@@ -4662,17 +4546,31 @@ static TypeTableEntry *analyze_fn_call_ptr(CodeGen *g, ImportTableEntry *import,
                 buf_sprintf("expected %d arguments, got %d", src_param_count, actual_param_count));
     }
 
+    bool all_args_const_expr = true;
+
+    if (struct_node) {
+        ConstExprValue *struct_const_val = &get_resolved_expr(struct_node)->const_val;
+        if (!struct_const_val->ok) {
+            all_args_const_expr = false;
+        }
+    }
+
     // 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.
     for (int i = 0; i < node->data.fn_call_expr.params.length; i += 1) {
-        AstNode *child = node->data.fn_call_expr.params.at(i);
+        AstNode **child = &node->data.fn_call_expr.params.at(i);
         // determine the expected type for each parameter
         TypeTableEntry *expected_param_type = nullptr;
-        int fn_proto_i = i + (struct_type ? 1 : 0);
+        int fn_proto_i = i + (struct_node ? 1 : 0);
         if (fn_proto_i < src_param_count) {
             expected_param_type = fn_type->data.fn.fn_type_id.param_info[fn_proto_i].type;
         }
-        analyze_expression(g, import, context, expected_param_type, child);
+        analyze_expression(g, import, context, expected_param_type, *child);
+
+        ConstExprValue *const_arg_val = &get_resolved_expr(*child)->const_val;
+        if (!const_arg_val->ok) {
+            all_args_const_expr = false;
+        }
     }
 
     TypeTableEntry *return_type = fn_type->data.fn.fn_type_id.return_type;
@@ -4681,6 +4579,30 @@ static TypeTableEntry *analyze_fn_call_ptr(CodeGen *g, ImportTableEntry *import,
         return return_type;
     }
 
+    FnTableEntry *fn_table_entry = node->data.fn_call_expr.fn_entry;
+    if (fn_table_entry && fn_table_entry->is_pure && all_args_const_expr) {
+        if (fn_table_entry->anal_state == FnAnalStateReady) {
+            analyze_fn_body(g, fn_table_entry);
+        } else if (fn_table_entry->anal_state == FnAnalStateProbing) {
+            mark_impure_fn(context);
+        }
+        if (fn_table_entry->is_pure) {
+            if (fn_table_entry->anal_state == FnAnalStateComplete) {
+                ConstExprValue *result_val = &get_resolved_expr(node)->const_val;
+                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;
+            } else if (fn_table_entry->anal_state == FnAnalStateSkipped) {
+                return g->builtin_types.entry_invalid;
+            }
+        } else {
+            // calling an impure fn is impure
+            mark_impure_fn(context);
+        }
+    }
+
     if (handle_is_ptr(return_type)) {
         context->fn_entry->cast_alloca_list.append(node);
     }
@@ -4689,13 +4611,13 @@ static TypeTableEntry *analyze_fn_call_ptr(CodeGen *g, ImportTableEntry *import,
 }
 
 static TypeTableEntry *analyze_fn_call_raw(CodeGen *g, ImportTableEntry *import, BlockContext *context,
-        TypeTableEntry *expected_type, AstNode *node, FnTableEntry *fn_table_entry, TypeTableEntry *struct_type)
+        TypeTableEntry *expected_type, AstNode *node, FnTableEntry *fn_table_entry, AstNode *struct_node)
 {
     assert(node->type == NodeTypeFnCallExpr);
 
     node->data.fn_call_expr.fn_entry = fn_table_entry;
 
-    return analyze_fn_call_ptr(g, import, context, expected_type, node, fn_table_entry->type_entry, struct_type);
+    return analyze_fn_call_ptr(g, import, context, expected_type, node, fn_table_entry->type_entry, struct_node);
 }
 
 static TypeTableEntry *analyze_generic_fn_call(CodeGen *g, ImportTableEntry *import, BlockContext *parent_context,
@@ -4861,17 +4783,17 @@ static TypeTableEntry *analyze_fn_call_expr(CodeGen *g, ImportTableEntry *import
                 return analyze_cast_expr(g, import, context, node);
             }
         } else if (invoke_type_entry->id == TypeTableEntryIdFn) {
-            TypeTableEntry *bare_struct_type;
+            AstNode *struct_node;
             if (fn_ref_expr->type == NodeTypeFieldAccessExpr &&
                 fn_ref_expr->data.field_access_expr.is_member_fn)
             {
-                bare_struct_type = fn_ref_expr->data.field_access_expr.bare_struct_type;
+                struct_node = fn_ref_expr;
             } else {
-                bare_struct_type = nullptr;
+                struct_node = nullptr;
             }
 
             return analyze_fn_call_raw(g, import, context, expected_type, node,
-                    const_val->data.x_fn, bare_struct_type);
+                    const_val->data.x_fn, struct_node);
         } else if (invoke_type_entry->id == TypeTableEntryIdGenericFn) {
             return analyze_generic_fn_call(g, import, context, expected_type, node, const_val->data.x_type);
         } else {
@@ -5421,6 +5343,8 @@ static TypeTableEntry *analyze_block_expr(CodeGen *g, ImportTableEntry *import,
 static TypeTableEntry *analyze_asm_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
         TypeTableEntry *expected_type, AstNode *node)
 {
+    mark_impure_fn(context);
+
     node->data.asm_expr.return_count = 0;
     TypeTableEntry *return_type = g->builtin_types.entry_void;
     for (int i = 0; i < node->data.asm_expr.output_list.length; i += 1) {
@@ -5641,8 +5565,10 @@ static void analyze_fn_body(CodeGen *g, FnTableEntry *fn_table_entry) {
     if (fn_proto_node->data.fn_proto.skip) {
         // we detected an error with this function definition which prevents us
         // from further analyzing it.
+        fn_table_entry->anal_state = FnAnalStateSkipped;
         return;
     }
+    fn_table_entry->anal_state = FnAnalStateProbing;
 
     BlockContext *context = node->data.fn_def.block_context;
 
@@ -5697,6 +5623,8 @@ static void analyze_fn_body(CodeGen *g, FnTableEntry *fn_table_entry) {
                         buf_ptr(&label->decl_node->data.label.name)));
         }
     }
+
+    fn_table_entry->anal_state = FnAnalStateComplete;
 }
 
 static void add_top_level_decl(CodeGen *g, ImportTableEntry *import, BlockContext *block_context,
@@ -6005,7 +5933,9 @@ void semantic_analyze(CodeGen *g) {
 
     for (int i = 0; i < g->fn_defs.length; i += 1) {
         FnTableEntry *fn_entry = g->fn_defs.at(i);
-        analyze_fn_body(g, fn_entry);
+        if (fn_entry->anal_state == FnAnalStateReady) {
+            analyze_fn_body(g, fn_entry);
+        }
     }
 }
 
src/eval.cpp
@@ -0,0 +1,607 @@
+#include "eval.hpp"
+#include "analyze.hpp"
+
+static bool eval_fn_args(EvalFnRoot *efr, FnTableEntry *fn, ConstExprValue *args, ConstExprValue *out_val);
+
+bool const_values_equal(ConstExprValue *a, ConstExprValue *b, TypeTableEntry *type_entry) {
+    switch (type_entry->id) {
+        case TypeTableEntryIdEnum:
+            {
+                ConstEnumValue *enum1 = &a->data.x_enum;
+                ConstEnumValue *enum2 = &b->data.x_enum;
+                if (enum1->tag == enum2->tag) {
+                    TypeEnumField *enum_field = &type_entry->data.enumeration.fields[enum1->tag];
+                    if (type_has_bits(enum_field->type_entry)) {
+                        zig_panic("TODO const expr analyze enum special value for equality");
+                    } else {
+                        return true;
+                    }
+                }
+                return false;
+            }
+        case TypeTableEntryIdMetaType:
+            return a->data.x_type == b->data.x_type;
+        case TypeTableEntryIdVoid:
+            return true;
+        case TypeTableEntryIdPureError:
+            return a->data.x_err.err == b->data.x_err.err;
+        case TypeTableEntryIdFn:
+            return a->data.x_fn == b->data.x_fn;
+        case TypeTableEntryIdBool:
+            return a->data.x_bool == b->data.x_bool;
+        case TypeTableEntryIdInt:
+        case TypeTableEntryIdFloat:
+        case TypeTableEntryIdNumLitFloat:
+        case TypeTableEntryIdNumLitInt:
+            return bignum_cmp_eq(&a->data.x_bignum, &b->data.x_bignum);
+        case TypeTableEntryIdPointer:
+            zig_panic("TODO");
+        case TypeTableEntryIdArray:
+            zig_panic("TODO");
+        case TypeTableEntryIdStruct:
+            zig_panic("TODO");
+        case TypeTableEntryIdUndefLit:
+            zig_panic("TODO");
+        case TypeTableEntryIdMaybe:
+            zig_panic("TODO");
+        case TypeTableEntryIdErrorUnion:
+            zig_panic("TODO");
+        case TypeTableEntryIdTypeDecl:
+            zig_panic("TODO");
+        case TypeTableEntryIdNamespace:
+            zig_panic("TODO");
+        case TypeTableEntryIdGenericFn:
+        case TypeTableEntryIdInvalid:
+        case TypeTableEntryIdUnreachable:
+            zig_unreachable();
+    }
+    zig_unreachable();
+}
+
+
+static bool eval_expr(EvalFn *ef, AstNode *node, ConstExprValue *out);
+
+static bool eval_block(EvalFn *ef, AstNode *node, ConstExprValue *out) {
+    assert(node->type == NodeTypeBlock);
+
+    EvalScope *my_scope = allocate<EvalScope>(1);
+    my_scope->block_context = node->block_context;
+    ef->scope_stack.append(my_scope);
+
+    for (int i = 0; i < node->data.block.statements.length; i += 1) {
+        AstNode *child = node->data.block.statements.at(i);
+        if (eval_expr(ef, child, out)) return true;
+    }
+
+    ef->scope_stack.pop();
+
+    return false;
+}
+
+static bool eval_return(EvalFn *ef, AstNode *node, ConstExprValue *out) {
+    assert(node->type == NodeTypeReturnExpr);
+
+    eval_expr(ef, node->data.return_expr.expr, ef->return_expr);
+    return true;
+}
+
+static bool eval_bool_bin_op_bool(bool a, BinOpType bin_op, bool b) {
+    if (bin_op == BinOpTypeBoolOr) {
+        return a || b;
+    } else if (bin_op == BinOpTypeBoolAnd) {
+        return a && b;
+    } else {
+        zig_unreachable();
+    }
+}
+
+static void eval_const_expr_bin_op_bignum(ConstExprValue *op1_val, ConstExprValue *op2_val,
+        ConstExprValue *out_val, bool (*bignum_fn)(BigNum *, BigNum *, BigNum *))
+{
+    bool overflow = bignum_fn(&out_val->data.x_bignum, &op1_val->data.x_bignum, &op2_val->data.x_bignum);
+    assert(!overflow);
+    out_val->ok = true;
+    out_val->depends_on_compile_var = op1_val->depends_on_compile_var || op2_val->depends_on_compile_var;
+}
+
+void eval_const_expr_bin_op(ConstExprValue *op1_val, TypeTableEntry *op1_type,
+        BinOpType bin_op, ConstExprValue *op2_val, TypeTableEntry *op2_type, ConstExprValue *out_val)
+{
+    assert(op1_val->ok);
+    assert(op2_val->ok);
+    assert(op1_type == op2_type);
+
+    switch (bin_op) {
+        case BinOpTypeAssign:
+        case BinOpTypeAssignTimes:
+        case BinOpTypeAssignDiv:
+        case BinOpTypeAssignMod:
+        case BinOpTypeAssignPlus:
+        case BinOpTypeAssignMinus:
+        case BinOpTypeAssignBitShiftLeft:
+        case BinOpTypeAssignBitShiftRight:
+        case BinOpTypeAssignBitAnd:
+        case BinOpTypeAssignBitXor:
+        case BinOpTypeAssignBitOr:
+        case BinOpTypeAssignBoolAnd:
+        case BinOpTypeAssignBoolOr:
+            out_val->ok = true;
+            return;
+        case BinOpTypeBoolOr:
+        case BinOpTypeBoolAnd:
+            assert(op1_type->id == TypeTableEntryIdBool);
+            assert(op2_type->id == TypeTableEntryIdBool);
+            out_val->data.x_bool = eval_bool_bin_op_bool(op1_val->data.x_bool, bin_op, op2_val->data.x_bool);
+            out_val->ok = true;
+            out_val->depends_on_compile_var = op1_val->depends_on_compile_var || op2_val->depends_on_compile_var;
+            return;
+        case BinOpTypeCmpEq:
+        case BinOpTypeCmpNotEq:
+        case BinOpTypeCmpLessThan:
+        case BinOpTypeCmpGreaterThan:
+        case BinOpTypeCmpLessOrEq:
+        case BinOpTypeCmpGreaterOrEq:
+            {
+                bool type_can_gt_lt_cmp = (op1_type->id == TypeTableEntryIdNumLitFloat ||
+                        op1_type->id == TypeTableEntryIdNumLitInt ||
+                        op1_type->id == TypeTableEntryIdFloat ||
+                        op1_type->id == TypeTableEntryIdInt);
+                bool answer;
+                if (type_can_gt_lt_cmp) {
+                    bool (*bignum_cmp)(BigNum *, BigNum *);
+                    if (bin_op == BinOpTypeCmpEq) {
+                        bignum_cmp = bignum_cmp_eq;
+                    } else if (bin_op == BinOpTypeCmpNotEq) {
+                        bignum_cmp = bignum_cmp_neq;
+                    } else if (bin_op == BinOpTypeCmpLessThan) {
+                        bignum_cmp = bignum_cmp_lt;
+                    } else if (bin_op == BinOpTypeCmpGreaterThan) {
+                        bignum_cmp = bignum_cmp_gt;
+                    } else if (bin_op == BinOpTypeCmpLessOrEq) {
+                        bignum_cmp = bignum_cmp_lte;
+                    } else if (bin_op == BinOpTypeCmpGreaterOrEq) {
+                        bignum_cmp = bignum_cmp_gte;
+                    } else {
+                        zig_unreachable();
+                    }
+
+                    answer = bignum_cmp(&op1_val->data.x_bignum, &op2_val->data.x_bignum);
+                } else {
+                    bool are_equal = const_values_equal(op1_val, op2_val, op1_type);
+                    if (bin_op == BinOpTypeCmpEq) {
+                        answer = are_equal;
+                    } else if (bin_op == BinOpTypeCmpNotEq) {
+                        answer = !are_equal;
+                    } else {
+                        zig_unreachable();
+                    }
+                }
+
+                out_val->depends_on_compile_var =
+                    op1_val->depends_on_compile_var || op2_val->depends_on_compile_var;
+                out_val->data.x_bool = answer;
+                out_val->ok = true;
+                return;
+            }
+        case BinOpTypeAdd:
+            return eval_const_expr_bin_op_bignum(op1_val, op2_val, out_val, bignum_add);
+        case BinOpTypeBinOr:
+            return eval_const_expr_bin_op_bignum(op1_val, op2_val, out_val, bignum_or);
+        case BinOpTypeBinXor:
+            return eval_const_expr_bin_op_bignum(op1_val, op2_val, out_val, bignum_xor);
+        case BinOpTypeBinAnd:
+            return eval_const_expr_bin_op_bignum(op1_val, op2_val, out_val, bignum_and);
+        case BinOpTypeBitShiftLeft:
+            return eval_const_expr_bin_op_bignum(op1_val, op2_val, out_val, bignum_shl);
+        case BinOpTypeBitShiftRight:
+            return eval_const_expr_bin_op_bignum(op1_val, op2_val, out_val, bignum_shr);
+        case BinOpTypeSub:
+            return eval_const_expr_bin_op_bignum(op1_val, op2_val, out_val, bignum_sub);
+        case BinOpTypeMult:
+            return eval_const_expr_bin_op_bignum(op1_val, op2_val, out_val, bignum_mul);
+        case BinOpTypeDiv:
+            {
+                bool is_int = false;
+                bool is_float = false;
+                if (op1_type->id == TypeTableEntryIdInt ||
+                    op1_type->id == TypeTableEntryIdNumLitInt)
+                {
+                    is_int = true;
+                } else if (op1_type->id == TypeTableEntryIdFloat ||
+                           op1_type->id == TypeTableEntryIdNumLitFloat)
+                {
+                    is_float = true;
+                }
+                if ((is_int && op2_val->data.x_bignum.data.x_uint == 0) ||
+                    (is_float && op2_val->data.x_bignum.data.x_float == 0.0))
+                {
+                    zig_panic("TODO handle errors in eval");
+                } else {
+                    return eval_const_expr_bin_op_bignum(op1_val, op2_val, out_val, bignum_div);
+                }
+            }
+        case BinOpTypeMod:
+            return eval_const_expr_bin_op_bignum(op1_val, op2_val, out_val, bignum_mod);
+        case BinOpTypeUnwrapMaybe:
+            zig_panic("TODO");
+        case BinOpTypeStrCat:
+            zig_panic("TODO");
+        case BinOpTypeInvalid:
+            zig_unreachable();
+    }
+    zig_unreachable();
+}
+
+static bool eval_bin_op_expr(EvalFn *ef, AstNode *node, ConstExprValue *out_val) {
+    assert(node->type == NodeTypeBinOpExpr);
+
+    AstNode *op1 = node->data.bin_op_expr.op1;
+    AstNode *op2 = node->data.bin_op_expr.op2;
+
+    TypeTableEntry *op1_type = get_resolved_expr(op1)->type_entry;
+    TypeTableEntry *op2_type = get_resolved_expr(op2)->type_entry;
+
+    ConstExprValue op1_val = {0};
+    if (eval_expr(ef, op1, &op1_val)) return true;
+
+    ConstExprValue op2_val = {0};
+    if (eval_expr(ef, op2, &op2_val)) return true;
+
+    BinOpType bin_op = node->data.bin_op_expr.bin_op;
+
+    eval_const_expr_bin_op(&op1_val, op1_type, bin_op, &op2_val, op2_type, out_val);
+
+    return false;
+}
+
+static EvalVar *find_var(EvalFn *ef, Buf *name) {
+    int scope_index = ef->scope_stack.length - 1;
+    while (scope_index >= 0) {
+        EvalScope *scope = ef->scope_stack.at(scope_index);
+        for (int var_i = 0; var_i < scope->vars.length; var_i += 1) {
+            EvalVar *var = &scope->vars.at(var_i);
+            if (buf_eql_buf(var->name, name)) {
+                return var;
+            }
+        }
+        scope_index -= 1;
+    }
+
+    return nullptr;
+}
+
+static bool eval_symbol_expr(EvalFn *ef, AstNode *node, ConstExprValue *out_val) {
+    assert(node->type == NodeTypeSymbol);
+
+    Buf *name = &node->data.symbol_expr.symbol;
+    EvalVar *var = find_var(ef, name);
+
+    *out_val = var->value;
+
+    return false;
+}
+
+static TypeTableEntry *resolve_expr_type(AstNode *node) {
+    Expr *expr = get_resolved_expr(node);
+    TypeTableEntry *type_entry = expr->type_entry;
+    assert(type_entry->id == TypeTableEntryIdMetaType);
+    ConstExprValue *const_val = &expr->const_val;
+    assert(const_val->ok);
+    return const_val->data.x_type;
+}
+
+static bool eval_container_init_expr(EvalFn *ef, AstNode *node, ConstExprValue *out_val) {
+    assert(node->type == NodeTypeContainerInitExpr);
+
+    AstNodeContainerInitExpr *container_init_expr = &node->data.container_init_expr;
+    ContainerInitKind kind = container_init_expr->kind;
+    TypeTableEntry *container_type = resolve_expr_type(container_init_expr->type);
+    out_val->ok = true;
+
+    if (container_type->id == TypeTableEntryIdStruct &&
+        !container_type->data.structure.is_unknown_size_array &&
+        kind == ContainerInitKindStruct)
+    {
+        int expr_field_count = container_init_expr->entries.length;
+        int actual_field_count = container_type->data.structure.src_field_count;
+        assert(expr_field_count == actual_field_count);
+
+        out_val->data.x_struct.fields = allocate<ConstExprValue*>(actual_field_count);
+
+        for (int i = 0; i < expr_field_count; i += 1) {
+            AstNode *val_field_node = container_init_expr->entries.at(i);
+            assert(val_field_node->type == NodeTypeStructValueField);
+
+            TypeStructField *type_field = val_field_node->data.struct_val_field.type_struct_field;
+            int field_index = type_field->src_index;
+
+            ConstExprValue src_field_val = {0};
+            if (eval_expr(ef, val_field_node->data.struct_val_field.expr, &src_field_val)) return true;
+
+            ConstExprValue *dest_field_val = allocate<ConstExprValue>(1);
+            *dest_field_val = src_field_val;
+
+            out_val->data.x_struct.fields[field_index] = dest_field_val;
+            out_val->depends_on_compile_var = out_val->depends_on_compile_var ||
+                src_field_val.depends_on_compile_var;
+        }
+    } else if (container_type->id == TypeTableEntryIdVoid) {
+        return false;
+    } else {
+        zig_panic("TODO");
+    }
+
+
+    return false;
+}
+
+static bool eval_if_bool_expr(EvalFn *ef, AstNode *node, ConstExprValue *out_val) {
+    assert(node->type == NodeTypeIfBoolExpr);
+
+    ConstExprValue cond_val = {0};
+    if (eval_expr(ef, node->data.if_bool_expr.condition, &cond_val)) return true;
+
+    AstNode *exec_node = cond_val.data.x_bool ?
+        node->data.if_bool_expr.then_block : node->data.if_bool_expr.else_node;
+
+    if (exec_node) {
+        if (eval_expr(ef, exec_node, out_val)) return true;
+    }
+    out_val->ok = true;
+    return false;
+}
+
+void eval_const_expr_implicit_cast(CastOp cast_op,
+        ConstExprValue *other_val, TypeTableEntry *other_type,
+        ConstExprValue *const_val)
+{
+    const_val->depends_on_compile_var = other_val->depends_on_compile_var;
+    const_val->undef = other_val->undef;
+
+    assert(other_val != const_val);
+    switch (cast_op) {
+        case CastOpNoCast:
+            zig_unreachable();
+        case CastOpNoop:
+        case CastOpWidenOrShorten:
+        case CastOpPointerReinterpret:
+            *const_val = *other_val;
+            break;
+        case CastOpPtrToInt:
+        case CastOpIntToPtr:
+            // can't do it
+            break;
+        case CastOpToUnknownSizeArray:
+            {
+                assert(other_type->id == TypeTableEntryIdArray);
+
+                ConstExprValue *all_fields = allocate<ConstExprValue>(2);
+                ConstExprValue *ptr_field = &all_fields[0];
+                ConstExprValue *len_field = &all_fields[1];
+
+                const_val->data.x_struct.fields = allocate<ConstExprValue*>(2);
+                const_val->data.x_struct.fields[0] = ptr_field;
+                const_val->data.x_struct.fields[1] = len_field;
+
+                ptr_field->ok = true;
+                ptr_field->data.x_ptr.ptr = other_val->data.x_array.fields;
+                ptr_field->data.x_ptr.len = other_type->data.array.len;
+
+                len_field->ok = true;
+                bignum_init_unsigned(&len_field->data.x_bignum, other_type->data.array.len);
+
+                const_val->ok = true;
+                break;
+            }
+        case CastOpMaybeWrap:
+            const_val->data.x_maybe = other_val;
+            const_val->ok = true;
+            break;
+        case CastOpErrorWrap:
+            const_val->data.x_err.err = nullptr;
+            const_val->data.x_err.payload = other_val;
+            const_val->ok = true;
+            break;
+        case CastOpPureErrorWrap:
+            const_val->data.x_err.err = other_val->data.x_err.err;
+            const_val->ok = true;
+            break;
+        case CastOpErrToInt:
+            {
+                uint64_t value = other_val->data.x_err.err ? other_val->data.x_err.err->value : 0;
+                bignum_init_unsigned(&const_val->data.x_bignum, value);
+                const_val->ok = true;
+                break;
+            }
+        case CastOpIntToFloat:
+            bignum_cast_to_float(&const_val->data.x_bignum, &other_val->data.x_bignum);
+            const_val->ok = true;
+            break;
+        case CastOpFloatToInt:
+            bignum_cast_to_int(&const_val->data.x_bignum, &other_val->data.x_bignum);
+            const_val->ok = true;
+            break;
+        case CastOpBoolToInt:
+            bignum_init_unsigned(&const_val->data.x_bignum, other_val->data.x_bool ? 1 : 0);
+            const_val->ok = true;
+            break;
+    }
+}
+
+static bool eval_fn_call_expr(EvalFn *ef, AstNode *node, ConstExprValue *out_val) {
+    assert(node->type == NodeTypeFnCallExpr);
+
+    CastOp cast_op = node->data.fn_call_expr.cast_op;
+    if (node->data.fn_call_expr.is_builtin) {
+        zig_panic("TODO");
+    } else if (cast_op != CastOpNoCast) {
+        AstNode *expr_node = node->data.fn_call_expr.params.at(0);
+        Expr *expr = get_resolved_expr(expr_node);
+        eval_const_expr_implicit_cast(cast_op, &expr->const_val, expr->type_entry, out_val);
+        return false;
+    }
+
+    AstNode *fn_ref_expr = node->data.fn_call_expr.fn_ref_expr;
+    if (node->data.fn_call_expr.enum_type) {
+        zig_panic("TODO");
+    }
+
+    FnTableEntry *fn_table_entry = node->data.fn_call_expr.fn_entry;
+
+    if (fn_ref_expr->type == NodeTypeFieldAccessExpr &&
+        fn_ref_expr->data.field_access_expr.is_member_fn)
+    {
+        zig_panic("TODO");
+    }
+
+    if (!fn_table_entry) {
+        zig_panic("TODO");
+    }
+
+    int param_count = node->data.fn_call_expr.params.length;
+    ConstExprValue *args = allocate<ConstExprValue>(param_count);
+    for (int i = 0; i < param_count; i += 1) {
+        AstNode *param_expr_node = node->data.fn_call_expr.params.at(i);
+        ConstExprValue *param_val = &args[i];
+        if (eval_expr(ef, param_expr_node, param_val)) return true;
+    }
+
+    ef->root->branches_used += 1;
+
+    eval_fn_args(ef->root, fn_table_entry, args, out_val);
+    return false;
+}
+
+static bool eval_expr(EvalFn *ef, AstNode *node, ConstExprValue *out) {
+    if (ef->root->branches_used > ef->root->branch_quota) {
+        ef->root->exceeded_quota_node = node;
+        return true;
+    }
+    switch (node->type) {
+        case NodeTypeBlock:
+            return eval_block(ef, node, out);
+        case NodeTypeReturnExpr:
+            return eval_return(ef, node, out);
+        case NodeTypeBinOpExpr:
+            return eval_bin_op_expr(ef, node, out);
+        case NodeTypeSymbol:
+            return eval_symbol_expr(ef, node, out);
+        case NodeTypeContainerInitExpr:
+            return eval_container_init_expr(ef, node, out);
+        case NodeTypeIfBoolExpr:
+            return eval_if_bool_expr(ef, node, out);
+        case NodeTypeFnCallExpr:
+            return eval_fn_call_expr(ef, node, out);
+        case NodeTypeRoot:
+        case NodeTypeFnProto:
+        case NodeTypeFnDef:
+        case NodeTypeFnDecl:
+        case NodeTypeParamDecl:
+        case NodeTypeDirective:
+        case NodeTypeDefer:
+        case NodeTypeVariableDeclaration:
+        case NodeTypeTypeDecl:
+        case NodeTypeErrorValueDecl:
+        case NodeTypeUnwrapErrorExpr:
+        case NodeTypeNumberLiteral:
+        case NodeTypeStringLiteral:
+        case NodeTypeCharLiteral:
+        case NodeTypePrefixOpExpr:
+        case NodeTypeArrayAccessExpr:
+        case NodeTypeSliceExpr:
+        case NodeTypeFieldAccessExpr:
+        case NodeTypeUse:
+        case NodeTypeBoolLiteral:
+        case NodeTypeNullLiteral:
+        case NodeTypeUndefinedLiteral:
+        case NodeTypeIfVarExpr:
+        case NodeTypeWhileExpr:
+        case NodeTypeForExpr:
+        case NodeTypeSwitchExpr:
+        case NodeTypeSwitchProng:
+        case NodeTypeSwitchRange:
+        case NodeTypeLabel:
+        case NodeTypeGoto:
+        case NodeTypeBreak:
+        case NodeTypeContinue:
+        case NodeTypeAsmExpr:
+        case NodeTypeStructDecl:
+        case NodeTypeStructField:
+        case NodeTypeStructValueField:
+        case NodeTypeArrayType:
+        case NodeTypeErrorType:
+        case NodeTypeTypeLiteral:
+            zig_unreachable();
+    }
+}
+
+static bool eval_fn_args(EvalFnRoot *efr, FnTableEntry *fn, ConstExprValue *args, ConstExprValue *out_val) {
+    EvalFn ef = {0};
+    ef.root = efr;
+    ef.fn = fn;
+    ef.return_expr = out_val;
+
+    EvalScope *root_scope = allocate<EvalScope>(1);
+    root_scope->block_context = fn->fn_def_node->data.fn_def.body->block_context;
+    ef.scope_stack.append(root_scope);
+
+    int param_count = fn->type_entry->data.fn.fn_type_id.param_count;
+    for (int i = 0; i < param_count; i += 1) {
+        AstNode *decl_param_node = fn->proto_node->data.fn_proto.params.at(i);
+        assert(decl_param_node->type == NodeTypeParamDecl);
+
+        ConstExprValue *src_const_val = &args[i];
+        assert(src_const_val->ok);
+
+        root_scope->vars.add_one();
+        EvalVar *eval_var = &root_scope->vars.last();
+        eval_var->name = &decl_param_node->data.param_decl.name;
+        eval_var->value = *src_const_val;
+    }
+
+    return eval_expr(&ef, fn->fn_def_node->data.fn_def.body, out_val);
+
+}
+
+bool eval_fn(CodeGen *g, AstNode *node, FnTableEntry *fn, ConstExprValue *out_val,
+        int branch_quota, AstNode *struct_node)
+{
+    assert(node->type == NodeTypeFnCallExpr);
+
+    EvalFnRoot efr = {0};
+    efr.codegen = g;
+    efr.fn = fn;
+    efr.call_node = node;
+    efr.branch_quota = branch_quota;
+
+    int call_param_count = node->data.fn_call_expr.params.length;
+    int type_param_count = fn->type_entry->data.fn.fn_type_id.param_count;
+    ConstExprValue *args = allocate<ConstExprValue>(type_param_count);
+    int next_arg_index = 0;
+    if (struct_node) {
+        ConstExprValue *struct_val = &get_resolved_expr(struct_node)->const_val;
+        assert(struct_val->ok);
+        args[next_arg_index] = *struct_val;
+        next_arg_index += 1;
+    }
+    for (int call_index = 0; call_index < call_param_count; call_index += 1) {
+        AstNode *call_param_node = node->data.fn_call_expr.params.at(call_index);
+        ConstExprValue *src_const_val = &get_resolved_expr(call_param_node)->const_val;
+        assert(src_const_val->ok);
+        args[next_arg_index] = *src_const_val;
+        next_arg_index += 1;
+    }
+    eval_fn_args(&efr, fn, args, out_val);
+
+    if (efr.exceeded_quota_node) {
+        ErrorMsg *msg = add_node_error(g, fn->fn_def_node,
+                buf_sprintf("function evaluation exceeded %d branches", efr.branch_quota));
+
+        add_error_note(g, msg, efr.call_node, buf_sprintf("called from here"));
+        add_error_note(g, msg, efr.exceeded_quota_node, buf_sprintf("quota exceeded here"));
+        return true;
+    }
+
+    return false;
+}
+
src/eval.hpp
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2016 Andrew Kelley
+ *
+ * This file is part of zig, which is MIT licensed.
+ * See http://opensource.org/licenses/MIT
+ */
+
+#ifndef ZIG_EVAL_HPP
+#define ZIG_EVAL_HPP
+
+#include "all_types.hpp"
+
+bool eval_fn(CodeGen *g, AstNode *node, FnTableEntry *fn, ConstExprValue *out_val, int branch_quota,
+        AstNode *struct_node);
+
+bool const_values_equal(ConstExprValue *a, ConstExprValue *b, TypeTableEntry *type_entry);
+void eval_const_expr_bin_op(ConstExprValue *op1_val, TypeTableEntry *op1_type,
+        BinOpType bin_op, ConstExprValue *op2_val, TypeTableEntry *op2_type, ConstExprValue *out_val);
+
+void eval_const_expr_implicit_cast(CastOp cast_op,
+        ConstExprValue *other_val, TypeTableEntry *other_type,
+        ConstExprValue *const_val);
+
+#endif
CMakeLists.txt
@@ -41,6 +41,7 @@ set(ZIG_SOURCES
     "${CMAKE_SOURCE_DIR}/src/bignum.cpp"
     "${CMAKE_SOURCE_DIR}/src/tokenizer.cpp"
     "${CMAKE_SOURCE_DIR}/src/parser.cpp"
+    "${CMAKE_SOURCE_DIR}/src/eval.cpp"
     "${CMAKE_SOURCE_DIR}/src/analyze.cpp"
     "${CMAKE_SOURCE_DIR}/src/codegen.cpp"
     "${CMAKE_SOURCE_DIR}/src/buffer.cpp"