Commit 1f602fe8c5

Andrew Kelley <andrew@ziglang.org>
2019-12-05 22:55:32
implement `@call`
closes #3732
1 parent 38791ac
lib/std/builtin.zig
@@ -372,6 +372,24 @@ pub const Version = struct {
     patch: u32,
 };
 
+/// This data structure is used by the Zig language code generation and
+/// therefore must be kept in sync with the compiler implementation.
+pub const CallOptions = struct {
+    modifier: Modifier = .auto,
+    stack: ?[]align(std.Target.stack_align) u8 = null,
+
+    pub const Modifier = enum {
+        auto,
+        no_async,
+        async_call,
+        never_tail,
+        never_inline,
+        always_tail,
+        always_inline,
+        compile_time,
+    };
+};
+
 /// This function type is used by the Zig language code generation and
 /// therefore must be kept in sync with the compiler implementation.
 pub const PanicFn = fn ([]const u8, ?*StackTrace) noreturn;
src/all_types.hpp
@@ -767,10 +767,18 @@ struct AstNodeUnwrapOptional {
     AstNode *expr;
 };
 
+// Must be synchronized with std.builtin.CallOptions.Modifier
 enum CallModifier {
     CallModifierNone,
-    CallModifierAsync,
     CallModifierNoAsync,
+    CallModifierAsync,
+    CallModifierNeverTail,
+    CallModifierNeverInline,
+    CallModifierAlwaysTail,
+    CallModifierAlwaysInline,
+    CallModifierCompileTime,
+
+    // This is an additional tag in the compiler, but not exposed in the std lib.
     CallModifierBuiltin,
 };
 
