Commit 41144a8566

Andrew Kelley <superjoe30@gmail.com>
2017-04-13 06:13:54
ability to inline at function callsite
closes #306
1 parent f043e0e
doc/langref.md
@@ -81,9 +81,9 @@ SwitchProng = (list(SwitchItem, ",") | "else") "=>" option("|" option("*") Symbo
 
 SwitchItem = Expression | (Expression "..." Expression)
 
-WhileExpression(body) = option("inline") "while" "(" Expression option(";" Expression) ")" body
+WhileExpression(body) = "while" "(" Expression option(";" Expression) ")" body
 
-ForExpression(body) = option("inline") "for" "(" Expression ")" option("|" option("*") Symbol option("," Symbol) "|") body
+ForExpression(body) = "for" "(" Expression ")" option("|" option("*") Symbol option("," Symbol) "|") body
 
 BoolOrExpression = BoolAndExpression "or" BoolOrExpression | BoolAndExpression
 
@@ -127,7 +127,9 @@ MultiplyOperator = "*" | "/" | "%" | "**" | "*%"
 
 PrefixOpExpression = PrefixOp PrefixOpExpression | SuffixOpExpression
 
-SuffixOpExpression = PrimaryExpression option(FnCallExpression | ArrayAccessExpression | FieldAccessExpression | SliceExpression)
+SuffixOpExpression = InlineExpression option(FnCallExpression | ArrayAccessExpression | FieldAccessExpression | SliceExpression)
+
+InlineExpression = option("inline") PrimaryExpression
 
 FieldAccessExpression = "." Symbol
 
@@ -161,6 +163,7 @@ ContainerDecl = option("extern" | "packed") ("struct" | "enum" | "union") "{" ma
 ## Operator Precedence
 
 ```
+inline x
 x() x[] x.y
 !x -x -%x ~x *x &x ?x %x %%x ??x
 x{}
src/all_types.hpp
@@ -167,6 +167,7 @@ struct ConstErrValue {
 struct ConstBoundFnValue {
     FnTableEntry *fn;
     IrInstruction *first_arg;
+    bool is_inline;
 };
 
 struct ConstArgTuple {
@@ -192,6 +193,11 @@ enum RuntimeHintMaybe {
     RuntimeHintMaybeNonNull,
 };
 
+struct ConstFn {
+    FnTableEntry *fn_entry;
+    bool is_inline;
+};
+
 struct ConstExprValue {
     TypeTableEntry *type;
     ConstValSpecial special;
@@ -202,7 +208,7 @@ struct ConstExprValue {
         // populated if special == ConstValSpecialStatic
         BigNum x_bignum;
         bool x_bool;
-        FnTableEntry *x_fn;
+        ConstFn x_fn;
         ConstBoundFnValue x_bound_fn;
         TypeTableEntry *x_type;
         ConstExprValue *x_maybe;
@@ -366,6 +372,7 @@ enum NodeType {
     NodeTypeTypeLiteral,
     NodeTypeVarLiteral,
     NodeTypeTryExpr,
+    NodeTypeInlineExpr,
 };
 
 struct AstNodeRoot {
@@ -789,6 +796,10 @@ struct AstNodeTypeLiteral {
 struct AstNodeVarLiteral {
 };
 
+struct AstNodeInlineExpr {
+    AstNode *body;
+};
+
 struct AstNode {
     enum NodeType type;
     size_t line;
@@ -847,6 +858,7 @@ struct AstNode {
         AstNodeErrorType error_type;
         AstNodeTypeLiteral type_literal;
         AstNodeVarLiteral var_literal;
+        AstNodeInlineExpr inline_expr;
     } data;
 };
 
@@ -1748,6 +1760,7 @@ enum IrInstructionId {
     IrInstructionIdDeclRef,
     IrInstructionIdPanic,
     IrInstructionIdEnumTagName,
+    IrInstructionIdSetFnRefInline,
 };
 
 struct IrInstruction {
@@ -1943,6 +1956,7 @@ struct IrInstructionCall {
     IrInstruction **args;
     bool is_comptime;
     LLVMValueRef tmp_ptr;
+    bool is_inline;
 };
 
 struct IrInstructionConst {
@@ -2493,6 +2507,12 @@ struct IrInstructionEnumTagName {
     IrInstruction *target;
 };
 
+struct IrInstructionSetFnRefInline {
+    IrInstruction base;
+
+    IrInstruction *fn_ref;
+};
+
 static const size_t slice_ptr_index = 0;
 static const size_t slice_len_index = 1;
 
src/analyze.cpp
@@ -2129,6 +2129,7 @@ void scan_decls(CodeGen *g, ScopeDecls *decls_scope, AstNode *node) {
         case NodeTypeTypeLiteral:
         case NodeTypeVarLiteral:
         case NodeTypeTryExpr:
+        case NodeTypeInlineExpr:
             zig_unreachable();
     }
 }
@@ -3251,7 +3252,8 @@ static uint32_t hash_const_val(ConstExprValue *const_val) {
             // TODO better hashing algorithm
             return 31643936;
         case TypeTableEntryIdFn:
-            return hash_ptr(const_val->data.x_fn);
+            return hash_ptr(const_val->data.x_fn.fn_entry) +
+                (const_val->data.x_fn.is_inline ? 4133894920 : 3983484790);
         case TypeTableEntryIdTypeDecl:
             return hash_ptr(const_val->data.x_type);
         case TypeTableEntryIdNamespace:
@@ -3697,7 +3699,8 @@ bool const_values_equal(ConstExprValue *a, ConstExprValue *b) {
         case TypeTableEntryIdPureError:
             return a->data.x_pure_err == b->data.x_pure_err;
         case TypeTableEntryIdFn:
-            return a->data.x_fn == b->data.x_fn;
+            return a->data.x_fn.fn_entry == b->data.x_fn.fn_entry &&
+                a->data.x_fn.is_inline == b->data.x_fn.is_inline;
         case TypeTableEntryIdBool:
             return a->data.x_bool == b->data.x_bool;
         case TypeTableEntryIdInt:
@@ -3933,8 +3936,9 @@ void render_const_value(Buf *buf, ConstExprValue *const_val) {
             zig_unreachable();
         case TypeTableEntryIdFn:
             {
-                FnTableEntry *fn_entry = const_val->data.x_fn;
-                buf_appendf(buf, "%s", buf_ptr(&fn_entry->symbol_name));
+                FnTableEntry *fn_entry = const_val->data.x_fn.fn_entry;
+                const char *inline_str = const_val->data.x_fn.is_inline ? "inline " : "";
+                buf_appendf(buf, "%s%s", inline_str, buf_ptr(&fn_entry->symbol_name));
                 return;
             }
         case TypeTableEntryIdBlock:
@@ -4011,7 +4015,8 @@ void render_const_value(Buf *buf, ConstExprValue *const_val) {
         case TypeTableEntryIdBoundFn:
             {
                 FnTableEntry *fn_entry = const_val->data.x_bound_fn.fn;
-                buf_appendf(buf, "(bound fn %s)", buf_ptr(&fn_entry->symbol_name));
+                const char *inline_str = const_val->data.x_bound_fn.is_inline ? "inline " : "";
+                buf_appendf(buf, "(%sbound fn %s)", inline_str, buf_ptr(&fn_entry->symbol_name));
                 return;
             }
         case TypeTableEntryIdStruct:
src/ast_render.cpp
@@ -240,6 +240,8 @@ static const char *node_type_str(NodeType node_type) {
             return "VarLiteral";
         case NodeTypeTryExpr:
             return "TryExpr";
+        case NodeTypeInlineExpr:
+            return "InlineExpr";
     }
     zig_unreachable();
 }
@@ -921,6 +923,12 @@ static void render_node_extra(AstRender *ar, AstNode *node, bool grouped) {
                 render_node_ungrouped(ar, node->data.unwrap_err_expr.op2);
                 break;
             }
+        case NodeTypeInlineExpr:
+            {
+                fprintf(ar->f, "inline ");
+                render_node_grouped(ar, node->data.inline_expr.body);
+                break;
+            }
         case NodeTypeFnDecl:
         case NodeTypeParamDecl:
         case NodeTypeErrorValueDecl:
src/codegen.cpp
@@ -608,7 +608,7 @@ static void gen_panic(CodeGen *g, LLVMValueRef msg_arg) {
         LLVMBuildLoad(g->builder, ptr_ptr, ""),
         LLVMBuildLoad(g->builder, len_ptr, ""),
     };
-    ZigLLVMBuildCall(g->builder, fn_val, args, 2, panic_fn->type_entry->data.fn.calling_convention, "");
+    ZigLLVMBuildCall(g->builder, fn_val, args, 2, panic_fn->type_entry->data.fn.calling_convention, false, "");
     LLVMBuildUnreachable(g->builder);
 }
 
@@ -1773,8 +1773,12 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutable *executable, IrInstr
         }
     }
 
+    bool want_always_inline = (instruction->fn_entry != nullptr &&
+            instruction->fn_entry->fn_inline == FnInlineAlways) || instruction->is_inline;
+
     LLVMValueRef result = ZigLLVMBuildCall(g->builder, fn_val,
-            gen_param_values, (unsigned)gen_param_index, fn_type->data.fn.calling_convention, "");
+            gen_param_values, (unsigned)gen_param_index, fn_type->data.fn.calling_convention,
+            want_always_inline, "");
 
     for (size_t param_i = 0; param_i < fn_type_id->param_count; param_i += 1) {
         FnGenParamInfo *gen_info = &fn_type->data.fn.gen_param_info[param_i];
@@ -2749,6 +2753,7 @@ static LLVMValueRef ir_render_instruction(CodeGen *g, IrExecutable *executable,
         case IrInstructionIdSetGlobalLinkage:
         case IrInstructionIdDeclRef:
         case IrInstructionIdSwitchVar:
+        case IrInstructionIdSetFnRefInline:
             zig_unreachable();
         case IrInstructionIdReturn:
             return ir_render_return(g, executable, (IrInstructionReturn *)instruction);
@@ -3183,7 +3188,7 @@ static LLVMValueRef gen_const_val(CodeGen *g, ConstExprValue *const_val) {
                 }
             }
         case TypeTableEntryIdFn:
-            return fn_llvm_value(g, const_val->data.x_fn);
+            return fn_llvm_value(g, const_val->data.x_fn.fn_entry);
         case TypeTableEntryIdPointer:
             {
                 render_const_val_global(g, const_val, "");
src/ir.cpp
@@ -545,6 +545,10 @@ static constexpr IrInstructionId ir_instruction_id(IrInstructionEnumTagName *) {
     return IrInstructionIdEnumTagName;
 }
 
+static constexpr IrInstructionId ir_instruction_id(IrInstructionSetFnRefInline *) {
+    return IrInstructionIdSetFnRefInline;
+}
+
 template<typename T>
 static T *ir_create_instruction(IrBuilder *irb, Scope *scope, AstNode *source_node) {
     T *special_instruction = allocate<T>(1);
@@ -701,7 +705,7 @@ static IrInstruction *ir_create_const_fn(IrBuilder *irb, Scope *scope, AstNode *
     IrInstructionConst *const_instruction = ir_create_instruction<IrInstructionConst>(irb, scope, source_node);
     const_instruction->base.value.type = fn_entry->type_entry;
     const_instruction->base.value.special = ConstValSpecialStatic;
-    const_instruction->base.value.data.x_fn = fn_entry;
+    const_instruction->base.value.data.x_fn.fn_entry = fn_entry;
     return &const_instruction->base;
 }
 
@@ -882,12 +886,13 @@ static IrInstruction *ir_build_enum_field_ptr_from(IrBuilder *irb, IrInstruction
 
 static IrInstruction *ir_build_call(IrBuilder *irb, Scope *scope, AstNode *source_node,
         FnTableEntry *fn_entry, IrInstruction *fn_ref, size_t arg_count, IrInstruction **args,
-        bool is_comptime)
+        bool is_comptime, bool is_inline)
 {
     IrInstructionCall *call_instruction = ir_build_instruction<IrInstructionCall>(irb, scope, source_node);
     call_instruction->fn_entry = fn_entry;
     call_instruction->fn_ref = fn_ref;
     call_instruction->is_comptime = is_comptime;
+    call_instruction->is_inline = is_inline;
     call_instruction->args = args;
     call_instruction->arg_count = arg_count;
 
@@ -901,10 +906,10 @@ static IrInstruction *ir_build_call(IrBuilder *irb, Scope *scope, AstNode *sourc
 
 static IrInstruction *ir_build_call_from(IrBuilder *irb, IrInstruction *old_instruction,
         FnTableEntry *fn_entry, IrInstruction *fn_ref, size_t arg_count, IrInstruction **args,
-        bool is_comptime)
+        bool is_comptime, bool is_inline)
 {
     IrInstruction *new_instruction = ir_build_call(irb, old_instruction->scope,
-            old_instruction->source_node, fn_entry, fn_ref, arg_count, args, is_comptime);
+            old_instruction->source_node, fn_entry, fn_ref, arg_count, args, is_comptime, is_inline);
     ir_link_new_instruction(new_instruction, old_instruction);
     return new_instruction;
 }
@@ -2145,6 +2150,18 @@ static IrInstruction *ir_build_enum_tag_name(IrBuilder *irb, Scope *scope, AstNo
     return &instruction->base;
 }
 
+static IrInstruction *ir_build_set_fn_ref_inline(IrBuilder *irb, Scope *scope, AstNode *source_node,
+        IrInstruction *fn_ref)
+{
+    IrInstructionSetFnRefInline *instruction = ir_build_instruction<IrInstructionSetFnRefInline>(
+            irb, scope, source_node);
+    instruction->fn_ref = fn_ref;
+
+    ir_ref_instruction(fn_ref, irb->current_basic_block);
+
+    return &instruction->base;
+}
+
 static IrInstruction *ir_instruction_br_get_dep(IrInstructionBr *instruction, size_t index) {
     return nullptr;
 }
@@ -2809,6 +2826,14 @@ static IrInstruction *ir_instruction_enumtagname_get_dep(IrInstructionEnumTagNam
     }
 }
 
+static IrInstruction *ir_instruction_setfnrefinline_get_dep(IrInstructionSetFnRefInline *instruction, size_t index) {
+    switch (index) {
+        case 0: return instruction->fn_ref;
+        default: return nullptr;
+    }
+}
+
+
 static IrInstruction *ir_instruction_get_dep(IrInstruction *instruction, size_t index) {
     switch (instruction->id) {
         case IrInstructionIdInvalid:
@@ -2999,6 +3024,8 @@ static IrInstruction *ir_instruction_get_dep(IrInstruction *instruction, size_t
             return ir_instruction_panic_get_dep((IrInstructionPanic *) instruction, index);
         case IrInstructionIdEnumTagName:
             return ir_instruction_enumtagname_get_dep((IrInstructionEnumTagName *) instruction, index);
+        case IrInstructionIdSetFnRefInline:
+            return ir_instruction_setfnrefinline_get_dep((IrInstructionSetFnRefInline *) instruction, index);
     }
     zig_unreachable();
 }
@@ -4297,7 +4324,7 @@ static IrInstruction *ir_gen_fn_call(IrBuilder *irb, Scope *scope, AstNode *node
             return args[i];
     }
 
-    return ir_build_call(irb, scope, node, nullptr, fn_ref, arg_count, args, false);
+    return ir_build_call(irb, scope, node, nullptr, fn_ref, arg_count, args, false, false);
 }
 
 static IrInstruction *ir_gen_if_bool_expr(IrBuilder *irb, Scope *scope, AstNode *node) {
@@ -5490,6 +5517,19 @@ static IrInstruction *ir_gen_fn_proto(IrBuilder *irb, Scope *parent_scope, AstNo
     return ir_build_fn_proto(irb, parent_scope, node, param_types, return_type);
 }
 
+static IrInstruction *ir_gen_inline_expr(IrBuilder *irb, Scope *parent_scope, AstNode *node) {
+    assert(node->type == NodeTypeInlineExpr);
+
+    AstNode *body_node = node->data.inline_expr.body;
+
+    IrInstruction *fn_ptr = ir_gen_node(irb, body_node, parent_scope);
+    if (fn_ptr == irb->codegen->invalid_instruction)
+        return irb->codegen->invalid_instruction;
+
+    return ir_build_set_fn_ref_inline(irb, parent_scope, node, fn_ptr);
+}
+
+
 static IrInstruction *ir_gen_node_raw(IrBuilder *irb, AstNode *node, Scope *scope,
         LVal lval)
 {
@@ -5580,6 +5620,8 @@ static IrInstruction *ir_gen_node_raw(IrBuilder *irb, AstNode *node, Scope *scop
             return ir_lval_wrap(irb, scope, ir_gen_container_decl(irb, scope, node), lval);
         case NodeTypeFnProto:
             return ir_lval_wrap(irb, scope, ir_gen_fn_proto(irb, scope, node), lval);
+        case NodeTypeInlineExpr:
+            return ir_lval_wrap(irb, scope, ir_gen_inline_expr(irb, scope, node), lval);
         case NodeTypeFnDef:
             zig_panic("TODO IR gen NodeTypeFnDef");
         case NodeTypeFnDecl:
@@ -6435,7 +6477,7 @@ static TypeTableEntry *ir_resolve_type(IrAnalyze *ira, IrInstruction *type_value
     return const_val->data.x_type;
 }
 
-static FnTableEntry *ir_resolve_fn(IrAnalyze *ira, IrInstruction *fn_value) {
+static FnTableEntry *ir_resolve_fn(IrAnalyze *ira, IrInstruction *fn_value, bool *is_inline) {
     if (fn_value == ira->codegen->invalid_instruction)
         return nullptr;
 
@@ -6452,7 +6494,8 @@ static FnTableEntry *ir_resolve_fn(IrAnalyze *ira, IrInstruction *fn_value) {
     if (!const_val)
         return nullptr;
 
-    return const_val->data.x_fn;
+    *is_inline = const_val->data.x_fn.is_inline;
+    return const_val->data.x_fn.fn_entry;
 }
 
 static IrInstruction *ir_analyze_maybe_wrap(IrAnalyze *ira, IrInstruction *source_instr, IrInstruction *value, TypeTableEntry *wanted_type) {
@@ -8245,7 +8288,7 @@ static IrInstruction *ir_get_var_ptr(IrAnalyze *ira, IrInstruction *instruction,
 
 static TypeTableEntry *ir_analyze_fn_call(IrAnalyze *ira, IrInstructionCall *call_instruction,
     FnTableEntry *fn_entry, TypeTableEntry *fn_type, IrInstruction *fn_ref,
-    IrInstruction *first_arg_ptr, bool inline_fn_call)
+    IrInstruction *first_arg_ptr, bool comptime_fn_call, bool inline_fn_call)
 {
     FnTypeId *fn_type_id = &fn_type->data.fn.fn_type_id;
     size_t first_arg_1_or_0 = first_arg_ptr ? 1 : 0;
@@ -8276,7 +8319,7 @@ static TypeTableEntry *ir_analyze_fn_call(IrAnalyze *ira, IrInstructionCall *cal
         return ira->codegen->builtin_types.entry_invalid;
     }
 
-    if (inline_fn_call) {
+    if (comptime_fn_call) {
         // No special handling is needed for compile time evaluation of generic functions.
         if (!fn_entry || fn_entry->type_entry->data.fn.fn_type_id.is_extern) {
             ir_add_error(ira, fn_ref, buf_sprintf("unable to evaluate constant expression"));
@@ -8472,8 +8515,8 @@ static TypeTableEntry *ir_analyze_fn_call(IrAnalyze *ira, IrInstructionCall *cal
             inst_fn_type_id.return_type = return_type;
 
             if (type_requires_comptime(return_type)) {
-                // Throw out our work and call the function as if it were inline.
-                return ir_analyze_fn_call(ira, call_instruction, fn_entry, fn_type, fn_ref, first_arg_ptr, true);
+                // Throw out our work and call the function as if it were comptime.
+                return ir_analyze_fn_call(ira, call_instruction, fn_entry, fn_type, fn_ref, first_arg_ptr, true, false);
             }
         }
 
@@ -8498,7 +8541,7 @@ static TypeTableEntry *ir_analyze_fn_call(IrAnalyze *ira, IrInstructionCall *cal
 
         size_t impl_param_count = impl_fn->type_entry->data.fn.fn_type_id.param_count;
         IrInstruction *new_call_instruction = ir_build_call_from(&ira->new_irb, &call_instruction->base,
-                impl_fn, nullptr, impl_param_count, casted_args, false);
+                impl_fn, nullptr, impl_param_count, casted_args, false, inline_fn_call);
 
         TypeTableEntry *return_type = impl_fn->type_entry->data.fn.fn_type_id.return_type;
         ir_add_alloca(ira, new_call_instruction, return_type);
@@ -8557,7 +8600,7 @@ static TypeTableEntry *ir_analyze_fn_call(IrAnalyze *ira, IrInstructionCall *cal
         return ira->codegen->builtin_types.entry_invalid;
 
     IrInstruction *new_call_instruction = ir_build_call_from(&ira->new_irb, &call_instruction->base,
-            fn_entry, fn_ref, call_param_count, casted_args, false);
+            fn_entry, fn_ref, call_param_count, casted_args, false, inline_fn_call);
 
     ir_add_alloca(ira, new_call_instruction, return_type);
     return ir_finish_anal(ira, return_type);
@@ -8568,10 +8611,10 @@ static TypeTableEntry *ir_analyze_instruction_call(IrAnalyze *ira, IrInstruction
     if (type_is_invalid(fn_ref->value.type))
         return ira->codegen->builtin_types.entry_invalid;
 
-    bool is_inline = call_instruction->is_comptime ||
+    bool is_comptime = call_instruction->is_comptime ||
         ir_should_inline(ira->new_irb.exec, call_instruction->base.scope);
 
-    if (is_inline || instr_is_comptime(fn_ref)) {
+    if (is_comptime || instr_is_comptime(fn_ref)) {
         if (fn_ref->value.type->id == TypeTableEntryIdMetaType) {
             TypeTableEntry *dest_type = ir_resolve_type(ira, fn_ref);
             if (type_is_invalid(dest_type))
@@ -8594,15 +8637,17 @@ static TypeTableEntry *ir_analyze_instruction_call(IrAnalyze *ira, IrInstruction
             ir_link_new_instruction(cast_instruction, &call_instruction->base);
             return ir_finish_anal(ira, cast_instruction->value.type);
         } else if (fn_ref->value.type->id == TypeTableEntryIdFn) {
-            FnTableEntry *fn_table_entry = ir_resolve_fn(ira, fn_ref);
+            bool is_inline;
+            FnTableEntry *fn_table_entry = ir_resolve_fn(ira, fn_ref, &is_inline);
             return ir_analyze_fn_call(ira, call_instruction, fn_table_entry, fn_table_entry->type_entry,
-                fn_ref, nullptr, is_inline);
+                fn_ref, nullptr, is_comptime, is_inline);
         } else if (fn_ref->value.type->id == TypeTableEntryIdBoundFn) {
             assert(fn_ref->value.special == ConstValSpecialStatic);
             FnTableEntry *fn_table_entry = fn_ref->value.data.x_bound_fn.fn;
             IrInstruction *first_arg_ptr = fn_ref->value.data.x_bound_fn.first_arg;
+            bool is_inline = fn_ref->value.data.x_bound_fn.is_inline;
             return ir_analyze_fn_call(ira, call_instruction, fn_table_entry, fn_table_entry->type_entry,
-                nullptr, first_arg_ptr, is_inline);
+                nullptr, first_arg_ptr, is_comptime, is_inline);
         } else {
             ir_add_error_node(ira, fn_ref->source_node,
                 buf_sprintf("type '%s' not a function", buf_ptr(&fn_ref->value.type->name)));
@@ -8612,7 +8657,7 @@ static TypeTableEntry *ir_analyze_instruction_call(IrAnalyze *ira, IrInstruction
 
     if (fn_ref->value.type->id == TypeTableEntryIdFn) {
         return ir_analyze_fn_call(ira, call_instruction, nullptr, fn_ref->value.type,
-            fn_ref, nullptr, false);
+            fn_ref, nullptr, false, false);
     } else {
         ir_add_error_node(ira, fn_ref->source_node,
             buf_sprintf("type '%s' not a function", buf_ptr(&fn_ref->value.type->name)));
@@ -9354,7 +9399,7 @@ static TypeTableEntry *ir_analyze_decl_ref(IrAnalyze *ira, IrInstruction *source
             ConstExprValue *const_val = allocate<ConstExprValue>(1);
             const_val->special = ConstValSpecialStatic;
             const_val->type = fn_entry->type_entry;
-            const_val->data.x_fn = fn_entry;
+            const_val->data.x_fn.fn_entry = fn_entry;
 
             bool ptr_is_const = true;
             bool ptr_is_volatile = false;
@@ -9891,7 +9936,7 @@ static TypeTableEntry *ir_analyze_instruction_set_debug_safety(IrAnalyze *ira,
         safety_off_ptr = &block_scope->safety_off;
         safety_set_node_ptr = &block_scope->safety_set_node;
     } else if (target_type->id == TypeTableEntryIdFn) {
-        FnTableEntry *target_fn = target_val->data.x_fn;
+        FnTableEntry *target_fn = target_val->data.x_fn.fn_entry;
         assert(target_fn->def_scope);
         safety_off_ptr = &target_fn->def_scope->safety_off;
         safety_set_node_ptr = &target_fn->def_scope->safety_set_node;
@@ -11164,6 +11209,38 @@ static TypeTableEntry *ir_analyze_instruction_enum_tag_name(IrAnalyze *ira, IrIn
     return result->value.type;
 }
 
+static TypeTableEntry *ir_analyze_instruction_set_fn_ref_inline(IrAnalyze *ira,
+        IrInstructionSetFnRefInline *instruction)
+{
+    IrInstruction *fn_ref = instruction->fn_ref->other;
+    if (type_is_invalid(fn_ref->value.type))
+        return ira->codegen->builtin_types.entry_invalid;
+
+    if (fn_ref->value.type->id == TypeTableEntryIdFn) {
+        ConstExprValue *fn_ref_val = ir_resolve_const(ira, fn_ref, UndefBad);
+        if (!fn_ref_val)
+            return ira->codegen->builtin_types.entry_invalid;
+
+        ConstExprValue *out_val = ir_build_const_from(ira, &instruction->base);
+        *out_val = *fn_ref_val;
+        out_val->data.x_fn.is_inline = true;
+        return out_val->type;
+    } else if (fn_ref->value.type->id == TypeTableEntryIdBoundFn) {
+        ConstExprValue *fn_ref_val = ir_resolve_const(ira, fn_ref, UndefBad);
+        if (!fn_ref_val)
+            return ira->codegen->builtin_types.entry_invalid;
+
+        ConstExprValue *out_val = ir_build_const_from(ira, &instruction->base);
+        *out_val = *fn_ref_val;
+        out_val->data.x_bound_fn.is_inline = true;
+        return out_val->type;
+    } else {
+        ir_add_error(ira, &instruction->base,
+                buf_sprintf("expected function type, found '%s'", buf_ptr(&fn_ref->value.type->name)));
+        return ira->codegen->builtin_types.entry_invalid;
+    }
+}
+
 static TypeTableEntry *ir_analyze_instruction_type_name(IrAnalyze *ira, IrInstructionTypeName *instruction) {
     IrInstruction *type_value = instruction->type_value->other;
     TypeTableEntry *type_entry = ir_resolve_type(ira, type_value);
@@ -12710,6 +12787,8 @@ static TypeTableEntry *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstructi
             return ir_analyze_instruction_int_to_ptr(ira, (IrInstructionIntToPtr *)instruction);
         case IrInstructionIdEnumTagName:
             return ir_analyze_instruction_enum_tag_name(ira, (IrInstructionEnumTagName *)instruction);
+        case IrInstructionIdSetFnRefInline:
+            return ir_analyze_instruction_set_fn_ref_inline(ira, (IrInstructionSetFnRefInline *)instruction);
         case IrInstructionIdMaybeWrap:
         case IrInstructionIdErrWrapCode:
         case IrInstructionIdErrWrapPayload:
@@ -12892,6 +12971,7 @@ bool ir_has_side_effects(IrInstruction *instruction) {
         case IrInstructionIdErrName:
         case IrInstructionIdTypeName:
         case IrInstructionIdEnumTagName:
+        case IrInstructionIdSetFnRefInline:
             return false;
         case IrInstructionIdAsm:
             {
@@ -12953,7 +13033,7 @@ FnTableEntry *ir_create_inline_fn(CodeGen *codegen, Buf *fn_name, VariableTableE
     }
 
     IrInstruction *call_instruction = ir_build_call(irb, scope, source_node, nullptr, fn_ref_instruction,
-            arg_count, args, false);
+            arg_count, args, false, false);
     ir_build_return(irb, scope, source_node, call_instruction);
 
     if (codegen->verbose) {
src/ir_print.cpp
@@ -870,6 +870,10 @@ static void ir_print_panic(IrPrint *irp, IrInstructionPanic *instruction) {
     fprintf(irp->f, ")");
 }
 
+static void ir_print_set_fn_ref_inline(IrPrint *irp, IrInstructionSetFnRefInline *instruction) {
+    fprintf(irp->f, "inline ");
+    ir_print_other_instruction(irp, instruction->fn_ref);
+}
 
 static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) {
     ir_print_prefix(irp, instruction);
@@ -1155,6 +1159,9 @@ static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) {
         case IrInstructionIdPanic:
             ir_print_panic(irp, (IrInstructionPanic *)instruction);
             break;
+        case IrInstructionIdSetFnRefInline:
+            ir_print_set_fn_ref_inline(irp, (IrInstructionSetFnRefInline *)instruction);
+            break;
     }
     fprintf(irp->f, "\n");
 }
src/parser.cpp
@@ -927,7 +927,32 @@ static AstNode *ast_parse_curly_suffix_expr(ParseContext *pc, size_t *token_inde
 }
 
 /*
-SuffixOpExpression = PrimaryExpression option(FnCallExpression | ArrayAccessExpression | FieldAccessExpression | SliceExpression)
+InlineExpression = option("inline") PrimaryExpression
+*/
+static AstNode *ast_parse_inline_expr(ParseContext *pc, size_t *token_index, bool mandatory) {
+    Token *token = &pc->tokens->at(*token_index);
+
+    if (token->id == TokenIdKeywordInline) {
+        *token_index += 1;
+        AstNode *primary_expr_node = ast_parse_primary_expr(pc, token_index, true);
+        if (primary_expr_node->type == NodeTypeWhileExpr) {
+            primary_expr_node->data.while_expr.is_inline = true;
+            return primary_expr_node;
+        } else if (primary_expr_node->type == NodeTypeForExpr) {
+            primary_expr_node->data.for_expr.is_inline = true;
+            return primary_expr_node;
+        } else {
+            AstNode *node = ast_create_node(pc, NodeTypeInlineExpr, token);
+            node->data.inline_expr.body = primary_expr_node;
+            return node;
+        }
+    } else {
+        return ast_parse_primary_expr(pc, token_index, mandatory);
+    }
+}
+
+/*
+SuffixOpExpression = InlineExpression option(FnCallExpression | ArrayAccessExpression | FieldAccessExpression | SliceExpression)
 FnCallExpression : token(LParen) list(Expression, token(Comma)) token(RParen)
 ArrayAccessExpression : token(LBracket) Expression token(RBracket)
 SliceExpression : token(LBracket) Expression token(Ellipsis) option(Expression) token(RBracket) option(token(Const))
@@ -935,8 +960,8 @@ FieldAccessExpression : token(Dot) token(Symbol)
 StructLiteralField : token(Dot) token(Symbol) token(Eq) Expression
 */
 static AstNode *ast_parse_suffix_op_expr(ParseContext *pc, size_t *token_index, bool mandatory) {
-    AstNode *primary_expr = ast_parse_primary_expr(pc, token_index, mandatory);
-    if (!primary_expr)
+    AstNode *inline_expr = ast_parse_inline_expr(pc, token_index, mandatory);
+    if (!inline_expr)
         return nullptr;
 
     while (true) {
@@ -945,10 +970,10 @@ static AstNode *ast_parse_suffix_op_expr(ParseContext *pc, size_t *token_index,
             *token_index += 1;
 
             AstNode *node = ast_create_node(pc, NodeTypeFnCallExpr, first_token);
-            node->data.fn_call_expr.fn_ref_expr = primary_expr;
+            node->data.fn_call_expr.fn_ref_expr = inline_expr;
             ast_parse_fn_call_param_list(pc, token_index, &node->data.fn_call_expr.params);
 
-            primary_expr = node;
+            inline_expr = node;
         } else if (first_token->id == TokenIdLBracket) {
             *token_index += 1;
 
@@ -960,7 +985,7 @@ static AstNode *ast_parse_suffix_op_expr(ParseContext *pc, size_t *token_index,
                 *token_index += 1;
 
                 AstNode *node = ast_create_node(pc, NodeTypeSliceExpr, first_token);
-                node->data.slice_expr.array_ref_expr = primary_expr;
+                node->data.slice_expr.array_ref_expr = inline_expr;
                 node->data.slice_expr.start = expr_node;
                 node->data.slice_expr.end = ast_parse_expression(pc, token_index, false);
 
@@ -972,15 +997,15 @@ static AstNode *ast_parse_suffix_op_expr(ParseContext *pc, size_t *token_index,
                     node->data.slice_expr.is_const = true;
                 }
 
-                primary_expr = node;
+                inline_expr = node;
             } else if (ellipsis_or_r_bracket->id == TokenIdRBracket) {
                 *token_index += 1;
 
                 AstNode *node = ast_create_node(pc, NodeTypeArrayAccessExpr, first_token);
-                node->data.array_access_expr.array_ref_expr = primary_expr;
+                node->data.array_access_expr.array_ref_expr = inline_expr;
                 node->data.array_access_expr.subscript = expr_node;
 
-                primary_expr = node;
+                inline_expr = node;
             } else {
                 ast_invalid_token_error(pc, first_token);
             }
@@ -990,12 +1015,12 @@ static AstNode *ast_parse_suffix_op_expr(ParseContext *pc, size_t *token_index,
             Token *name_token = ast_eat_token(pc, token_index, TokenIdSymbol);
 
             AstNode *node = ast_create_node(pc, NodeTypeFieldAccessExpr, first_token);
-            node->data.field_access_expr.struct_expr = primary_expr;
+            node->data.field_access_expr.struct_expr = inline_expr;
             node->data.field_access_expr.field_name = token_buf(name_token);
 
-            primary_expr = node;
+            inline_expr = node;
         } else {
-            return primary_expr;
+            return inline_expr;
         }
     }
 }
@@ -2807,5 +2832,7 @@ void ast_visit_node_children(AstNode *node, void (*visit)(AstNode **, void *cont
         case NodeTypeVarLiteral:
             // none
             break;
+        case NodeTypeInlineExpr:
+            visit_field(&node->data.inline_expr.body, visit, context);
     }
 }
src/zig_llvm.cpp
@@ -176,10 +176,13 @@ bool ZigLLVMTargetMachineEmitToFile(LLVMTargetMachineRef targ_machine_ref, LLVMM
 
 
 LLVMValueRef ZigLLVMBuildCall(LLVMBuilderRef B, LLVMValueRef Fn, LLVMValueRef *Args,
-        unsigned NumArgs, unsigned CC, const char *Name)
+        unsigned NumArgs, unsigned CC, bool always_inline, const char *Name)
 {
     CallInst *call_inst = CallInst::Create(unwrap(Fn), makeArrayRef(unwrap(Args), NumArgs), Name);
     call_inst->setCallingConv(CC);
+    if (always_inline) {
+        call_inst->addAttribute(AttributeSet::FunctionIndex, Attribute::AlwaysInline);
+    }
     return wrap(unwrap(B)->Insert(call_inst));
 }
 
src/zig_llvm.hpp
@@ -38,7 +38,7 @@ bool ZigLLVMTargetMachineEmitToFile(LLVMTargetMachineRef targ_machine_ref, LLVMM
         const char *filename, LLVMCodeGenFileType file_type, char **error_message, bool is_debug);
 
 LLVMValueRef ZigLLVMBuildCall(LLVMBuilderRef B, LLVMValueRef Fn, LLVMValueRef *Args,
-        unsigned NumArgs, unsigned CC, const char *Name);
+        unsigned NumArgs, unsigned CC, bool always_inline, const char *Name);
 
 LLVMValueRef ZigLLVMConstInlineAsm(LLVMTypeRef Ty, const char *AsmString,
         const char *Constraints, bool HasSideEffects, bool IsAlignStack, bool is_x86);
test/cases/fn.zig
@@ -87,3 +87,10 @@ fn fn1() -> u32 {5}
 fn fn2() -> u32 {6}
 fn fn3() -> u32 {7}
 fn fn4() -> u32 {8}
+
+
+test "inline function call" {
+    assert((inline add(3, 9)) == 12);
+}
+
+fn add(a: i32, b: i32) -> i32 { a + b }