@@ -1717,6 +1725,7 @@ enum BuiltinFnId {
     BuiltinFnIdFrameHandle,
     BuiltinFnIdFrameSize,
     BuiltinFnIdAs,
+    BuiltinFnIdCall,
 };
 
 struct BuiltinFnEntry {
@@ -2479,6 +2488,7 @@ enum IrInstructionId {
     IrInstructionIdVarPtr,
     IrInstructionIdReturnPtr,
     IrInstructionIdCallSrc,
+    IrInstructionIdCallExtra,
     IrInstructionIdCallGen,
     IrInstructionIdConst,
     IrInstructionIdReturn,
@@ -2886,15 +2896,24 @@ struct IrInstructionCallSrc {
     ZigFn *fn_entry;
     size_t arg_count;
     IrInstruction **args;
+    IrInstruction *ret_ptr;
     ResultLoc *result_loc;
 
     IrInstruction *new_stack;
 
-    FnInline fn_inline;
     CallModifier modifier;
-
     bool is_async_call_builtin;
-    bool is_comptime;
+};
+
+/// This is a pass1 instruction, used by @call.
+/// `args` is expected to be either a struct or a tuple.
+struct IrInstructionCallExtra {
+    IrInstruction base;
+
+    IrInstruction *options;
+    IrInstruction *fn_ref;
+    IrInstruction *args;
+    ResultLoc *result_loc;
 };
 
 struct IrInstructionCallGen {
@@ -2908,7 +2927,6 @@ struct IrInstructionCallGen {
     IrInstruction *frame_result_loc;
     IrInstruction *new_stack;
 
-    FnInline fn_inline;
     CallModifier modifier;
 
     bool is_async_call_builtin;
src/analyze.cpp
@@ -956,10 +956,7 @@ bool calling_convention_allows_zig_types(CallingConvention cc) {
 
 ZigType *get_stack_trace_type(CodeGen *g) {
     if (g->stack_trace_type == nullptr) {
-        ZigValue *stack_trace_type_val = get_builtin_value(g, "StackTrace");
-        assert(stack_trace_type_val->type->id == ZigTypeIdMetaType);
-
-        g->stack_trace_type = stack_trace_type_val->data.x_type;
+        g->stack_trace_type = get_builtin_type(g, "StackTrace");
         assertNoError(type_resolve(g, g->stack_trace_type, ResolveStatusZeroBitsKnown));
     }
     return g->stack_trace_type;
@@ -2717,10 +2714,10 @@ static Error resolve_struct_zero_bits(CodeGen *g, ZigType *struct_type) {
         src_assert(struct_type->data.structure.fields == nullptr, decl_node);
         struct_type->data.structure.fields = alloc_type_struct_fields(field_count);
     } else if (decl_node->type == NodeTypeContainerInitExpr) {
-        src_assert(struct_type->data.structure.is_inferred, decl_node);
-        src_assert(struct_type->data.structure.fields != nullptr, decl_node);
-
         field_count = struct_type->data.structure.src_field_count;
+
+        src_assert(struct_type->data.structure.is_inferred, decl_node);
+        src_assert(field_count == 0 || struct_type->data.structure.fields != nullptr, decl_node);
     } else zig_unreachable();
 
     struct_type->data.structure.fields_by_name.init(field_count);
@@ -7531,6 +7528,12 @@ ZigValue *get_builtin_value(CodeGen *codegen, const char *name) {
     return var_value;
 }
 
+ZigType *get_builtin_type(CodeGen *codegen, const char *name) {
+    ZigValue *type_val = get_builtin_value(codegen, name);
+    assert(type_val->type->id == ZigTypeIdMetaType);
+    return type_val->data.x_type;
+}
+
 bool type_is_global_error_set(ZigType *err_set_type) {
     assert(err_set_type->id == ZigTypeIdErrorSet);
     assert(!err_set_type->data.error_set.incomplete);
src/analyze.hpp
@@ -207,6 +207,7 @@ void add_var_export(CodeGen *g, ZigVar *fn_table_entry, const char *symbol_name,
 
 
 ZigValue *get_builtin_value(CodeGen *codegen, const char *name);
+ZigType *get_builtin_type(CodeGen *codegen, const char *name);
 ZigType *get_stack_trace_type(CodeGen *g);
 bool resolve_inferred_error_set(CodeGen *g, ZigType *err_set_type, AstNode *source_node);
 
src/ast_render.cpp
@@ -702,14 +702,29 @@ static void render_node_extra(AstRender *ar, AstNode *node, bool grouped) {
                 switch (node->data.fn_call_expr.modifier) {
                     case CallModifierNone:
                         break;
-                    case CallModifierBuiltin:
-                        fprintf(ar->f, "@");
+                    case CallModifierNoAsync:
+                        fprintf(ar->f, "noasync ");
                         break;
                     case CallModifierAsync:
                         fprintf(ar->f, "async ");
                         break;
-                    case CallModifierNoAsync:
-                        fprintf(ar->f, "noasync ");
+                    case CallModifierNeverTail:
+                        fprintf(ar->f, "notail ");
+                        break;
+                    case CallModifierNeverInline:
+                        fprintf(ar->f, "noinline ");
+                        break;
+                    case CallModifierAlwaysTail:
+                        fprintf(ar->f, "tail ");
+                        break;
+                    case CallModifierAlwaysInline:
+                        fprintf(ar->f, "inline ");
+                        break;
+                    case CallModifierCompileTime:
+                        fprintf(ar->f, "comptime ");
+                        break;
+                    case CallModifierBuiltin:
+                        fprintf(ar->f, "@");
                         break;
                 }
                 AstNode *fn_ref_node = node->data.fn_call_expr.fn_ref_expr;
src/codegen.cpp
@@ -981,7 +981,7 @@ static void gen_panic(CodeGen *g, LLVMValueRef msg_arg, LLVMValueRef stack_trace
         msg_arg,
         stack_trace_arg,
     };
-    ZigLLVMBuildCall(g->builder, fn_val, args, 2, llvm_cc, ZigLLVM_FnInlineAuto, "");
+    ZigLLVMBuildCall(g->builder, fn_val, args, 2, llvm_cc, ZigLLVM_CallAttrAuto, "");
     if (!stack_trace_is_llvm_alloca) {
         // The stack trace argument is not in the stack of the caller, so
         // we'd like to set tail call here, but because slices (the type of msg_arg) are
@@ -1201,7 +1201,8 @@ static LLVMValueRef get_return_err_fn(CodeGen *g) {
 
     LLVMPositionBuilderAtEnd(g->builder, dest_non_null_block);
     LLVMValueRef args[] = { err_ret_trace_ptr, return_address };
-    ZigLLVMBuildCall(g->builder, add_error_return_trace_addr_fn_val, args, 2, get_llvm_cc(g, CallingConventionUnspecified), ZigLLVM_FnInlineAlways, "");
+    ZigLLVMBuildCall(g->builder, add_error_return_trace_addr_fn_val, args, 2,
+            get_llvm_cc(g, CallingConventionUnspecified), ZigLLVM_CallAttrAlwaysInline, "");
     LLVMBuildRetVoid(g->builder);
 
     LLVMPositionBuilderAtEnd(g->builder, prev_block);
@@ -1370,13 +1371,13 @@ static void gen_safety_crash_for_err(CodeGen *g, LLVMValueRef err_val, Scope *sc
             err_val,
         };
         call_instruction = ZigLLVMBuildCall(g->builder, safety_crash_err_fn, args, 2,
-                get_llvm_cc(g, CallingConventionUnspecified), ZigLLVM_FnInlineAuto, "");
+                get_llvm_cc(g, CallingConventionUnspecified), ZigLLVM_CallAttrAuto, "");
     } else {
         LLVMValueRef args[] = {
             err_val,
         };
         call_instruction = ZigLLVMBuildCall(g->builder, safety_crash_err_fn, args, 1,
-                get_llvm_cc(g, CallingConventionUnspecified), ZigLLVM_FnInlineAuto, "");
+                get_llvm_cc(g, CallingConventionUnspecified), ZigLLVM_CallAttrAuto, "");
     }
     if (!is_llvm_alloca) {
         LLVMSetTailCall(call_instruction, true);
@@ -2216,7 +2217,7 @@ static LLVMValueRef get_merge_err_ret_traces_fn_val(CodeGen *g) {
     LLVMValueRef addr_ptr = LLVMBuildInBoundsGEP(g->builder, src_ptr_val, &ptr_index, 1, "");
     LLVMValueRef this_addr_val = LLVMBuildLoad(g->builder, addr_ptr, "");
     LLVMValueRef args[] = {dest_stack_trace_ptr, this_addr_val};
-    ZigLLVMBuildCall(g->builder, add_error_return_trace_addr_fn_val, args, 2, get_llvm_cc(g, CallingConventionUnspecified), ZigLLVM_FnInlineAlways, "");
+    ZigLLVMBuildCall(g->builder, add_error_return_trace_addr_fn_val, args, 2, get_llvm_cc(g, CallingConventionUnspecified), ZigLLVM_CallAttrAlwaysInline, "");
     LLVMValueRef prev_frames_left = LLVMBuildLoad(g->builder, frames_left_ptr, "");
     LLVMValueRef new_frames_left = LLVMBuildNUWSub(g->builder, prev_frames_left, usize_one, "");
     LLVMValueRef done_bit = LLVMBuildICmp(g->builder, LLVMIntEQ, new_frames_left, usize_zero, "");
@@ -2253,7 +2254,7 @@ static LLVMValueRef ir_render_save_err_ret_addr(CodeGen *g, IrExecutable *execut
     LLVMValueRef my_err_trace_val = get_cur_err_ret_trace_val(g, save_err_ret_addr_instruction->base.scope,
             &is_llvm_alloca);
     ZigLLVMBuildCall(g->builder, return_err_fn, &my_err_trace_val, 1,
-            get_llvm_cc(g, CallingConventionUnspecified), ZigLLVM_FnInlineAuto, "");
+            get_llvm_cc(g, CallingConventionUnspecified), ZigLLVM_CallAttrAuto, "");
 
     ZigType *ret_type = g->cur_fn->type_entry->data.fn.fn_type_id.return_type;
     if (fn_is_async(g->cur_fn) && codegen_fn_has_err_ret_tracing_arg(g, ret_type)) {
@@ -2297,7 +2298,7 @@ static LLVMValueRef gen_resume(CodeGen *g, LLVMValueRef fn_val, LLVMValueRef tar
     LLVMValueRef arg_val = LLVMConstSub(LLVMConstAllOnes(usize_type_ref),
             LLVMConstInt(usize_type_ref, resume_id, false));
     LLVMValueRef args[] = {target_frame_ptr, arg_val};
-    return ZigLLVMBuildCall(g->builder, fn_val, args, 2, LLVMFastCallConv, ZigLLVM_FnInlineAuto, "");
+    return ZigLLVMBuildCall(g->builder, fn_val, args, 2, LLVMFastCallConv, ZigLLVM_CallAttrAuto, "");
 }
 
 static LLVMBasicBlockRef gen_suspend_begin(CodeGen *g, const char *name_hint) {
@@ -2424,7 +2425,7 @@ static void gen_async_return(CodeGen *g, IrInstructionReturn *instruction) {
             LLVMValueRef my_err_trace_val = get_cur_err_ret_trace_val(g, instruction->base.scope, &is_llvm_alloca);
             LLVMValueRef args[] = { dest_trace_ptr, my_err_trace_val };
             ZigLLVMBuildCall(g->builder, get_merge_err_ret_traces_fn_val(g), args, 2,
-                    get_llvm_cc(g, CallingConventionUnspecified), ZigLLVM_FnInlineAuto, "");
+                    get_llvm_cc(g, CallingConventionUnspecified), ZigLLVM_CallAttrAuto, "");
         }
     }
 
@@ -4142,16 +4143,28 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutable *executable, IrInstr
     fn_walk.data.call.gen_param_types = &gen_param_types;
     walk_function_params(g, fn_type, &fn_walk);
 
-    ZigLLVM_FnInline fn_inline;
-    switch (instruction->fn_inline) {
-        case FnInlineAuto:
-            fn_inline = ZigLLVM_FnInlineAuto;
+    ZigLLVM_CallAttr call_attr;
+    switch (instruction->modifier) {
+        case CallModifierBuiltin:
+        case CallModifierCompileTime:
+            zig_unreachable();
+        case CallModifierNone:
+        case CallModifierNoAsync:
+        case CallModifierAsync:
+            call_attr = ZigLLVM_CallAttrAuto;
             break;
-        case FnInlineAlways:
-            fn_inline = (instruction->fn_entry == nullptr) ? ZigLLVM_FnInlineAuto : ZigLLVM_FnInlineAlways;
+        case CallModifierNeverTail:
+            call_attr = ZigLLVM_CallAttrNeverTail;
             break;
-        case FnInlineNever:
-            fn_inline = ZigLLVM_FnInlineNever;
+        case CallModifierNeverInline:
+            call_attr = ZigLLVM_CallAttrNeverInline;
+            break;
+        case CallModifierAlwaysTail:
+            call_attr = ZigLLVM_CallAttrAlwaysTail;
+            break;
+        case CallModifierAlwaysInline:
+            ir_assert(instruction->fn_entry != nullptr, &instruction->base);
+            call_attr = ZigLLVM_CallAttrAlwaysInline;
             break;
     }
 
@@ -4257,7 +4270,7 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutable *executable, IrInstr
 
     if (instruction->new_stack == nullptr || instruction->is_async_call_builtin) {
         result = ZigLLVMBuildCall(g->builder, fn_val,
-                gen_param_values.items, (unsigned)gen_param_values.length, llvm_cc, fn_inline, "");
+                gen_param_values.items, (unsigned)gen_param_values.length, llvm_cc, call_attr, "");
     } else if (instruction->modifier == CallModifierAsync) {
         zig_panic("TODO @asyncCall of non-async function");
     } else {
@@ -4269,7 +4282,7 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutable *executable, IrInstr
         }
         gen_set_stack_pointer(g, new_stack_addr);
         result = ZigLLVMBuildCall(g->builder, fn_val,
-                gen_param_values.items, (unsigned)gen_param_values.length, llvm_cc, fn_inline, "");
+                gen_param_values.items, (unsigned)gen_param_values.length, llvm_cc, call_attr, "");
         if (src_return_type->id != ZigTypeIdUnreachable) {
             LLVMValueRef stackrestore_fn_val = get_stackrestore_fn_val(g);
             LLVMBuildCall(g->builder, stackrestore_fn_val, &old_stack_ref, 1, "");
@@ -4947,7 +4960,7 @@ static LLVMValueRef ir_render_enum_tag_name(CodeGen *g, IrExecutable *executable
 
     LLVMValueRef enum_tag_value = ir_llvm_value(g, instruction->target);
     return ZigLLVMBuildCall(g->builder, enum_name_function, &enum_tag_value, 1,
-            get_llvm_cc(g, CallingConventionUnspecified), ZigLLVM_FnInlineAuto, "");
+            get_llvm_cc(g, CallingConventionUnspecified), ZigLLVM_CallAttrAuto, "");
 }
 
 static LLVMValueRef ir_render_field_parent_ptr(CodeGen *g, IrExecutable *executable,
@@ -5903,7 +5916,7 @@ static LLVMValueRef gen_await_early_return(CodeGen *g, IrInstruction *source_ins
         LLVMValueRef dest_trace_ptr = get_cur_err_ret_trace_val(g, source_instr->scope, &is_llvm_alloca);
         LLVMValueRef args[] = { dest_trace_ptr, src_trace_ptr };
         ZigLLVMBuildCall(g->builder, get_merge_err_ret_traces_fn_val(g), args, 2,
-                get_llvm_cc(g, CallingConventionUnspecified), ZigLLVM_FnInlineAuto, "");
+                get_llvm_cc(g, CallingConventionUnspecified), ZigLLVM_CallAttrAuto, "");
     }
     if (non_async && type_has_bits(result_type)) {
         LLVMValueRef result_ptr = (result_loc == nullptr) ? their_result_ptr : result_loc;
@@ -6137,6 +6150,7 @@ static LLVMValueRef ir_render_instruction(CodeGen *g, IrExecutable *executable,
         case IrInstructionIdLoadPtr:
         case IrInstructionIdHasDecl:
         case IrInstructionIdUndeclaredIdent:
+        case IrInstructionIdCallExtra:
         case IrInstructionIdCallSrc:
         case IrInstructionIdAllocaSrc:
         case IrInstructionIdEndExpr:
@@ -8146,6 +8160,7 @@ static void define_builtin_fns(CodeGen *g) {
     create_builtin_fn(g, BuiltinFnIdFrameAddress, "frameAddress", 0);
     create_builtin_fn(g, BuiltinFnIdFrameSize, "frameSize", 1);
     create_builtin_fn(g, BuiltinFnIdAs, "as", 2);
+    create_builtin_fn(g, BuiltinFnIdCall, "call", 3);
 }
 
 static const char *bool_to_str(bool b) {
src/ir.cpp
@@ -265,6 +265,7 @@ static IrInstruction *ir_analyze_struct_field_ptr(IrAnalyze *ira, IrInstruction
 static IrInstruction *ir_analyze_inferred_field_ptr(IrAnalyze *ira, Buf *field_name,
     IrInstruction *source_instr, IrInstruction *container_ptr, ZigType *container_type);
 static ResultLoc *no_result_loc(void);
+static IrInstruction *ir_analyze_test_non_null(IrAnalyze *ira, IrInstruction *source_inst, IrInstruction *value);
 
 static void destroy_instruction(IrInstruction *inst) {
 #ifdef ZIG_ENABLE_MEM_PROFILE
@@ -289,6 +290,8 @@ static void destroy_instruction(IrInstruction *inst) {
             return destroy(reinterpret_cast<IrInstructionCast *>(inst), name);
         case IrInstructionIdCallSrc:
             return destroy(reinterpret_cast<IrInstructionCallSrc *>(inst), name);
+        case IrInstructionIdCallExtra:
+            return destroy(reinterpret_cast<IrInstructionCallExtra *>(inst), name);
         case IrInstructionIdCallGen:
             return destroy(reinterpret_cast<IrInstructionCallGen *>(inst), name);
         case IrInstructionIdUnOp:
@@ -705,6 +708,13 @@ static bool is_opt_err_set(ZigType *ty) {
         (ty->id == ZigTypeIdOptional && ty->data.maybe.child_type->id == ZigTypeIdErrorSet);
 }
 
+static bool is_tuple(ZigType *type) {
+    return type->id == ZigTypeIdStruct && type->data.structure.decl_node != nullptr &&
+        type->data.structure.decl_node->type == NodeTypeContainerInitExpr &&
+        (type->data.structure.decl_node->data.container_init_expr.kind == ContainerInitKindArray ||
+         type->data.structure.decl_node->data.container_init_expr.entries.length == 0);
+}
+
 static bool is_slice(ZigType *type) {
     return type->id == ZigTypeIdStruct && type->data.structure.is_slice;
 }
@@ -968,6 +978,10 @@ static constexpr IrInstructionId ir_instruction_id(IrInstructionCallSrc *) {
     return IrInstructionIdCallSrc;
 }
 
+static constexpr IrInstructionId ir_instruction_id(IrInstructionCallExtra *) {
+    return IrInstructionIdCallExtra;
+}
+
 static constexpr IrInstructionId ir_instruction_id(IrInstructionCallGen *) {
     return IrInstructionIdCallGen;
 }
@@ -1891,30 +1905,42 @@ static IrInstruction *ir_build_union_field_ptr(IrBuilder *irb, Scope *scope, Ast
     return &instruction->base;
 }
 
+static IrInstruction *ir_build_call_extra(IrBuilder *irb, Scope *scope, AstNode *source_node,
+        IrInstruction *options, IrInstruction *fn_ref, IrInstruction *args, ResultLoc *result_loc)
+{
+    IrInstructionCallExtra *call_instruction = ir_build_instruction<IrInstructionCallExtra>(irb, scope, source_node);
+    call_instruction->options = options;
+    call_instruction->fn_ref = fn_ref;
+    call_instruction->args = args;
+    call_instruction->result_loc = result_loc;
+
+    ir_ref_instruction(options, irb->current_basic_block);
+    ir_ref_instruction(fn_ref, irb->current_basic_block);
+    ir_ref_instruction(args, irb->current_basic_block);
+
+    return &call_instruction->base;
+}
+
 static IrInstruction *ir_build_call_src(IrBuilder *irb, Scope *scope, AstNode *source_node,
         ZigFn *fn_entry, IrInstruction *fn_ref, size_t arg_count, IrInstruction **args,
-        bool is_comptime, FnInline fn_inline, CallModifier modifier, bool is_async_call_builtin,
+        IrInstruction *ret_ptr, CallModifier modifier, bool is_async_call_builtin,
         IrInstruction *new_stack, ResultLoc *result_loc)
 {
     IrInstructionCallSrc *call_instruction = ir_build_instruction<IrInstructionCallSrc>(irb, scope, source_node);
     call_instruction->fn_entry = fn_entry;
     call_instruction->fn_ref = fn_ref;
-    call_instruction->is_comptime = is_comptime;
-    call_instruction->fn_inline = fn_inline;
     call_instruction->args = args;
     call_instruction->arg_count = arg_count;
     call_instruction->modifier = modifier;
     call_instruction->is_async_call_builtin = is_async_call_builtin;
     call_instruction->new_stack = new_stack;
     call_instruction->result_loc = result_loc;
+    call_instruction->ret_ptr = ret_ptr;
 
     if (fn_ref != nullptr) ir_ref_instruction(fn_ref, irb->current_basic_block);
     for (size_t i = 0; i < arg_count; i += 1)
         ir_ref_instruction(args[i], irb->current_basic_block);
-    if (modifier == CallModifierAsync && new_stack != nullptr) {
-        // in this case the arg at the end is the return pointer
-        ir_ref_instruction(args[arg_count], irb->current_basic_block);
-    }
+    if (ret_ptr != nullptr) ir_ref_instruction(ret_ptr, irb->current_basic_block);
     if (new_stack != nullptr) ir_ref_instruction(new_stack, irb->current_basic_block);
 
     return &call_instruction->base;
@@ -1922,7 +1948,7 @@ static IrInstruction *ir_build_call_src(IrBuilder *irb, Scope *scope, AstNode *s
 
 static IrInstructionCallGen *ir_build_call_gen(IrAnalyze *ira, IrInstruction *source_instruction,
         ZigFn *fn_entry, IrInstruction *fn_ref, size_t arg_count, IrInstruction **args,
-        FnInline fn_inline, CallModifier modifier, IrInstruction *new_stack, bool is_async_call_builtin,
+        CallModifier modifier, IrInstruction *new_stack, bool is_async_call_builtin,
         IrInstruction *result_loc, ZigType *return_type)
 {
     IrInstructionCallGen *call_instruction = ir_build_instruction<IrInstructionCallGen>(&ira->new_irb,
@@ -1930,7 +1956,6 @@ static IrInstructionCallGen *ir_build_call_gen(IrAnalyze *ira, IrInstruction *so
     call_instruction->base.value->type = return_type;
     call_instruction->fn_entry = fn_entry;
     call_instruction->fn_ref = fn_ref;
-    call_instruction->fn_inline = fn_inline;
     call_instruction->args = args;
     call_instruction->arg_count = arg_count;
     call_instruction->modifier = modifier;
@@ -5054,10 +5079,7 @@ static IrInstruction *ir_gen_async_call(IrBuilder *irb, Scope *scope, AstNode *a
         return fn_ref;
 
     size_t arg_count = call_node->data.fn_call_expr.params.length - arg_offset;
-
-    // last "arg" is return pointer
-    IrInstruction **args = allocate<IrInstruction*>(arg_count + 1);
-
+    IrInstruction **args = allocate<IrInstruction*>(arg_count);
     for (size_t i = 0; i < arg_count; i += 1) {
         AstNode *arg_node = call_node->data.fn_call_expr.params.at(i + arg_offset);
         IrInstruction *arg = ir_gen_node(irb, arg_node, scope);
@@ -5066,12 +5088,10 @@ static IrInstruction *ir_gen_async_call(IrBuilder *irb, Scope *scope, AstNode *a
         args[i] = arg;
     }
 
-    args[arg_count] = ret_ptr;
-
     CallModifier modifier = (await_node == nullptr) ? CallModifierAsync : CallModifierNone;
     bool is_async_call_builtin = true;
-    IrInstruction *call = ir_build_call_src(irb, scope, call_node, nullptr, fn_ref, arg_count, args, false,
-            FnInlineAuto, modifier, is_async_call_builtin, bytes, result_loc);
+    IrInstruction *call = ir_build_call_src(irb, scope, call_node, nullptr, fn_ref, arg_count, args,
+            ret_ptr, modifier, is_async_call_builtin, bytes, result_loc);
     return ir_lval_wrap(irb, scope, call, lval, result_loc);
 }
 
@@ -6015,10 +6035,11 @@ static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, Scope *scope, AstNo
                     if (args[i] == irb->codegen->invalid_instruction)
                         return args[i];
                 }
-                FnInline fn_inline = (builtin_fn->id == BuiltinFnIdInlineCall) ? FnInlineAlways : FnInlineNever;
+                CallModifier modifier = (builtin_fn->id == BuiltinFnIdInlineCall) ?
+                    CallModifierAlwaysInline : CallModifierNeverInline;
 
-                IrInstruction *call = ir_build_call_src(irb, scope, node, nullptr, fn_ref, arg_count, args, false,
-                        fn_inline, CallModifierNone, false, nullptr, result_loc);
+                IrInstruction *call = ir_build_call_src(irb, scope, node, nullptr, fn_ref, arg_count, args,
+                        nullptr, modifier, false, nullptr, result_loc);
                 return ir_lval_wrap(irb, scope, call, lval, result_loc);
             }
         case BuiltinFnIdNewStackCall:
@@ -6050,10 +6071,36 @@ static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, Scope *scope, AstNo
                         return args[i];
                 }
 
-                IrInstruction *call = ir_build_call_src(irb, scope, node, nullptr, fn_ref, arg_count, args, false,
-                        FnInlineAuto, CallModifierNone, false, new_stack, result_loc);
+                IrInstruction *call = ir_build_call_src(irb, scope, node, nullptr, fn_ref, arg_count, args,
+                        nullptr, CallModifierNone, false, new_stack, result_loc);
                 return ir_lval_wrap(irb, scope, call, lval, result_loc);
             }
+        case BuiltinFnIdCall: {
+            // Cast the options parameter to the options type
+            ZigType *options_type = get_builtin_type(irb->codegen, "CallOptions");
+            IrInstruction *options_type_inst = ir_build_const_type(irb, scope, node, options_type);
+            ResultLocCast *result_loc_cast = ir_build_cast_result_loc(irb, options_type_inst, no_result_loc());
+
+            AstNode *options_node = node->data.fn_call_expr.params.at(0);
+            IrInstruction *options_inner = ir_gen_node_extra(irb, options_node, scope,
+                    LValNone, &result_loc_cast->base);
+            if (options_inner == irb->codegen->invalid_instruction)
+                return options_inner;
+            IrInstruction *options = ir_build_implicit_cast(irb, scope, options_node, options_inner, result_loc_cast);
+
+            AstNode *fn_ref_node = node->data.fn_call_expr.params.at(1);
+            IrInstruction *fn_ref = ir_gen_node(irb, fn_ref_node, scope);
+            if (fn_ref == irb->codegen->invalid_instruction)
+                return fn_ref;
+
+            AstNode *args_node = node->data.fn_call_expr.params.at(2);
+            IrInstruction *args = ir_gen_node(irb, args_node, scope);
+            if (args == irb->codegen->invalid_instruction)
+                return args;
+
+            IrInstruction *call = ir_build_call_extra(irb, scope, node, options, fn_ref, args, result_loc);
+            return ir_lval_wrap(irb, scope, call, lval, result_loc);
+        }
         case BuiltinFnIdAsyncCall:
             return ir_gen_async_call(irb, scope, nullptr, node, lval, result_loc);
         case BuiltinFnIdTypeId:
@@ -6395,8 +6442,8 @@ static IrInstruction *ir_gen_fn_call(IrBuilder *irb, Scope *scope, AstNode *node
         args[i] = ir_build_implicit_cast(irb, scope, arg_node, arg, result_loc_cast);
     }
 
-    IrInstruction *fn_call = ir_build_call_src(irb, scope, node, nullptr, fn_ref, arg_count, args, false,
-            FnInlineAuto, node->data.fn_call_expr.modifier, false, nullptr, result_loc);
+    IrInstruction *fn_call = ir_build_call_src(irb, scope, node, nullptr, fn_ref, arg_count, args, nullptr,
+            node->data.fn_call_expr.modifier, false, nullptr, result_loc);
     return ir_lval_wrap(irb, scope, fn_call, lval, result_loc);
 }
 
@@ -14102,9 +14149,7 @@ static bool ir_resolve_atomic_order(IrAnalyze *ira, IrInstruction *value, Atomic
     if (type_is_invalid(value->value->type))
         return false;
 
-    ZigValue *atomic_order_val = get_builtin_value(ira->codegen, "AtomicOrder");
-    assert(atomic_order_val->type->id == ZigTypeIdMetaType);
-    ZigType *atomic_order_type = atomic_order_val->data.x_type;
+    ZigType *atomic_order_type = get_builtin_type(ira->codegen, "AtomicOrder");
 
     IrInstruction *casted_value = ir_implicit_cast(ira, value, atomic_order_type);
     if (type_is_invalid(casted_value->value->type))
@@ -14122,9 +14167,7 @@ static bool ir_resolve_atomic_rmw_op(IrAnalyze *ira, IrInstruction *value, Atomi
     if (type_is_invalid(value->value->type))
         return false;
 
-    ZigValue *atomic_rmw_op_val = get_builtin_value(ira->codegen, "AtomicRmwOp");
-    assert(atomic_rmw_op_val->type->id == ZigTypeIdMetaType);
-    ZigType *atomic_rmw_op_type = atomic_rmw_op_val->data.x_type;
+    ZigType *atomic_rmw_op_type = get_builtin_type(ira->codegen, "AtomicRmwOp");
 
     IrInstruction *casted_value = ir_implicit_cast(ira, value, atomic_rmw_op_type);
     if (type_is_invalid(casted_value->value->type))
@@ -14142,9 +14185,7 @@ static bool ir_resolve_global_linkage(IrAnalyze *ira, IrInstruction *value, Glob
     if (type_is_invalid(value->value->type))
         return false;
 
-    ZigValue *global_linkage_val = get_builtin_value(ira->codegen, "GlobalLinkage");
-    assert(global_linkage_val->type->id == ZigTypeIdMetaType);
-    ZigType *global_linkage_type = global_linkage_val->data.x_type;
+    ZigType *global_linkage_type = get_builtin_type(ira->codegen, "GlobalLinkage");
 
     IrInstruction *casted_value = ir_implicit_cast(ira, value, global_linkage_type);
     if (type_is_invalid(casted_value->value->type))
@@ -14162,9 +14203,7 @@ static bool ir_resolve_float_mode(IrAnalyze *ira, IrInstruction *value, FloatMod
     if (type_is_invalid(value->value->type))
         return false;
 
-    ZigValue *float_mode_val = get_builtin_value(ira->codegen, "FloatMode");
-    assert(float_mode_val->type->id == ZigTypeIdMetaType);
-    ZigType *float_mode_type = float_mode_val->data.x_type;
+    ZigType *float_mode_type = get_builtin_type(ira->codegen, "FloatMode");
 
     IrInstruction *casted_value = ir_implicit_cast(ira, value, float_mode_type);
     if (type_is_invalid(casted_value->value->type))
@@ -16972,11 +17011,11 @@ static IrInstruction *ir_analyze_instruction_reset_result(IrAnalyze *ira, IrInst
     return ir_const_void(ira, &instruction->base);
 }
 
-static IrInstruction *get_async_call_result_loc(IrAnalyze *ira, IrInstructionCallSrc *call_instruction,
-        ZigType *fn_ret_type)
+static IrInstruction *get_async_call_result_loc(IrAnalyze *ira, IrInstruction *source_instr,
+        ZigType *fn_ret_type, bool is_async_call_builtin, IrInstruction **args_ptr, size_t args_len,
+        IrInstruction *ret_ptr_uncasted)
 {
-    ir_assert(call_instruction->is_async_call_builtin, &call_instruction->base);
-    IrInstruction *ret_ptr_uncasted = call_instruction->args[call_instruction->arg_count]->child;
+    ir_assert(is_async_call_builtin, source_instr);
     if (type_is_invalid(ret_ptr_uncasted->value->type))
         return ira->codegen->invalid_instruction;
     if (ret_ptr_uncasted->value->type->id == ZigTypeIdVoid) {
@@ -16986,9 +17025,10 @@ static IrInstruction *get_async_call_result_loc(IrAnalyze *ira, IrInstructionCal
     return ir_implicit_cast(ira, ret_ptr_uncasted, get_pointer_to_type(ira->codegen, fn_ret_type, false));
 }
 
-static IrInstruction *ir_analyze_async_call(IrAnalyze *ira, IrInstructionCallSrc *call_instruction, ZigFn *fn_entry,
+static IrInstruction *ir_analyze_async_call(IrAnalyze *ira, IrInstruction *source_instr, ZigFn *fn_entry,
         ZigType *fn_type, IrInstruction *fn_ref, IrInstruction **casted_args, size_t arg_count,
-        IrInstruction *casted_new_stack)
+        IrInstruction *casted_new_stack, bool is_async_call_builtin, IrInstruction *ret_ptr_uncasted,
+        ResultLoc *call_result_loc)
 {
     if (fn_entry == nullptr) {
         if (fn_type->data.fn.fn_type_id.cc != CallingConventionAsync) {
@@ -17003,19 +17043,20 @@ static IrInstruction *ir_analyze_async_call(IrAnalyze *ira, IrInstructionCallSrc
     }
     if (casted_new_stack != nullptr) {
         ZigType *fn_ret_type = fn_type->data.fn.fn_type_id.return_type;
-        IrInstruction *ret_ptr = get_async_call_result_loc(ira, call_instruction, fn_ret_type);
+        IrInstruction *ret_ptr = get_async_call_result_loc(ira, source_instr, fn_ret_type, is_async_call_builtin,
+                casted_args, arg_count, ret_ptr_uncasted);
         if (ret_ptr != nullptr && type_is_invalid(ret_ptr->value->type))
             return ira->codegen->invalid_instruction;
 
         ZigType *anyframe_type = get_any_frame_type(ira->codegen, fn_ret_type);
 
-        IrInstructionCallGen *call_gen = ir_build_call_gen(ira, &call_instruction->base, fn_entry, fn_ref,
-                arg_count, casted_args, FnInlineAuto, CallModifierAsync, casted_new_stack,
-                call_instruction->is_async_call_builtin, ret_ptr, anyframe_type);
+        IrInstructionCallGen *call_gen = ir_build_call_gen(ira, source_instr, fn_entry, fn_ref,
+                arg_count, casted_args, CallModifierAsync, casted_new_stack,
+                is_async_call_builtin, ret_ptr, anyframe_type);
         return &call_gen->base;
     } else {
         ZigType *frame_type = get_fn_frame_type(ira->codegen, fn_entry);
-        IrInstruction *result_loc = ir_resolve_result(ira, &call_instruction->base, call_instruction->result_loc,
+        IrInstruction *result_loc = ir_resolve_result(ira, source_instr, call_result_loc,
                 frame_type, nullptr, true, true, false);
         if (type_is_invalid(result_loc->value->type) || instr_is_unreachable(result_loc)) {
             return result_loc;
@@ -17023,9 +17064,9 @@ static IrInstruction *ir_analyze_async_call(IrAnalyze *ira, IrInstructionCallSrc
         result_loc = ir_implicit_cast(ira, result_loc, get_pointer_to_type(ira->codegen, frame_type, false));
         if (type_is_invalid(result_loc->value->type))
             return ira->codegen->invalid_instruction;
-        return &ir_build_call_gen(ira, &call_instruction->base, fn_entry, fn_ref, arg_count,
-                casted_args, FnInlineAuto, CallModifierAsync, casted_new_stack,
-                call_instruction->is_async_call_builtin, result_loc, frame_type)->base;
+        return &ir_build_call_gen(ira, source_instr, fn_entry, fn_ref, arg_count,
+                casted_args, CallModifierAsync, casted_new_stack,
+                is_async_call_builtin, result_loc, frame_type)->base;
     }
 }
 static bool ir_analyze_fn_call_inline_arg(IrAnalyze *ira, AstNode *fn_proto_node,
@@ -17417,25 +17458,21 @@ static IrInstruction *ir_analyze_store_ptr(IrAnalyze *ira, IrInstruction *source
     return &store_ptr->base;
 }
 
-static IrInstruction *analyze_casted_new_stack(IrAnalyze *ira, IrInstructionCallSrc *call_instruction,
-        ZigFn *fn_entry)
+static IrInstruction *analyze_casted_new_stack(IrAnalyze *ira, IrInstruction *source_instr,
+        IrInstruction *new_stack, bool is_async_call_builtin, ZigFn *fn_entry)
 {
-    if (call_instruction->new_stack == nullptr)
+    if (new_stack == nullptr)
         return nullptr;
 
-    if (!call_instruction->is_async_call_builtin &&
+    if (!is_async_call_builtin &&
         arch_stack_pointer_register_name(ira->codegen->zig_target->arch) == nullptr)
     {
-        ir_add_error(ira, &call_instruction->base,
+        ir_add_error(ira, source_instr,
             buf_sprintf("target arch '%s' does not support @newStackCall",
                 target_arch_name(ira->codegen->zig_target->arch)));
     }
 
-    IrInstruction *new_stack = call_instruction->new_stack->child;
-    if (type_is_invalid(new_stack->value->type))
-        return ira->codegen->invalid_instruction;
-
-    if (call_instruction->is_async_call_builtin &&
+    if (is_async_call_builtin &&
         fn_entry != nullptr && new_stack->value->type->id == ZigTypeIdPointer &&
         new_stack->value->type->data.pointer.child_type->id == ZigTypeIdFnFrame)
     {
@@ -17451,9 +17488,11 @@ static IrInstruction *analyze_casted_new_stack(IrAnalyze *ira, IrInstructionCall
     }
 }
 
-static IrInstruction *ir_analyze_fn_call(IrAnalyze *ira, IrInstructionCallSrc *call_instruction,
+static IrInstruction *ir_analyze_fn_call(IrAnalyze *ira, IrInstruction *source_instr,
     ZigFn *fn_entry, ZigType *fn_type, IrInstruction *fn_ref,
-    IrInstruction *first_arg_ptr, bool comptime_fn_call, FnInline fn_inline)
+    IrInstruction *first_arg_ptr, CallModifier modifier,
+    IrInstruction *new_stack, bool is_async_call_builtin,
+    IrInstruction **args_ptr, size_t args_len, IrInstruction *ret_ptr, ResultLoc *call_result_loc)
 {
     Error err;
     FnTypeId *fn_type_id = &fn_type->data.fn.fn_type_id;
@@ -17469,16 +17508,16 @@ static IrInstruction *ir_analyze_fn_call(IrAnalyze *ira, IrInstructionCallSrc *c
     }
     size_t src_param_count = fn_type_id->param_count - var_args_1_or_0;
 
-    size_t call_param_count = call_instruction->arg_count + first_arg_1_or_0;
-    for (size_t i = 0; i < call_instruction->arg_count; i += 1) {
-        ZigValue *arg_tuple_value = call_instruction->args[i]->child->value;
+    size_t call_param_count = args_len + first_arg_1_or_0;
+    for (size_t i = 0; i < args_len; i += 1) {
+        ZigValue *arg_tuple_value = args_ptr[i]->value;
         if (arg_tuple_value->type->id == ZigTypeIdArgTuple) {
             call_param_count -= 1;
             call_param_count += arg_tuple_value->data.x_arg_tuple.end_index -
                 arg_tuple_value->data.x_arg_tuple.start_index;
         }
     }
-    AstNode *source_node = call_instruction->base.source_node;
+    AstNode *source_node = source_instr->source_node;
 
     AstNode *fn_proto_node = fn_entry ? fn_entry->proto_node : nullptr;;
 
@@ -17511,14 +17550,14 @@ static IrInstruction *ir_analyze_fn_call(IrAnalyze *ira, IrInstructionCallSrc *c
         return ira->codegen->invalid_instruction;
     }
 
-    if (comptime_fn_call) {
+    if (modifier == CallModifierCompileTime) {
         // No special handling is needed for compile time evaluation of generic functions.
         if (!fn_entry || fn_entry->body_node == nullptr) {
             ir_add_error(ira, fn_ref, buf_sprintf("unable to evaluate constant expression"));
             return ira->codegen->invalid_instruction;
         }
 
-        if (!ir_emit_backward_branch(ira, &call_instruction->base))
+        if (!ir_emit_backward_branch(ira, source_instr))
             return ira->codegen->invalid_instruction;
 
         // Fork a scope of the function with known values for the parameters.
@@ -17550,16 +17589,14 @@ static IrInstruction *ir_analyze_fn_call(IrAnalyze *ira, IrInstructionCallSrc *c
         }
 
         if (fn_proto_node->data.fn_proto.is_var_args) {
-            ir_add_error(ira, &call_instruction->base,
+            ir_add_error(ira, source_instr,
                     buf_sprintf("compiler bug: unable to call var args function at compile time. https://github.com/ziglang/zig/issues/313"));
             return ira->codegen->invalid_instruction;
         }
 
 
-        for (size_t call_i = 0; call_i < call_instruction->arg_count; call_i += 1) {
-            IrInstruction *old_arg = call_instruction->args[call_i]->child;
-            if (type_is_invalid(old_arg->value->type))
-                return ira->codegen->invalid_instruction;
+        for (size_t call_i = 0; call_i < args_len; call_i += 1) {
+            IrInstruction *old_arg = args_ptr[call_i];
 
             if (!ir_analyze_fn_call_inline_arg(ira, fn_proto_node, old_arg, &exec_scope, &next_proto_i))
                 return ira->codegen->invalid_instruction;
@@ -17593,7 +17630,7 @@ static IrInstruction *ir_analyze_fn_call(IrAnalyze *ira, IrInstructionCallSrc *c
             AstNode *body_node = fn_entry->body_node;
             result = ir_eval_const_value(ira->codegen, exec_scope, body_node, return_type,
                 ira->new_irb.exec->backward_branch_count, ira->new_irb.exec->backward_branch_quota, fn_entry,
-                nullptr, call_instruction->base.source_node, nullptr, ira->new_irb.exec, return_type_node,
+                nullptr, source_instr->source_node, nullptr, ira->new_irb.exec, return_type_node,
                 UndefOk);
 
             if (inferred_err_set_type != nullptr) {
@@ -17623,24 +17660,21 @@ static IrInstruction *ir_analyze_fn_call(IrAnalyze *ira, IrInstructionCallSrc *c
             }
         }
 
-        IrInstruction *new_instruction = ir_const_move(ira, &call_instruction->base, result);
+        IrInstruction *new_instruction = ir_const_move(ira, source_instr, result);
         return ir_finish_anal(ira, new_instruction);
     }
 
     if (fn_type->data.fn.is_generic) {
         if (!fn_entry) {
-            ir_add_error(ira, call_instruction->fn_ref,
+            ir_add_error(ira, fn_ref,
                 buf_sprintf("calling a generic function requires compile-time known function value"));
             return ira->codegen->invalid_instruction;
         }
 
         // Count the arguments of the function type id we are creating
         size_t new_fn_arg_count = first_arg_1_or_0;
-        for (size_t call_i = 0; call_i < call_instruction->arg_count; call_i += 1) {
-            IrInstruction *arg = call_instruction->args[call_i]->child;
-            if (type_is_invalid(arg->value->type))
-                return ira->codegen->invalid_instruction;
-
+        for (size_t call_i = 0; call_i < args_len; call_i += 1) {
+            IrInstruction *arg = args_ptr[call_i];
             if (arg->value->type->id == ZigTypeIdArgTuple) {
                 new_fn_arg_count += arg->value->data.x_arg_tuple.end_index - arg->value->data.x_arg_tuple.start_index;
             } else {
@@ -17702,10 +17736,8 @@ static IrInstruction *ir_analyze_fn_call(IrAnalyze *ira, IrInstructionCallSrc *c
 
         ZigFn *parent_fn_entry = exec_fn_entry(ira->new_irb.exec);
         assert(parent_fn_entry);
-        for (size_t call_i = 0; call_i < call_instruction->arg_count; call_i += 1) {
-            IrInstruction *arg = call_instruction->args[call_i]->child;
-            if (type_is_invalid(arg->value->type))
-                return ira->codegen->invalid_instruction;
+        for (size_t call_i = 0; call_i < args_len; call_i += 1) {
+            IrInstruction *arg = args_ptr[call_i];
 
             if (arg->value->type->id == ZigTypeIdArgTuple) {
                 for (size_t arg_tuple_i = arg->value->data.x_arg_tuple.start_index;
@@ -17804,8 +17836,9 @@ static IrInstruction *ir_analyze_fn_call(IrAnalyze *ira, IrInstructionCallSrc *c
             switch (type_requires_comptime(ira->codegen, specified_return_type)) {
             case ReqCompTimeYes:
                 // 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, FnInlineAuto);
+                return ir_analyze_fn_call(ira, source_instr, fn_entry, fn_type, fn_ref, first_arg_ptr,
+                        CallModifierCompileTime, new_stack, is_async_call_builtin, args_ptr, args_len,
+                        ret_ptr, call_result_loc);
             case ReqCompTimeInvalid:
                 return ira->codegen->invalid_instruction;
             case ReqCompTimeNo:
@@ -17823,9 +17856,9 @@ static IrInstruction *ir_analyze_fn_call(IrAnalyze *ira, IrInstructionCallSrc *c
             if (type_is_invalid(impl_fn->type_entry))
                 return ira->codegen->invalid_instruction;
 
-            impl_fn->ir_executable->source_node = call_instruction->base.source_node;
+            impl_fn->ir_executable->source_node = source_instr->source_node;
             impl_fn->ir_executable->parent_exec = ira->new_irb.exec;
-            impl_fn->analyzed_executable.source_node = call_instruction->base.source_node;
+            impl_fn->analyzed_executable.source_node = source_instr->source_node;
             impl_fn->analyzed_executable.parent_exec = ira->new_irb.exec;
             impl_fn->analyzed_executable.backward_branch_quota = ira->new_irb.exec->backward_branch_quota;
             impl_fn->analyzed_executable.is_generic_instantiation = true;
@@ -17839,32 +17872,35 @@ static IrInstruction *ir_analyze_fn_call(IrAnalyze *ira, IrInstructionCallSrc *c
             parent_fn_entry->calls_or_awaits_errorable_fn = true;
         }
 
-        IrInstruction *casted_new_stack = analyze_casted_new_stack(ira, call_instruction, impl_fn);
+        IrInstruction *casted_new_stack = analyze_casted_new_stack(ira, source_instr, new_stack,
+                is_async_call_builtin, impl_fn);
         if (casted_new_stack != nullptr && type_is_invalid(casted_new_stack->value->type))
             return ira->codegen->invalid_instruction;
 
         size_t impl_param_count = impl_fn_type_id->param_count;
-        if (call_instruction->modifier == CallModifierAsync) {
-            IrInstruction *result = ir_analyze_async_call(ira, call_instruction, impl_fn, impl_fn->type_entry,
-                    nullptr, casted_args, impl_param_count, casted_new_stack);
+        if (modifier == CallModifierAsync) {
+            IrInstruction *result = ir_analyze_async_call(ira, source_instr, impl_fn, impl_fn->type_entry,
+                nullptr, casted_args, impl_param_count, casted_new_stack, is_async_call_builtin, ret_ptr,
+                call_result_loc);
             return ir_finish_anal(ira, result);
         }
 
         IrInstruction *result_loc;
         if (handle_is_ptr(impl_fn_type_id->return_type)) {
-            result_loc = ir_resolve_result(ira, &call_instruction->base, call_instruction->result_loc,
+            result_loc = ir_resolve_result(ira, source_instr, call_result_loc,
                     impl_fn_type_id->return_type, nullptr, true, true, false);
             if (result_loc != nullptr) {
                 if (type_is_invalid(result_loc->value->type) || instr_is_unreachable(result_loc)) {
                     return result_loc;
                 }
                 if (!handle_is_ptr(result_loc->value->type->data.pointer.child_type)) {
-                    ir_reset_result(call_instruction->result_loc);
+                    ir_reset_result(call_result_loc);
                     result_loc = nullptr;
                 }
             }
-        } else if (call_instruction->is_async_call_builtin) {
-            result_loc = get_async_call_result_loc(ira, call_instruction, impl_fn_type_id->return_type);
+        } else if (is_async_call_builtin) {
+            result_loc = get_async_call_result_loc(ira, source_instr, impl_fn_type_id->return_type,
+                    is_async_call_builtin, args_ptr, args_len, ret_ptr);
             if (result_loc != nullptr && type_is_invalid(result_loc->value->type))
                 return ira->codegen->invalid_instruction;
         } else {
@@ -17873,18 +17909,17 @@ static IrInstruction *ir_analyze_fn_call(IrAnalyze *ira, IrInstructionCallSrc *c
 
         if (impl_fn_type_id->cc == CallingConventionAsync &&
             parent_fn_entry->inferred_async_node == nullptr &&
-            call_instruction->modifier != CallModifierNoAsync)
+            modifier != CallModifierNoAsync)
         {
             parent_fn_entry->inferred_async_node = fn_ref->source_node;
             parent_fn_entry->inferred_async_fn = impl_fn;
         }
 
-        IrInstructionCallGen *new_call_instruction = ir_build_call_gen(ira, &call_instruction->base,
-                impl_fn, nullptr, impl_param_count, casted_args, fn_inline,
-                call_instruction->modifier, casted_new_stack, call_instruction->is_async_call_builtin, result_loc,
-                impl_fn_type_id->return_type);
+        IrInstructionCallGen *new_call_instruction = ir_build_call_gen(ira, source_instr,
+                impl_fn, nullptr, impl_param_count, casted_args, modifier, casted_new_stack,
+                is_async_call_builtin, result_loc, impl_fn_type_id->return_type);
 
-        if (get_scope_typeof(call_instruction->base.scope) == nullptr) {
+        if (get_scope_typeof(source_instr->scope) == nullptr) {
             parent_fn_entry->call_list.append(new_call_instruction);
         }
 
@@ -17926,8 +17961,8 @@ static IrInstruction *ir_analyze_fn_call(IrAnalyze *ira, IrInstructionCallSrc *c
         casted_args[next_arg_index] = casted_arg;
         next_arg_index += 1;
     }
-    for (size_t call_i = 0; call_i < call_instruction->arg_count; call_i += 1) {
-        IrInstruction *old_arg = call_instruction->args[call_i]->child;
+    for (size_t call_i = 0; call_i < args_len; call_i += 1) {
+        IrInstruction *old_arg = args_ptr[call_i];
         if (type_is_invalid(old_arg->value->type))
             return ira->codegen->invalid_instruction;
 
@@ -17988,25 +18023,26 @@ static IrInstruction *ir_analyze_fn_call(IrAnalyze *ira, IrInstructionCallSrc *c
     if (type_is_invalid(return_type))
         return ira->codegen->invalid_instruction;
 
-    if (fn_entry != nullptr && fn_entry->fn_inline == FnInlineAlways && fn_inline == FnInlineNever) {
-        ir_add_error(ira, &call_instruction->base,
+    if (fn_entry != nullptr && fn_entry->fn_inline == FnInlineAlways && modifier == CallModifierNeverInline) {
+        ir_add_error(ira, source_instr,
             buf_sprintf("no-inline call of inline function"));
         return ira->codegen->invalid_instruction;
     }
 
-    IrInstruction *casted_new_stack = analyze_casted_new_stack(ira, call_instruction, fn_entry);
+    IrInstruction *casted_new_stack = analyze_casted_new_stack(ira, source_instr, new_stack,
+            is_async_call_builtin, fn_entry);
     if (casted_new_stack != nullptr && type_is_invalid(casted_new_stack->value->type))
         return ira->codegen->invalid_instruction;
 
-    if (call_instruction->modifier == CallModifierAsync) {
-        IrInstruction *result = ir_analyze_async_call(ira, call_instruction, fn_entry, fn_type, fn_ref,
-                casted_args, call_param_count, casted_new_stack);
+    if (modifier == CallModifierAsync) {
+        IrInstruction *result = ir_analyze_async_call(ira, source_instr, fn_entry, fn_type, fn_ref,
+                casted_args, call_param_count, casted_new_stack, is_async_call_builtin, ret_ptr, call_result_loc);
         return ir_finish_anal(ira, result);
     }
 
     if (fn_type_id->cc == CallingConventionAsync &&
         parent_fn_entry->inferred_async_node == nullptr &&
-        call_instruction->modifier != CallModifierNoAsync)
+        modifier != CallModifierNoAsync)
     {
         parent_fn_entry->inferred_async_node = fn_ref->source_node;
         parent_fn_entry->inferred_async_fn = fn_entry;
@@ -18014,41 +18050,163 @@ static IrInstruction *ir_analyze_fn_call(IrAnalyze *ira, IrInstructionCallSrc *c
 
     IrInstruction *result_loc;
     if (handle_is_ptr(return_type)) {
-        result_loc = ir_resolve_result(ira, &call_instruction->base, call_instruction->result_loc,
+        result_loc = ir_resolve_result(ira, source_instr, call_result_loc,
                 return_type, nullptr, true, true, false);
         if (result_loc != nullptr) {
             if (type_is_invalid(result_loc->value->type) || instr_is_unreachable(result_loc)) {
                 return result_loc;
             }
             if (!handle_is_ptr(result_loc->value->type->data.pointer.child_type)) {
-                ir_reset_result(call_instruction->result_loc);
+                ir_reset_result(call_result_loc);
                 result_loc = nullptr;
             }
         }
-    } else if (call_instruction->is_async_call_builtin) {
-        result_loc = get_async_call_result_loc(ira, call_instruction, return_type);
+    } else if (is_async_call_builtin) {
+        result_loc = get_async_call_result_loc(ira, source_instr, return_type, is_async_call_builtin,
+                args_ptr, args_len, ret_ptr);
         if (result_loc != nullptr && type_is_invalid(result_loc->value->type))
             return ira->codegen->invalid_instruction;
     } else {
         result_loc = nullptr;
     }
 
-    IrInstructionCallGen *new_call_instruction = ir_build_call_gen(ira, &call_instruction->base, fn_entry, fn_ref,
-            call_param_count, casted_args, fn_inline, call_instruction->modifier, casted_new_stack,
-            call_instruction->is_async_call_builtin, result_loc, return_type);
-    if (get_scope_typeof(call_instruction->base.scope) == nullptr) {
+    IrInstructionCallGen *new_call_instruction = ir_build_call_gen(ira, source_instr, fn_entry, fn_ref,
+            call_param_count, casted_args, modifier, casted_new_stack,
+            is_async_call_builtin, result_loc, return_type);
+    if (get_scope_typeof(source_instr->scope) == nullptr) {
         parent_fn_entry->call_list.append(new_call_instruction);
     }
     return ir_finish_anal(ira, &new_call_instruction->base);
 }
 
+static IrInstruction *ir_analyze_fn_call_src(IrAnalyze *ira, IrInstructionCallSrc *call_instruction,
+    ZigFn *fn_entry, ZigType *fn_type, IrInstruction *fn_ref,
+    IrInstruction *first_arg_ptr, CallModifier modifier)
+{
+    IrInstruction *new_stack = nullptr;
+    if (call_instruction->new_stack) {
+        new_stack = call_instruction->new_stack->child;
+        if (type_is_invalid(new_stack->value->type))
+            return ira->codegen->invalid_instruction;
+    }
+    IrInstruction **args_ptr = allocate<IrInstruction *>(call_instruction->arg_count, "IrInstruction *");
+    for (size_t i = 0; i < call_instruction->arg_count; i += 1) {
+        args_ptr[i] = call_instruction->args[i]->child;
+        if (type_is_invalid(args_ptr[i]->value->type))
+            return ira->codegen->invalid_instruction;
+    }
+    IrInstruction *ret_ptr = nullptr;
+    if (call_instruction->ret_ptr != nullptr) {
+        ret_ptr = call_instruction->ret_ptr->child;
+        if (type_is_invalid(ret_ptr->value->type))
+            return ira->codegen->invalid_instruction;
+    }
+    IrInstruction *result = ir_analyze_fn_call(ira, &call_instruction->base, fn_entry, fn_type, fn_ref,
+            first_arg_ptr, modifier, new_stack, call_instruction->is_async_call_builtin,
+            args_ptr, call_instruction->arg_count, ret_ptr, call_instruction->result_loc);
+    deallocate(args_ptr, call_instruction->arg_count, "IrInstruction *");
+    return result;
+}
+
+static IrInstruction *ir_analyze_instruction_call_extra(IrAnalyze *ira, IrInstructionCallExtra *instruction) {
+    IrInstruction *options = instruction->options->child;
+    if (type_is_invalid(options->value->type))
+        return ira->codegen->invalid_instruction;
+
+    IrInstruction *fn_ref = instruction->fn_ref->child;
+    if (type_is_invalid(fn_ref->value->type))
+        return ira->codegen->invalid_instruction;
+    ZigFn *fn = ir_resolve_fn(ira, fn_ref);
+    ZigType *fn_type = (fn != nullptr) ? fn->type_entry : fn_ref->value->type;
+
+    IrInstruction *args = instruction->args->child;
+    ZigType *args_type = args->value->type;
+    if (type_is_invalid(args_type))
+        return ira->codegen->invalid_instruction;
+
+    if (args_type->id != ZigTypeIdStruct) {
+        ir_add_error(ira, args,
+            buf_sprintf("expected tuple or struct, found '%s'", buf_ptr(&args_type->name)));
+        return ira->codegen->invalid_instruction;
+    }
+
+    IrInstruction **args_ptr = nullptr;
+    size_t args_len = 0;
+
+    if (is_tuple(args_type)) {
+        args_len = args_type->data.structure.src_field_count;
+        args_ptr = allocate<IrInstruction *>(args_len, "IrInstruction *");
+        for (size_t i = 0; i < args_len; i += 1) {
+            TypeStructField *arg_field = args_type->data.structure.fields[i];
+            args_ptr[i] = ir_analyze_struct_value_field_value(ira, &instruction->base, args, arg_field);
+            if (type_is_invalid(args_ptr[i]->value->type))
+                return ira->codegen->invalid_instruction;
+        }
+    } else {
+        ir_add_error(ira, args, buf_sprintf("TODO: struct args"));
+        return ira->codegen->invalid_instruction;
+    }
+
+    TypeStructField *modifier_field = find_struct_type_field(options->value->type, buf_create_from_str("modifier"));
+    ir_assert(modifier_field != nullptr, &instruction->base);
+    IrInstruction *modifier_inst = ir_analyze_struct_value_field_value(ira, &instruction->base, options, modifier_field);
+    ZigValue *modifier_val = ir_resolve_const(ira, modifier_inst, UndefBad);
+    if (modifier_val == nullptr)
+        return ira->codegen->invalid_instruction;
+    CallModifier modifier = (CallModifier)bigint_as_u32(&modifier_val->data.x_enum_tag);
+    if (modifier == CallModifierAsync) {
+        ir_add_error(ira, args, buf_sprintf("TODO: @call with async modifier"));
+        return ira->codegen->invalid_instruction;
+    }
+    if (ir_should_inline(ira->new_irb.exec, instruction->base.scope)) {
+        switch (modifier) {
+            case CallModifierBuiltin:
+                zig_unreachable();
+            case CallModifierAsync:
+                ir_add_error(ira, args, buf_sprintf("TODO: comptime @call with async modifier"));
+                return ira->codegen->invalid_instruction;
+            case CallModifierCompileTime:
+            case CallModifierNone:
+            case CallModifierAlwaysInline:
+            case CallModifierAlwaysTail:
+            case CallModifierNoAsync:
+                modifier = CallModifierCompileTime;
+                break;
+            case CallModifierNeverInline:
+                ir_add_error(ira, args,
+                    buf_sprintf("unable to perform 'never_inline' call at compile-time"));
+                return ira->codegen->invalid_instruction;
+            case CallModifierNeverTail:
+                ir_add_error(ira, args,
+                    buf_sprintf("unable to perform 'never_tail' call at compile-time"));
+                return ira->codegen->invalid_instruction;
+        }
+    }
+
+    TypeStructField *stack_field = find_struct_type_field(options->value->type, buf_create_from_str("stack"));
+    ir_assert(stack_field != nullptr, &instruction->base);
+    IrInstruction *stack = ir_analyze_struct_value_field_value(ira, &instruction->base, options, stack_field);
+    IrInstruction *stack_is_non_null_inst = ir_analyze_test_non_null(ira, &instruction->base, stack);
+    bool stack_is_non_null;
+    if (!ir_resolve_bool(ira, stack_is_non_null_inst, &stack_is_non_null))
+        return ira->codegen->invalid_instruction;
+    if (!stack_is_non_null)
+        stack = nullptr;
+
+    IrInstruction *result = ir_analyze_fn_call(ira, &instruction->base, fn, fn_type, fn_ref, nullptr,
+        modifier, stack, false, args_ptr, args_len, nullptr, instruction->result_loc);
+    deallocate(args_ptr, args_len, "IrInstruction *");
+    return result;
+}
+
 static IrInstruction *ir_analyze_instruction_call(IrAnalyze *ira, IrInstructionCallSrc *call_instruction) {
     IrInstruction *fn_ref = call_instruction->fn_ref->child;
     if (type_is_invalid(fn_ref->value->type))
         return ira->codegen->invalid_instruction;
 
-    bool is_comptime = call_instruction->is_comptime ||
+    bool is_comptime = (call_instruction->modifier == CallModifierCompileTime) ||
         ir_should_inline(ira->new_irb.exec, call_instruction->base.scope);
+    CallModifier modifier = is_comptime ? CallModifierCompileTime : call_instruction->modifier;
 
     if (is_comptime || instr_is_comptime(fn_ref)) {
         if (fn_ref->value->type->id == ZigTypeIdMetaType) {
@@ -18063,14 +18221,16 @@ static IrInstruction *ir_analyze_instruction_call(IrAnalyze *ira, IrInstructionC
         } else if (fn_ref->value->type->id == ZigTypeIdFn) {
             ZigFn *fn_table_entry = ir_resolve_fn(ira, fn_ref);
             ZigType *fn_type = fn_table_entry ? fn_table_entry->type_entry : fn_ref->value->type;
-            return ir_analyze_fn_call(ira, call_instruction, fn_table_entry, fn_type,
-                fn_ref, nullptr, is_comptime, call_instruction->fn_inline);
+            CallModifier modifier = is_comptime ? CallModifierCompileTime : call_instruction->modifier;
+            return ir_analyze_fn_call_src(ira, call_instruction, fn_table_entry, fn_type,
+                fn_ref, nullptr, modifier);
         } else if (fn_ref->value->type->id == ZigTypeIdBoundFn) {
             assert(fn_ref->value->special == ConstValSpecialStatic);
             ZigFn *fn_table_entry = fn_ref->value->data.x_bound_fn.fn;
             IrInstruction *first_arg_ptr = fn_ref->value->data.x_bound_fn.first_arg;
-            return ir_analyze_fn_call(ira, call_instruction, fn_table_entry, fn_table_entry->type_entry,
-                fn_ref, first_arg_ptr, is_comptime, call_instruction->fn_inline);
+            CallModifier modifier = is_comptime ? CallModifierCompileTime : call_instruction->modifier;
+            return ir_analyze_fn_call_src(ira, call_instruction, fn_table_entry, fn_table_entry->type_entry,
+                fn_ref, first_arg_ptr, modifier);
         } else {
             ir_add_error_node(ira, fn_ref->source_node,
                 buf_sprintf("type '%s' not a function", buf_ptr(&fn_ref->value->type->name)));
@@ -18079,8 +18239,8 @@ static IrInstruction *ir_analyze_instruction_call(IrAnalyze *ira, IrInstructionC
     }
 
     if (fn_ref->value->type->id == ZigTypeIdFn) {
-        return ir_analyze_fn_call(ira, call_instruction, nullptr, fn_ref->value->type,
-            fn_ref, nullptr, false, call_instruction->fn_inline);
+        return ir_analyze_fn_call_src(ira, call_instruction, nullptr, fn_ref->value->type,
+            fn_ref, nullptr, modifier);
     } else {
         ir_add_error_node(ira, fn_ref->source_node,
             buf_sprintf("type '%s' not a function", buf_ptr(&fn_ref->value->type->name)));
@@ -21794,9 +21954,7 @@ static void ensure_field_index(ZigType *type, const char *field_name, size_t ind
 
 static ZigType *ir_type_info_get_type(IrAnalyze *ira, const char *type_name, ZigType *root) {
     Error err;
-    ZigValue *type_info_var = get_builtin_value(ira->codegen, "TypeInfo");
-    assert(type_info_var->type->id == ZigTypeIdMetaType);
-    ZigType *type_info_type = type_info_var->data.x_type;
+    ZigType *type_info_type = get_builtin_type(ira->codegen, "TypeInfo");
     assert(type_info_type->id == ZigTypeIdUnion);
     if ((err = type_resolve(ira->codegen, type_info_type, ResolveStatusSizeKnown))) {
         zig_unreachable();
@@ -23026,9 +23184,7 @@ static IrInstruction *ir_analyze_instruction_type_id(IrAnalyze *ira,
     if (type_is_invalid(type_entry))
         return ira->codegen->invalid_instruction;
 
-    ZigValue *var_value = get_builtin_value(ira->codegen, "TypeId");
-    assert(var_value->type->id == ZigTypeIdMetaType);
-    ZigType *result_type = var_value->data.x_type;
+    ZigType *result_type = get_builtin_type(ira->codegen, "TypeId");
 
     IrInstruction *result = ir_const(ira, &instruction->base, result_type);
     bigint_init_unsigned(&result->value->data.x_enum_tag, type_id_index(type_entry));
@@ -27779,6 +27935,8 @@ static IrInstruction *ir_analyze_instruction_base(IrAnalyze *ira, IrInstruction
             return ir_analyze_instruction_field_ptr(ira, (IrInstructionFieldPtr *)instruction);
         case IrInstructionIdCallSrc:
             return ir_analyze_instruction_call(ira, (IrInstructionCallSrc *)instruction);
+        case IrInstructionIdCallExtra:
+            return ir_analyze_instruction_call_extra(ira, (IrInstructionCallExtra *)instruction);
         case IrInstructionIdBr:
             return ir_analyze_instruction_br(ira, (IrInstructionBr *)instruction);
         case IrInstructionIdCondBr:
@@ -28176,6 +28334,7 @@ bool ir_has_side_effects(IrInstruction *instruction) {
         case IrInstructionIdDeclVarGen:
         case IrInstructionIdStorePtr:
         case IrInstructionIdVectorStoreElem:
+        case IrInstructionIdCallExtra:
         case IrInstructionIdCallSrc:
         case IrInstructionIdCallGen:
         case IrInstructionIdReturn:
src/ir_print.cpp
@@ -92,6 +92,8 @@ const char* ir_instruction_type_str(IrInstructionId id) {
             return "VarPtr";
         case IrInstructionIdReturnPtr:
             return "ReturnPtr";
+        case IrInstructionIdCallExtra:
+            return "CallExtra";
         case IrInstructionIdCallSrc:
             return "CallSrc";
         case IrInstructionIdCallGen:
@@ -636,15 +638,41 @@ static void ir_print_result_loc(IrPrint *irp, ResultLoc *result_loc) {
     zig_unreachable();
 }
 
+static void ir_print_call_extra(IrPrint *irp, IrInstructionCallExtra *instruction) {
+    fprintf(irp->f, "opts=");
+    ir_print_other_instruction(irp, instruction->options);
+    fprintf(irp->f, ", fn=");
+    ir_print_other_instruction(irp, instruction->fn_ref);
+    fprintf(irp->f, ", args=");
+    ir_print_other_instruction(irp, instruction->args);
+    fprintf(irp->f, ", result=");
+    ir_print_result_loc(irp, instruction->result_loc);
+}
+
 static void ir_print_call_src(IrPrint *irp, IrInstructionCallSrc *call_instruction) {
     switch (call_instruction->modifier) {
         case CallModifierNone:
             break;
+        case CallModifierNoAsync:
+            fprintf(irp->f, "noasync ");
+            break;
         case CallModifierAsync:
             fprintf(irp->f, "async ");
             break;
-        case CallModifierNoAsync:
-            fprintf(irp->f, "noasync ");
+        case CallModifierNeverTail:
+            fprintf(irp->f, "notail ");
+            break;
+        case CallModifierNeverInline:
+            fprintf(irp->f, "noinline ");
+            break;
+        case CallModifierAlwaysTail:
+            fprintf(irp->f, "tail ");
+            break;
+        case CallModifierAlwaysInline:
+            fprintf(irp->f, "inline ");
+            break;
+        case CallModifierCompileTime:
+            fprintf(irp->f, "comptime ");
             break;
         case CallModifierBuiltin:
             zig_unreachable();
@@ -670,11 +698,26 @@ static void ir_print_call_gen(IrPrint *irp, IrInstructionCallGen *call_instructi
     switch (call_instruction->modifier) {
         case CallModifierNone:
             break;
+        case CallModifierNoAsync:
+            fprintf(irp->f, "noasync ");
+            break;
         case CallModifierAsync:
             fprintf(irp->f, "async ");
             break;
-        case CallModifierNoAsync:
-            fprintf(irp->f, "noasync ");
+        case CallModifierNeverTail:
+            fprintf(irp->f, "notail ");
+            break;
+        case CallModifierNeverInline:
+            fprintf(irp->f, "noinline ");
+            break;
+        case CallModifierAlwaysTail:
+            fprintf(irp->f, "tail ");
+            break;
+        case CallModifierAlwaysInline:
+            fprintf(irp->f, "inline ");
+            break;
+        case CallModifierCompileTime:
+            fprintf(irp->f, "comptime ");
             break;
         case CallModifierBuiltin:
             zig_unreachable();
@@ -2082,6 +2125,9 @@ static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction, bool
         case IrInstructionIdCast:
             ir_print_cast(irp, (IrInstructionCast *)instruction);
             break;
+        case IrInstructionIdCallExtra:
+            ir_print_call_extra(irp, (IrInstructionCallExtra *)instruction);
+            break;
         case IrInstructionIdCallSrc:
             ir_print_call_src(irp, (IrInstructionCallSrc *)instruction);
             break;
src/zig_llvm.cpp
@@ -269,19 +269,25 @@ ZIG_EXTERN_C LLVMTypeRef ZigLLVMTokenTypeInContext(LLVMContextRef context_ref) {
 }
 
 LLVMValueRef ZigLLVMBuildCall(LLVMBuilderRef B, LLVMValueRef Fn, LLVMValueRef *Args,
-        unsigned NumArgs, unsigned CC, ZigLLVM_FnInline fn_inline, const char *Name)
+        unsigned NumArgs, unsigned CC, ZigLLVM_CallAttr attr, const char *Name)
 {
     CallInst *call_inst = CallInst::Create(unwrap(Fn), makeArrayRef(unwrap(Args), NumArgs), Name);
     call_inst->setCallingConv(CC);
-    switch (fn_inline) {
-        case ZigLLVM_FnInlineAuto:
+    switch (attr) {
+        case ZigLLVM_CallAttrAuto:
             break;
-        case ZigLLVM_FnInlineAlways:
-            call_inst->addAttribute(AttributeList::FunctionIndex, Attribute::AlwaysInline);
+        case ZigLLVM_CallAttrNeverTail:
+            call_inst->setTailCallKind(CallInst::TCK_NoTail);
             break;
-        case ZigLLVM_FnInlineNever:
+        case ZigLLVM_CallAttrNeverInline:
             call_inst->addAttribute(AttributeList::FunctionIndex, Attribute::NoInline);
             break;
+        case ZigLLVM_CallAttrAlwaysTail:
+            call_inst->setTailCallKind(CallInst::TCK_MustTail);
+            break;
+        case ZigLLVM_CallAttrAlwaysInline:
+            call_inst->addAttribute(AttributeList::FunctionIndex, Attribute::AlwaysInline);
+            break;
     }
     return wrap(unwrap(B)->Insert(call_inst));
 }
src/zig_llvm.h
@@ -64,13 +64,15 @@ ZIG_EXTERN_C LLVMTargetMachineRef ZigLLVMCreateTargetMachine(LLVMTargetRef T, co
 
 ZIG_EXTERN_C LLVMTypeRef ZigLLVMTokenTypeInContext(LLVMContextRef context_ref);
 
-enum ZigLLVM_FnInline {
-    ZigLLVM_FnInlineAuto,
-    ZigLLVM_FnInlineAlways,
-    ZigLLVM_FnInlineNever,
+enum ZigLLVM_CallAttr {
+    ZigLLVM_CallAttrAuto,
+    ZigLLVM_CallAttrNeverTail,
+    ZigLLVM_CallAttrNeverInline,
+    ZigLLVM_CallAttrAlwaysTail,
+    ZigLLVM_CallAttrAlwaysInline,
 };
 ZIG_EXTERN_C LLVMValueRef ZigLLVMBuildCall(LLVMBuilderRef B, LLVMValueRef Fn, LLVMValueRef *Args,
-        unsigned NumArgs, unsigned CC, enum ZigLLVM_FnInline fn_inline, const char *Name);
+        unsigned NumArgs, unsigned CC, enum ZigLLVM_CallAttr attr, const char *Name);
 
 ZIG_EXTERN_C LLVMValueRef ZigLLVMBuildMemCpy(LLVMBuilderRef B, LLVMValueRef Dst, unsigned DstAlign,
         LLVMValueRef Src, unsigned SrcAlign, LLVMValueRef Size, bool isVolatile);
src-self-hosted/llvm.zig
@@ -260,10 +260,12 @@ pub const X86StdcallCallConv = c.LLVMX86StdcallCallConv;
 pub const X86FastcallCallConv = c.LLVMX86FastcallCallConv;
 pub const CallConv = c.LLVMCallConv;
 
-pub const FnInline = extern enum {
+pub const CallAttr = extern enum {
     Auto,
-    Always,
-    Never,
+    NeverTail,
+    NeverInline,
+    AlwaysTail,
+    AlwaysInline,
 };
 
 fn removeNullability(comptime T: type) type {
@@ -286,6 +288,6 @@ extern fn ZigLLVMTargetMachineEmitToFile(
 ) bool;
 
 pub const BuildCall = ZigLLVMBuildCall;
-extern fn ZigLLVMBuildCall(B: *Builder, Fn: *Value, Args: [*]*Value, NumArgs: c_uint, CC: c_uint, fn_inline: FnInline, Name: [*:0]const u8) ?*Value;
+extern fn ZigLLVMBuildCall(B: *Builder, Fn: *Value, Args: [*]*Value, NumArgs: c_uint, CC: c_uint, fn_inline: CallAttr, Name: [*:0]const u8) ?*Value;
 
 pub const PrivateLinkage = c.LLVMLinkage.LLVMPrivateLinkage;
test/stage1/behavior/call.zig
@@ -0,0 +1,37 @@
+const std = @import("std");
+const expect = std.testing.expect;
+
+test "basic invocations" {
+    const foo = struct {
+        fn foo() i32 {
+            return 1234;
+        }
+    }.foo;
+    expect(@call(.{}, foo, .{}) == 1234);
+    comptime {
+        // modifiers that allow comptime calls
+        expect(@call(.{}, foo, .{}) == 1234);
+        expect(@call(.{ .modifier = .no_async }, foo, .{}) == 1234);
+        expect(@call(.{ .modifier = .always_tail }, foo, .{}) == 1234);
+        expect(@call(.{ .modifier = .always_inline }, foo, .{}) == 1234);
+    }
+    {
+        // comptime call without comptime keyword
+        const result = @call(.{ .modifier = .compile_time }, foo, .{}) == 1234;
+        comptime expect(result);
+    }
+}
+
+test "tuple parameters" {
+    const add = struct {
+        fn add(a: i32, b: i32) i32 {
+            return a + b;
+        }
+    }.add;
+    expect(@call(.{}, add, .{ 12, 34 }) == 46);
+    comptime expect(@call(.{}, add, .{ 12, 34 }) == 46);
+    {
+        const separate_args = .{ 12, 34 };
+        expect(@call(.{ .modifier = .always_inline }, add, separate_args) == 46);
+    }
+}
test/stage1/behavior.zig
@@ -52,6 +52,7 @@ comptime {
     _ = @import("behavior/bugs/920.zig");
     _ = @import("behavior/byteswap.zig");
     _ = @import("behavior/byval_arg_var.zig");
+    _ = @import("behavior/call.zig");
     _ = @import("behavior/cast.zig");
     _ = @import("behavior/const_slice_child.zig");
     _ = @import("behavior/defer.zig");
test/compile_errors.zig
@@ -2,6 +2,24 @@ const tests = @import("tests.zig");
 const builtin = @import("builtin");
 
 pub fn addCases(cases: *tests.CompileErrorContext) void {
+    cases.add(
+        "bad usage of @call",
+        \\export fn entry1() void {
+        \\    @call(.{}, foo, {});
+        \\}
+        \\export fn entry2() void {
+        \\    comptime @call(.{ .modifier = .never_inline }, foo, .{});
+        \\}
+        \\export fn entry3() void {
+        \\    comptime @call(.{ .modifier = .never_tail }, foo, .{});
+        \\}
+        \\fn foo() void {}
+    ,
+        "tmp.zig:2:21: error: expected tuple or struct, found 'void'",
+        "tmp.zig:5:58: error: unable to perform 'never_inline' call at compile-time",
+        "tmp.zig:8:56: error: unable to perform 'never_tail' call at compile-time",
+    );
+
     cases.add(
         \\export async fn foo() void {}
     , "tmp.zig:1:1: error: exported function cannot be async");