Commit aa44f8f0fd

Jacob Young <jacobly0@users.noreply.github.com>
2023-07-21 06:51:50
llvm: convert attributes and non-intrinsic calls
1 parent 23a8061
Changed files (4)
src/codegen/llvm/bindings.zig
@@ -26,10 +26,13 @@ pub const Context = opaque {
     extern fn LLVMContextDispose(C: *Context) void;
 
     pub const createEnumAttribute = LLVMCreateEnumAttribute;
-    extern fn LLVMCreateEnumAttribute(*Context, KindID: c_uint, Val: u64) *Attribute;
+    extern fn LLVMCreateEnumAttribute(C: *Context, KindID: c_uint, Val: u64) *Attribute;
+
+    pub const createTypeAttribute = LLVMCreateTypeAttribute;
+    extern fn LLVMCreateTypeAttribute(C: *Context, KindID: c_uint, Type: *Type) *Attribute;
 
     pub const createStringAttribute = LLVMCreateStringAttribute;
-    extern fn LLVMCreateStringAttribute(*Context, Key: [*]const u8, Key_Len: c_uint, Value: [*]const u8, Value_Len: c_uint) *Attribute;
+    extern fn LLVMCreateStringAttribute(C: *Context, Key: [*]const u8, Key_Len: c_uint, Value: [*]const u8, Value_Len: c_uint) *Attribute;
 
     pub const pointerType = LLVMPointerTypeInContext;
     extern fn LLVMPointerTypeInContext(C: *Context, AddressSpace: c_uint) *Type;
@@ -309,12 +312,18 @@ pub const Value = opaque {
     pub const setAlignment = LLVMSetAlignment;
     extern fn LLVMSetAlignment(V: *Value, Bytes: c_uint) void;
 
-    pub const getFunctionCallConv = LLVMGetFunctionCallConv;
-    extern fn LLVMGetFunctionCallConv(Fn: *Value) CallConv;
-
     pub const setFunctionCallConv = LLVMSetFunctionCallConv;
     extern fn LLVMSetFunctionCallConv(Fn: *Value, CC: CallConv) void;
 
+    pub const setInstructionCallConv = LLVMSetInstructionCallConv;
+    extern fn LLVMSetInstructionCallConv(Instr: *Value, CC: CallConv) void;
+
+    pub const setTailCallKind = ZigLLVMSetTailCallKind;
+    extern fn ZigLLVMSetTailCallKind(CallInst: *Value, TailCallKind: TailCallKind) void;
+
+    pub const addCallSiteAttribute = LLVMAddCallSiteAttribute;
+    extern fn LLVMAddCallSiteAttribute(C: *Value, Idx: AttributeIndex, A: *Attribute) void;
+
     pub const fnSetSubprogram = ZigLLVMFnSetSubprogram;
     extern fn ZigLLVMFnSetSubprogram(f: *Value, subprogram: *DISubprogram) void;
 
@@ -642,7 +651,17 @@ pub const Builder = opaque {
         Name: [*:0]const u8,
     ) *Value;
 
-    pub const buildCall = ZigLLVMBuildCall;
+    pub const buildCall = LLVMBuildCall2;
+    extern fn LLVMBuildCall2(
+        *Builder,
+        *Type,
+        Fn: *Value,
+        Args: [*]const *Value,
+        NumArgs: c_uint,
+        Name: [*:0]const u8,
+    ) *Value;
+
+    pub const buildCallOld = ZigLLVMBuildCall;
     extern fn ZigLLVMBuildCall(
         *Builder,
         *Type,
@@ -1605,6 +1624,13 @@ pub const CallAttr = enum(c_int) {
     AlwaysInline,
 };
 
+pub const TailCallKind = enum(c_uint) {
+    None,
+    Tail,
+    MustTail,
+    NoTail,
+};
+
 pub const DLLStorageClass = enum(c_uint) {
     Default,
     DLLImport,
src/codegen/llvm/Builder.zig
@@ -4,13 +4,15 @@ strip: bool,
 
 llvm: if (build_options.have_llvm) struct {
     context: *llvm.Context,
-    module: ?*llvm.Module = null,
-    target: ?*llvm.Target = null,
-    di_builder: ?*llvm.DIBuilder = null,
-    di_compile_unit: ?*llvm.DICompileUnit = null,
-    types: std.ArrayListUnmanaged(*llvm.Type) = .{},
-    globals: std.ArrayListUnmanaged(*llvm.Value) = .{},
-    constants: std.ArrayListUnmanaged(*llvm.Value) = .{},
+    module: ?*llvm.Module,
+    target: ?*llvm.Target,
+    di_builder: ?*llvm.DIBuilder,
+    di_compile_unit: ?*llvm.DICompileUnit,
+    attribute_kind_ids: ?*[Attribute.Kind.len]c_uint,
+    attributes: std.ArrayListUnmanaged(*llvm.Attribute),
+    types: std.ArrayListUnmanaged(*llvm.Type),
+    globals: std.ArrayListUnmanaged(*llvm.Value),
+    constants: std.ArrayListUnmanaged(*llvm.Value),
 } else void,
 
 source_filename: String,
@@ -18,8 +20,8 @@ data_layout: String,
 target_triple: String,
 
 string_map: std.AutoArrayHashMapUnmanaged(void, void),
-string_bytes: std.ArrayListUnmanaged(u8),
 string_indices: std.ArrayListUnmanaged(u32),
+string_bytes: std.ArrayListUnmanaged(u8),
 
 types: std.AutoArrayHashMapUnmanaged(String, Type),
 next_unnamed_type: String,
@@ -28,6 +30,11 @@ type_map: std.AutoArrayHashMapUnmanaged(void, void),
 type_items: std.ArrayListUnmanaged(Type.Item),
 type_extra: std.ArrayListUnmanaged(u32),
 
+attributes: std.AutoArrayHashMapUnmanaged(Attribute.Storage, void),
+attributes_map: std.AutoArrayHashMapUnmanaged(void, void),
+attributes_indices: std.ArrayListUnmanaged(u32),
+attributes_extra: std.ArrayListUnmanaged(u32),
+
 globals: std.AutoArrayHashMapUnmanaged(String, Global),
 next_unnamed_global: String,
 next_replaced_global: String,
@@ -41,6 +48,7 @@ constant_items: std.MultiArrayList(Constant.Item),
 constant_extra: std.ArrayListUnmanaged(u32),
 constant_limbs: std.ArrayListUnmanaged(std.math.big.Limb),
 
+pub const expected_args_len = 16;
 pub const expected_fields_len = 32;
 pub const expected_gep_indices_len = 8;
 pub const expected_cases_len = 8;
@@ -65,7 +73,7 @@ pub const String = enum(u32) {
         return self.toIndex() == null;
     }
 
-    pub fn toSlice(self: String, b: *const Builder) ?[:0]const u8 {
+    pub fn slice(self: String, b: *const Builder) ?[:0]const u8 {
         const index = self.toIndex() orelse return null;
         const start = b.string_indices.items[index];
         const end = b.string_indices.items[index + 1];
@@ -85,9 +93,9 @@ pub const String = enum(u32) {
         if (comptime std.mem.indexOfNone(u8, fmt_str, "@\"")) |_|
             @compileError("invalid format string: '" ++ fmt_str ++ "'");
         assert(data.string != .none);
-        const slice = data.string.toSlice(data.builder) orelse
+        const sentinel_slice = data.string.slice(data.builder) orelse
             return writer.print("{d}", .{@intFromEnum(data.string)});
-        const full_slice = slice[0 .. slice.len + comptime @intFromBool(
+        const full_slice = sentinel_slice[0 .. sentinel_slice.len + comptime @intFromBool(
             std.mem.indexOfScalar(u8, fmt_str, '@') != null,
         )];
         const need_quotes = (comptime std.mem.indexOfScalar(u8, fmt_str, '"') != null) or
@@ -108,6 +116,7 @@ pub const String = enum(u32) {
         return @enumFromInt(@as(u32, @intCast((index orelse return .none) +
             @intFromEnum(String.empty))));
     }
+
     fn toIndex(self: String) ?usize {
         return std.math.sub(u32, @intFromEnum(self), @intFromEnum(String.empty)) catch null;
     }
@@ -118,7 +127,7 @@ pub const String = enum(u32) {
             return @truncate(std.hash.Wyhash.hash(0, key));
         }
         pub fn eql(ctx: Adapter, lhs_key: []const u8, _: void, rhs_index: usize) bool {
-            return std.mem.eql(u8, lhs_key, String.fromIndex(rhs_index).toSlice(ctx.builder).?);
+            return std.mem.eql(u8, lhs_key, String.fromIndex(rhs_index).slice(ctx.builder).?);
         }
     };
 };
@@ -290,6 +299,17 @@ pub const Type = enum(u32) {
         };
     }
 
+    pub fn pointerAddrSpace(self: Type, builder: *const Builder) AddrSpace {
+        switch (self) {
+            .ptr => return .default,
+            else => {
+                const item = builder.type_items.items[@intFromEnum(self)];
+                assert(item.tag == .pointer);
+                return @enumFromInt(item.data);
+            },
+        }
+    }
+
     pub fn isFunction(self: Type, builder: *const Builder) bool {
         return switch (self.tag(builder)) {
             .function, .vararg_function => true,
@@ -606,7 +626,7 @@ pub const Type = enum(u32) {
                     var extra = data.builder.typeExtraDataTrail(Type.Target, item.data);
                     const types = extra.trail.next(extra.data.types_len, Type, data.builder);
                     const ints = extra.trail.next(extra.data.ints_len, u32, data.builder);
-                    try writer.print("t{s}", .{extra.data.name.toSlice(data.builder).?});
+                    try writer.print("t{s}", .{extra.data.name.slice(data.builder).?});
                     for (types) |ty| try writer.print("_{m}", .{ty.fmt(data.builder)});
                     for (ints) |int| try writer.print("_{d}", .{int});
                     try writer.writeByte('t');
@@ -641,7 +661,7 @@ pub const Type = enum(u32) {
                 .named_structure => {
                     const extra = data.builder.typeExtraData(Type.NamedStructure, item.data);
                     try writer.writeAll("s_");
-                    if (extra.id.toSlice(data.builder)) |id| try writer.writeAll(id);
+                    if (extra.id.slice(data.builder)) |id| try writer.writeAll(id);
                 },
             }
             return;
@@ -823,6 +843,789 @@ pub const Type = enum(u32) {
     }
 };
 
+pub const Attribute = union(Kind) {
+    // Parameter Attributes
+    zeroext,
+    signext,
+    inreg,
+    byval: Type,
+    byref: Type,
+    preallocated: Type,
+    inalloca: Type,
+    sret: Type,
+    elementtype: Type,
+    @"align": Alignment,
+    @"noalias",
+    nocapture,
+    nofree,
+    nest,
+    returned,
+    nonnull,
+    dereferenceable: u32,
+    dereferenceable_or_null: u32,
+    swiftself,
+    swiftasync,
+    swifterror,
+    immarg,
+    noundef,
+    nofpclass: FpClass,
+    alignstack: Alignment,
+    allocalign,
+    allocptr,
+    readnone,
+    readonly,
+    writeonly,
+
+    // Function Attributes
+    //alignstack: Alignment,
+    allockind: AllocKind,
+    allocsize: AllocSize,
+    alwaysinline,
+    builtin,
+    cold,
+    convergent,
+    disable_sanitizer_information,
+    fn_ret_thunk_extern,
+    hot,
+    inlinehint,
+    jumptable,
+    memory: Memory,
+    minsize,
+    naked,
+    nobuiltin,
+    nocallback,
+    noduplicate,
+    //nofree,
+    noimplicitfloat,
+    @"noinline",
+    nomerge,
+    nonlazybind,
+    noprofile,
+    skipprofile,
+    noredzone,
+    noreturn,
+    norecurse,
+    willreturn,
+    nosync,
+    nounwind,
+    nosanitize_bounds,
+    nosanitize_coverage,
+    null_pointer_is_valid,
+    optforfuzzing,
+    optnone,
+    optsize,
+    //preallocated: Type,
+    returns_twice,
+    safestack,
+    sanitize_address,
+    sanitize_memory,
+    sanitize_thread,
+    sanitize_hwaddress,
+    sanitize_memtag,
+    speculative_load_hardening,
+    speculatable,
+    ssp,
+    sspstrong,
+    sspreq,
+    strictfp,
+    uwtable: UwTable,
+    nocf_check,
+    shadowcallstack,
+    mustprogress,
+    vscale_range: VScaleRange,
+
+    // Global Attributes
+    no_sanitize_address,
+    no_sanitize_hwaddress,
+    //sanitize_memtag,
+    sanitize_address_dyninit,
+
+    string: struct { kind: String, value: String },
+    none: noreturn,
+
+    pub const Index = enum(u32) {
+        _,
+
+        pub fn getKind(self: Index, builder: *const Builder) Kind {
+            return self.toStorage(builder).kind;
+        }
+
+        pub fn toAttribute(self: Index, builder: *const Builder) Attribute {
+            @setEvalBranchQuota(2_000);
+            const storage = self.toStorage(builder);
+            if (storage.kind.toString()) |kind| return .{ .string = .{
+                .kind = kind,
+                .value = @enumFromInt(storage.value),
+            } } else return switch (storage.kind) {
+                inline .zeroext,
+                .signext,
+                .inreg,
+                .byval,
+                .byref,
+                .preallocated,
+                .inalloca,
+                .sret,
+                .elementtype,
+                .@"align",
+                .@"noalias",
+                .nocapture,
+                .nofree,
+                .nest,
+                .returned,
+                .nonnull,
+                .dereferenceable,
+                .dereferenceable_or_null,
+                .swiftself,
+                .swiftasync,
+                .swifterror,
+                .immarg,
+                .noundef,
+                .nofpclass,
+                .alignstack,
+                .allocalign,
+                .allocptr,
+                .readnone,
+                .readonly,
+                .writeonly,
+                //.alignstack,
+                .allockind,
+                .allocsize,
+                .alwaysinline,
+                .builtin,
+                .cold,
+                .convergent,
+                .disable_sanitizer_information,
+                .fn_ret_thunk_extern,
+                .hot,
+                .inlinehint,
+                .jumptable,
+                .memory,
+                .minsize,
+                .naked,
+                .nobuiltin,
+                .nocallback,
+                .noduplicate,
+                //.nofree,
+                .noimplicitfloat,
+                .@"noinline",
+                .nomerge,
+                .nonlazybind,
+                .noprofile,
+                .skipprofile,
+                .noredzone,
+                .noreturn,
+                .norecurse,
+                .willreturn,
+                .nosync,
+                .nounwind,
+                .nosanitize_bounds,
+                .nosanitize_coverage,
+                .null_pointer_is_valid,
+                .optforfuzzing,
+                .optnone,
+                .optsize,
+                //.preallocated,
+                .returns_twice,
+                .safestack,
+                .sanitize_address,
+                .sanitize_memory,
+                .sanitize_thread,
+                .sanitize_hwaddress,
+                .sanitize_memtag,
+                .speculative_load_hardening,
+                .speculatable,
+                .ssp,
+                .sspstrong,
+                .sspreq,
+                .strictfp,
+                .uwtable,
+                .nocf_check,
+                .shadowcallstack,
+                .mustprogress,
+                .vscale_range,
+                .no_sanitize_address,
+                .no_sanitize_hwaddress,
+                .sanitize_address_dyninit,
+                => |kind| {
+                    const field = @typeInfo(Attribute).Union.fields[@intFromEnum(kind)];
+                    comptime assert(std.mem.eql(u8, @tagName(kind), field.name));
+                    return @unionInit(Attribute, field.name, switch (field.type) {
+                        void => {},
+                        u32 => storage.value,
+                        Alignment, String, Type, UwTable => @enumFromInt(storage.value),
+                        AllocKind, AllocSize, FpClass, Memory, VScaleRange => @bitCast(storage.value),
+                        else => @compileError("bad payload type: " ++ @typeName(field.type)),
+                    });
+                },
+                .string, .none => unreachable,
+                _ => unreachable,
+            };
+        }
+
+        const FormatData = struct {
+            attribute_index: Index,
+            builder: *const Builder,
+        };
+        fn format(
+            data: FormatData,
+            comptime fmt_str: []const u8,
+            _: std.fmt.FormatOptions,
+            writer: anytype,
+        ) @TypeOf(writer).Error!void {
+            if (comptime std.mem.indexOfNone(u8, fmt_str, "\"")) |_|
+                @compileError("invalid format string: '" ++ fmt_str ++ "'");
+            const attribute = data.attribute_index.toAttribute(data.builder);
+            switch (attribute) {
+                .zeroext,
+                .signext,
+                .inreg,
+                .@"noalias",
+                .nocapture,
+                .nofree,
+                .nest,
+                .returned,
+                .nonnull,
+                .swiftself,
+                .swiftasync,
+                .swifterror,
+                .immarg,
+                .noundef,
+                .allocalign,
+                .allocptr,
+                .readnone,
+                .readonly,
+                .writeonly,
+                .alwaysinline,
+                .builtin,
+                .cold,
+                .convergent,
+                .disable_sanitizer_information,
+                .fn_ret_thunk_extern,
+                .hot,
+                .inlinehint,
+                .jumptable,
+                .minsize,
+                .naked,
+                .nobuiltin,
+                .nocallback,
+                .noduplicate,
+                .noimplicitfloat,
+                .@"noinline",
+                .nomerge,
+                .nonlazybind,
+                .noprofile,
+                .skipprofile,
+                .noredzone,
+                .noreturn,
+                .norecurse,
+                .willreturn,
+                .nosync,
+                .nounwind,
+                .nosanitize_bounds,
+                .nosanitize_coverage,
+                .null_pointer_is_valid,
+                .optforfuzzing,
+                .optnone,
+                .optsize,
+                .returns_twice,
+                .safestack,
+                .sanitize_address,
+                .sanitize_memory,
+                .sanitize_thread,
+                .sanitize_hwaddress,
+                .sanitize_memtag,
+                .speculative_load_hardening,
+                .speculatable,
+                .ssp,
+                .sspstrong,
+                .sspreq,
+                .strictfp,
+                .nocf_check,
+                .shadowcallstack,
+                .mustprogress,
+                .no_sanitize_address,
+                .no_sanitize_hwaddress,
+                .sanitize_address_dyninit,
+                => try writer.print(" {s}", .{@tagName(attribute)}),
+                .byval,
+                .byref,
+                .preallocated,
+                .inalloca,
+                .sret,
+                .elementtype,
+                => |ty| try writer.print(" {s}({%})", .{ @tagName(attribute), ty.fmt(data.builder) }),
+                .@"align" => @panic("todo"),
+                .dereferenceable,
+                .dereferenceable_or_null,
+                => @panic("todo"),
+                .nofpclass => @panic("todo"),
+                .alignstack => @panic("todo"),
+                .allockind => @panic("todo"),
+                .allocsize => @panic("todo"),
+                .memory => @panic("todo"),
+                .uwtable => @panic("todo"),
+                .vscale_range => @panic("todo"),
+                .string => |string_attr| if (comptime std.mem.indexOfScalar(u8, fmt_str, '"') != null) {
+                    try writer.print(" {\"}", .{string_attr.kind.fmt(data.builder)});
+                    if (string_attr.value != .empty)
+                        try writer.print("={\"}", .{string_attr.value.fmt(data.builder)});
+                },
+                .none => unreachable,
+            }
+        }
+        pub fn fmt(self: Index, builder: *const Builder) std.fmt.Formatter(format) {
+            return .{ .data = .{ .attribute_index = self, .builder = builder } };
+        }
+
+        fn toStorage(self: Index, builder: *const Builder) Storage {
+            return builder.attributes.keys()[@intFromEnum(self)];
+        }
+
+        fn toLlvm(self: Index, builder: *const Builder) *llvm.Attribute {
+            assert(builder.useLibLlvm());
+            return builder.llvm.attributes.items[@intFromEnum(self)];
+        }
+    };
+
+    pub const Kind = enum(u32) {
+        // Parameter Attributes
+        zeroext,
+        signext,
+        inreg,
+        byval,
+        byref,
+        preallocated,
+        inalloca,
+        sret,
+        elementtype,
+        @"align",
+        @"noalias",
+        nocapture,
+        nofree,
+        nest,
+        returned,
+        nonnull,
+        dereferenceable,
+        dereferenceable_or_null,
+        swiftself,
+        swiftasync,
+        swifterror,
+        immarg,
+        noundef,
+        nofpclass,
+        alignstack,
+        allocalign,
+        allocptr,
+        readnone,
+        readonly,
+        writeonly,
+
+        // Function Attributes
+        //alignstack,
+        allockind,
+        allocsize,
+        alwaysinline,
+        builtin,
+        cold,
+        convergent,
+        disable_sanitizer_information,
+        fn_ret_thunk_extern,
+        hot,
+        inlinehint,
+        jumptable,
+        memory,
+        minsize,
+        naked,
+        nobuiltin,
+        nocallback,
+        noduplicate,
+        //nofree,
+        noimplicitfloat,
+        @"noinline",
+        nomerge,
+        nonlazybind,
+        noprofile,
+        skipprofile,
+        noredzone,
+        noreturn,
+        norecurse,
+        willreturn,
+        nosync,
+        nounwind,
+        nosanitize_bounds,
+        nosanitize_coverage,
+        null_pointer_is_valid,
+        optforfuzzing,
+        optnone,
+        optsize,
+        //preallocated,
+        returns_twice,
+        safestack,
+        sanitize_address,
+        sanitize_memory,
+        sanitize_thread,
+        sanitize_hwaddress,
+        sanitize_memtag,
+        speculative_load_hardening,
+        speculatable,
+        ssp,
+        sspstrong,
+        sspreq,
+        strictfp,
+        uwtable,
+        nocf_check,
+        shadowcallstack,
+        mustprogress,
+        vscale_range,
+
+        // Global Attributes
+        no_sanitize_address,
+        no_sanitize_hwaddress,
+        //sanitize_memtag,
+        sanitize_address_dyninit,
+
+        string = std.math.maxInt(u31) - 1,
+        none = std.math.maxInt(u31),
+        _,
+
+        pub const len = @typeInfo(Kind).Enum.fields.len - 2;
+
+        pub fn fromString(str: String) Kind {
+            assert(!str.isAnon());
+            return @enumFromInt(@intFromEnum(str));
+        }
+
+        fn toString(self: Kind) ?String {
+            const str: String = @enumFromInt(@intFromEnum(self));
+            return if (str.isAnon()) null else str;
+        }
+    };
+
+    pub const FpClass = packed struct(u32) {
+        signaling_nan: bool = false,
+        quiet_nan: bool = false,
+        negative_infinity: bool = false,
+        negative_normal: bool = false,
+        negative_subnormal: bool = false,
+        negative_zero: bool = false,
+        positive_zero: bool = false,
+        positive_subnormal: bool = false,
+        positive_normal: bool = false,
+        positive_infinity: bool = false,
+        _: u22 = 0,
+
+        pub const nan = FpClass{ .signaling_nan = true, .quiet_nan = true };
+        pub const inf = FpClass{ .negative_infinity = true, .positive_infinity = true };
+        pub const norm = FpClass{ .positive_normal = true, .negative_normal = true };
+        pub const sub = FpClass{ .positive_subnormal = true, .negative_subnormal = true };
+        pub const zero = FpClass{ .positive_zero = true, .negative_zero = true };
+        pub const all = FpClass{
+            .signaling_nan = true,
+            .quiet_nan = true,
+            .negative_infinity = true,
+            .negative_normal = true,
+            .negative_subnormal = true,
+            .negative_zero = true,
+            .positive_zero = true,
+            .positive_subnormal = true,
+            .positive_normal = true,
+            .positive_infinity = true,
+        };
+        pub const snan = FpClass{ .signaling_nan = true };
+        pub const qnan = FpClass{ .quiet_nan = true };
+        pub const ninf = FpClass{ .negative_infinity = true };
+        pub const nnorm = FpClass{ .negative_normal = true };
+        pub const nsub = FpClass{ .negative_subnormal = true };
+        pub const nzero = FpClass{ .negative_zero = true };
+        pub const pzero = FpClass{ .positive_zero = true };
+        pub const psub = FpClass{ .positive_subnormal = true };
+        pub const pnorm = FpClass{ .positive_normal = true };
+        pub const pinf = FpClass{ .positive_infinity = true };
+    };
+
+    pub const AllocKind = packed struct(u32) {
+        alloc: bool,
+        realloc: bool,
+        free: bool,
+        uninitialized: bool,
+        zeroed: bool,
+        aligned: bool,
+        _: u26 = 0,
+    };
+
+    pub const AllocSize = packed struct(u32) {
+        elem_size: u16,
+        num_elems: u16,
+
+        pub const none = std.math.maxInt(u16);
+
+        fn toLlvm(self: AllocSize) packed struct(u64) { num_elems: u32, elem_size: u32 } {
+            return .{ .num_elems = switch (self.num_elems) {
+                else => self.num_elems,
+                none => std.math.maxInt(u32),
+            }, .elem_size = self.elem_size };
+        }
+    };
+
+    pub const Memory = packed struct(u32) {
+        argmem: Effect,
+        inaccessiblemem: Effect,
+        other: Effect,
+        _: u26 = 0,
+
+        pub const Effect = enum(u2) { none, read, write, readwrite };
+    };
+
+    pub const UwTable = enum(u32) {
+        none,
+        sync,
+        @"async",
+
+        pub const default = UwTable.@"async";
+    };
+
+    pub const VScaleRange = packed struct(u32) {
+        min: Alignment,
+        max: Alignment,
+        _: u20 = 0,
+
+        fn toLlvm(self: VScaleRange) packed struct(u64) { max: u32, min: u32 } {
+            return .{
+                .max = @intCast(self.max.toByteUnits() orelse 0),
+                .min = @intCast(self.min.toByteUnits().?),
+            };
+        }
+    };
+
+    pub fn getKind(self: Attribute) Kind {
+        return switch (self) {
+            else => self,
+            .string => |string_attr| Kind.fromString(string_attr.kind),
+        };
+    }
+
+    const Storage = extern struct {
+        kind: Kind,
+        value: u32,
+    };
+
+    fn toStorage(self: Attribute) Storage {
+        return switch (self) {
+            inline else => |value| .{ .kind = @as(Kind, self), .value = switch (@TypeOf(value)) {
+                void => 0,
+                u32 => value,
+                Alignment, String, Type, UwTable => @intFromEnum(value),
+                AllocKind, AllocSize, FpClass, Memory, VScaleRange => @bitCast(value),
+                else => @compileError("bad payload type: " ++ @typeName(@TypeOf(value))),
+            } },
+            .string => |string_attr| .{
+                .kind = Kind.fromString(string_attr.kind),
+                .value = @intFromEnum(string_attr.value),
+            },
+            .none => unreachable,
+        };
+    }
+};
+
+pub const Attributes = enum(u32) {
+    none,
+    _,
+
+    pub fn slice(self: Attributes, builder: *const Builder) []const Attribute.Index {
+        const start = builder.attributes_indices.items[@intFromEnum(self)];
+        const end = builder.attributes_indices.items[@intFromEnum(self) + 1];
+        return @ptrCast(builder.attributes_extra.items[start..end]);
+    }
+
+    const FormatData = struct {
+        attributes: Attributes,
+        builder: *const Builder,
+    };
+    fn format(
+        data: FormatData,
+        comptime fmt_str: []const u8,
+        fmt_opts: std.fmt.FormatOptions,
+        writer: anytype,
+    ) @TypeOf(writer).Error!void {
+        for (data.attributes.slice(data.builder)) |attribute_index| try Attribute.Index.format(.{
+            .attribute_index = attribute_index,
+            .builder = data.builder,
+        }, fmt_str, fmt_opts, writer);
+    }
+    pub fn fmt(self: Attributes, builder: *const Builder) std.fmt.Formatter(format) {
+        return .{ .data = .{ .attributes = self, .builder = builder } };
+    }
+};
+
+pub const FunctionAttributes = enum(u32) {
+    none,
+    _,
+
+    const function_index = 0;
+    const return_index = 1;
+    const params_index = 2;
+
+    pub const Wip = struct {
+        maps: Maps = .{},
+
+        const Map = std.AutoArrayHashMapUnmanaged(Attribute.Kind, Attribute.Index);
+        const Maps = std.ArrayListUnmanaged(Map);
+
+        pub fn deinit(self: *Wip, builder: *const Builder) void {
+            for (self.maps.items) |*map| map.deinit(builder.gpa);
+            self.maps.deinit(builder.gpa);
+            self.* = undefined;
+        }
+
+        pub fn addFnAttr(self: *Wip, attribute: Attribute, builder: *Builder) Allocator.Error!void {
+            try self.addAttr(function_index, attribute, builder);
+        }
+
+        pub fn addFnAttrIndex(
+            self: *Wip,
+            attribute_index: Attribute.Index,
+            builder: *const Builder,
+        ) Allocator.Error!void {
+            try self.addAttrIndex(function_index, attribute_index, builder);
+        }
+
+        pub fn removeFnAttr(self: *Wip, attribute_kind: Attribute.Kind) Allocator.Error!bool {
+            return self.removeAttr(function_index, attribute_kind);
+        }
+
+        pub fn addRetAttr(self: *Wip, attribute: Attribute, builder: *Builder) Allocator.Error!void {
+            try self.addAttr(return_index, attribute, builder);
+        }
+
+        pub fn addRetAttrIndex(
+            self: *Wip,
+            attribute_index: Attribute.Index,
+            builder: *const Builder,
+        ) Allocator.Error!void {
+            try self.addAttrIndex(return_index, attribute_index, builder);
+        }
+
+        pub fn removeRetAttr(self: *Wip, attribute_kind: Attribute.Kind) Allocator.Error!bool {
+            return self.removeAttr(return_index, attribute_kind);
+        }
+
+        pub fn addParamAttr(
+            self: *Wip,
+            param_index: usize,
+            attribute: Attribute,
+            builder: *Builder,
+        ) Allocator.Error!void {
+            try self.addAttr(params_index + param_index, attribute, builder);
+        }
+
+        pub fn addParamAttrIndex(
+            self: *Wip,
+            param_index: usize,
+            attribute_index: Attribute.Index,
+            builder: *const Builder,
+        ) Allocator.Error!void {
+            try self.addAttrIndex(params_index + param_index, attribute_index, builder);
+        }
+
+        pub fn removeParamAttr(
+            self: *Wip,
+            param_index: usize,
+            attribute_kind: Attribute.Kind,
+        ) Allocator.Error!bool {
+            return self.removeAttr(params_index + param_index, attribute_kind);
+        }
+
+        pub fn finish(self: *const Wip, builder: *Builder) Allocator.Error!FunctionAttributes {
+            const attributes = try builder.gpa.alloc(Attributes, self.maps.items.len);
+            defer builder.gpa.free(attributes);
+            for (attributes, self.maps.items) |*attribute, map|
+                attribute.* = try builder.attrs(map.values());
+            return builder.fnAttrs(attributes);
+        }
+
+        fn addAttr(
+            self: *Wip,
+            index: usize,
+            attribute: Attribute,
+            builder: *Builder,
+        ) Allocator.Error!void {
+            const map = try self.getOrPutMap(builder.gpa, index);
+            try map.put(builder.gpa, attribute.getKind(), try builder.attr(attribute));
+        }
+
+        fn addAttrIndex(
+            self: *Wip,
+            index: usize,
+            attribute_index: Attribute.Index,
+            builder: *const Builder,
+        ) Allocator.Error!void {
+            const map = try self.getOrPutMap(builder.gpa, index);
+            try map.put(builder.gpa, attribute_index.getKind(builder), attribute_index);
+        }
+
+        fn removeAttr(self: *Wip, index: usize, attribute_kind: Attribute.Kind) Allocator.Error!bool {
+            const map = self.getMap(index) orelse return false;
+            return map.swapRemove(attribute_kind);
+        }
+
+        fn getOrPutMap(self: *Wip, allocator: Allocator, index: usize) Allocator.Error!*Map {
+            if (index >= self.maps.items.len)
+                try self.maps.appendNTimes(allocator, .{}, index + 1 - self.maps.items.len);
+            return &self.maps.items[index];
+        }
+
+        fn getMap(self: *Wip, index: usize) ?*Map {
+            return if (index >= self.maps.items.len) null else &self.maps.items[index];
+        }
+
+        fn ensureTotalLength(self: *Wip, new_len: usize) Allocator.Error!void {
+            try self.maps.appendNTimes(
+                .{},
+                std.math.sub(usize, new_len, self.maps.items.len) catch return,
+            );
+        }
+    };
+
+    pub fn func(self: FunctionAttributes, builder: *const Builder) Attributes {
+        return self.get(function_index, builder);
+    }
+
+    pub fn ret(self: FunctionAttributes, builder: *const Builder) Attributes {
+        return self.get(return_index, builder);
+    }
+
+    pub fn param(self: FunctionAttributes, param_index: usize, builder: *const Builder) Attributes {
+        return self.get(params_index + param_index, builder);
+    }
+
+    pub fn toWip(self: FunctionAttributes, builder: *const Builder) Allocator.Error!Wip {
+        var wip: Wip = .{};
+        errdefer wip.deinit(builder);
+        const attributes_slice = self.slice(builder);
+        try wip.maps.ensureTotalCapacityPrecise(builder.gpa, attributes_slice.len);
+        for (attributes_slice) |attributes| {
+            const map = wip.maps.addOneAssumeCapacity();
+            map.* = .{};
+            const attribute_slice = attributes.slice(builder);
+            try map.ensureTotalCapacity(builder.gpa, attribute_slice.len);
+            for (attributes.slice(builder)) |attribute|
+                map.putAssumeCapacityNoClobber(attribute.getKind(builder), attribute);
+        }
+        return wip;
+    }
+
+    fn get(self: FunctionAttributes, index: usize, builder: *const Builder) Attributes {
+        const attribute_slice = self.slice(builder);
+        return if (index < attribute_slice.len) attribute_slice[index] else .none;
+    }
+
+    fn slice(self: FunctionAttributes, builder: *const Builder) []const Attributes {
+        const start = builder.attributes_indices.items[@intFromEnum(self)];
+        const end = builder.attributes_indices.items[@intFromEnum(self) + 1];
+        return @ptrCast(builder.attributes_extra.items[start..end]);
+    }
+};
+
 pub const Linkage = enum {
     external,
     private,
@@ -1053,6 +1856,127 @@ pub const Alignment = enum(u6) {
     }
 };
 
+pub const CallConv = enum(u10) {
+    ccc,
+
+    fastcc = 8,
+    coldcc,
+    ghccc,
+
+    webkit_jscc = 12,
+    anyregcc,
+    preserve_mostcc,
+    preserve_allcc,
+    swiftcc,
+    cxx_fast_tlscc,
+    tailcc,
+    cfguard_checkcc,
+    swifttailcc,
+
+    x86_stdcallcc = 64,
+    x86_fastcallcc,
+    arm_apcscc,
+    arm_aapcscc,
+    arm_aapcs_vfpcc,
+    msp430_intrcc,
+    x86_thiscallcc,
+    ptx_kernel,
+    ptx_device,
+
+    spir_func = 75,
+    spir_kernel,
+    intel_ocl_bicc,
+    x86_64_sysvcc,
+    win64cc,
+    x86_vectorcallcc,
+    hhvmcc,
+    hhvm_ccc,
+    x86_intrcc,
+    avr_intrcc,
+    avr_signalcc,
+
+    amdgpu_vs = 87,
+    amdgpu_gs,
+    amdgpu_ps,
+    amdgpu_cs,
+    amdgpu_kernel,
+    x86_regcallcc,
+    amdgpu_hs,
+
+    amdgpu_ls = 95,
+    amdgpu_es,
+    aarch64_vector_pcs,
+    aarch64_sve_vector_pcs,
+
+    amdgpu_gfx = 100,
+
+    aarch64_sme_preservemost_from_x0 = 102,
+    aarch64_sme_preservemost_from_x2,
+
+    _,
+
+    pub const default = CallConv.ccc;
+
+    pub fn format(
+        self: CallConv,
+        comptime _: []const u8,
+        _: std.fmt.FormatOptions,
+        writer: anytype,
+    ) @TypeOf(writer).Error!void {
+        switch (self) {
+            .ccc => {},
+            .fastcc,
+            .coldcc,
+            .ghccc,
+            .webkit_jscc,
+            .anyregcc,
+            .preserve_mostcc,
+            .preserve_allcc,
+            .swiftcc,
+            .cxx_fast_tlscc,
+            .tailcc,
+            .cfguard_checkcc,
+            .swifttailcc,
+            .x86_stdcallcc,
+            .x86_fastcallcc,
+            .arm_apcscc,
+            .arm_aapcscc,
+            .arm_aapcs_vfpcc,
+            .msp430_intrcc,
+            .x86_thiscallcc,
+            .ptx_kernel,
+            .ptx_device,
+            .spir_func,
+            .spir_kernel,
+            .intel_ocl_bicc,
+            .x86_64_sysvcc,
+            .win64cc,
+            .x86_vectorcallcc,
+            .hhvmcc,
+            .hhvm_ccc,
+            .x86_intrcc,
+            .avr_intrcc,
+            .avr_signalcc,
+            .amdgpu_vs,
+            .amdgpu_gs,
+            .amdgpu_ps,
+            .amdgpu_cs,
+            .amdgpu_kernel,
+            .x86_regcallcc,
+            .amdgpu_hs,
+            .amdgpu_ls,
+            .amdgpu_es,
+            .aarch64_vector_pcs,
+            .aarch64_sve_vector_pcs,
+            .amdgpu_gfx,
+            .aarch64_sme_preservemost_from_x0,
+            .aarch64_sme_preservemost_from_x2,
+            => try writer.print(" {s}", .{@tagName(self)}),
+            _ => try writer.print(" cc{d}", .{@intFromEnum(self)}),
+        }
+    }
+};
+
 pub const Global = struct {
     linkage: Linkage = .external,
     preemption: Preemption = .dso_preemptable,
@@ -1170,7 +2094,7 @@ pub const Global = struct {
         fn updateName(self: Index, builder: *const Builder) void {
             if (!builder.useLibLlvm()) return;
             const index = @intFromEnum(self.unwrap(builder));
-            const name_slice = self.name(builder).toSlice(builder) orelse "";
+            const name_slice = self.name(builder).slice(builder) orelse "";
             builder.llvm.globals.items[index].setValueName2(name_slice.ptr, name_slice.len);
         }
 
@@ -1301,6 +2225,8 @@ pub const Variable = struct {
 
 pub const Function = struct {
     global: Global.Index,
+    call_conv: CallConv = CallConv.default,
+    attributes: FunctionAttributes = .none,
     section: String = .none,
     alignment: Alignment = .default,
     blocks: []const Block = &.{},
@@ -1364,6 +2290,8 @@ pub const Function = struct {
             block,
             br,
             br_cond,
+            call,
+            @"call fast",
             extractelement,
             extractvalue,
             fadd,
@@ -1454,6 +2382,10 @@ pub const Function = struct {
             @"mul nsw",
             @"mul nuw",
             @"mul nuw nsw",
+            @"musttail call",
+            @"musttail call fast",
+            @"notail call",
+            @"notail call fast",
             @"or",
             phi,
             @"phi fast",
@@ -1481,6 +2413,8 @@ pub const Function = struct {
             @"sub nuw",
             @"sub nuw nsw",
             @"switch",
+            @"tail call",
+            @"tail call fast",
             trunc,
             udiv,
             @"udiv exact",
@@ -1530,6 +2464,15 @@ pub const Function = struct {
                     .@"store volatile",
                     .@"unreachable",
                     => false,
+                    .call,
+                    .@"call fast",
+                    .@"musttail call",
+                    .@"musttail call fast",
+                    .@"notail call",
+                    .@"notail call fast",
+                    .@"tail call",
+                    .@"tail call fast",
+                    => self.typeOfWip(wip) != .void,
                     else => true,
                 };
             }
@@ -1625,6 +2568,15 @@ pub const Function = struct {
                     .@"switch",
                     .@"unreachable",
                     => .none,
+                    .call,
+                    .@"call fast",
+                    .@"musttail call",
+                    .@"musttail call fast",
+                    .@"notail call",
+                    .@"notail call fast",
+                    .@"tail call",
+                    .@"tail call fast",
+                    => wip.extraData(Call, instruction.data).ty.functionReturn(wip.builder),
                     .extractelement => wip.extraData(ExtractElement, instruction.data)
                         .val.typeOfWip(wip).childType(wip.builder),
                     .extractvalue => {
@@ -1813,6 +2765,15 @@ pub const Function = struct {
                     .@"switch",
                     .@"unreachable",
                     => .none,
+                    .call,
+                    .@"call fast",
+                    .@"musttail call",
+                    .@"musttail call fast",
+                    .@"notail call",
+                    .@"notail call fast",
+                    .@"tail call",
+                    .@"tail call fast",
+                    => function.extraData(Call, instruction.data).ty.functionReturn(builder),
                     .extractelement => function.extraData(ExtractElement, instruction.data)
                         .val.typeOf(function_index, builder).childType(builder),
                     .extractvalue => {
@@ -1955,7 +2916,7 @@ pub const Function = struct {
                 return if (wip.builder.strip)
                     ""
                 else
-                    wip.names.items[@intFromEnum(self)].toSlice(wip.builder).?;
+                    wip.names.items[@intFromEnum(self)].slice(wip.builder).?;
             }
         };
 
@@ -2063,6 +3024,30 @@ pub const Function = struct {
             rhs: Value,
         };
 
+        pub const Call = struct {
+            info: Info,
+            attributes: FunctionAttributes,
+            ty: Type,
+            callee: Value,
+            args_len: u32,
+            //args: [args_len]Value,
+
+            pub const Kind = enum {
+                normal,
+                fast,
+                musttail,
+                musttail_fast,
+                notail,
+                notail_fast,
+                tail,
+                tail_fast,
+            };
+            pub const Info = packed struct(u32) {
+                call_conv: CallConv,
+                _: u22 = undefined,
+            };
+        };
+
         pub const VaArg = struct {
             list: Value,
             type: Type,
@@ -2117,8 +3102,17 @@ pub const Function = struct {
         inline for (fields, self.extra[index..][0..fields.len]) |field, value|
             @field(result, field.name) = switch (field.type) {
                 u32 => value,
-                Alignment, AtomicOrdering, Block.Index, Type, Value => @enumFromInt(value),
-                MemoryAccessInfo, Instruction.Alloca.Info => @bitCast(value),
+                Alignment,
+                AtomicOrdering,
+                Block.Index,
+                FunctionAttributes,
+                Type,
+                Value,
+                => @enumFromInt(value),
+                MemoryAccessInfo,
+                Instruction.Alloca.Info,
+                Instruction.Call.Info,
+                => @bitCast(value),
                 else => @compileError("bad field type: " ++ @typeName(field.type)),
             };
         return .{
@@ -2243,7 +3237,7 @@ pub const WipFunction = struct {
         if (self.builder.useLibLlvm()) self.llvm.blocks.appendAssumeCapacity(
             self.builder.llvm.context.appendBasicBlock(
                 self.function.toLlvm(self.builder),
-                final_name.toSlice(self.builder).?,
+                final_name.slice(self.builder).?,
             ),
         );
         return index;
@@ -3162,6 +4156,88 @@ pub const WipFunction = struct {
         return self.selectTag(.@"select fast", cond, lhs, rhs, name);
     }
 
+    pub fn call(
+        self: *WipFunction,
+        kind: Instruction.Call.Kind,
+        call_conv: CallConv,
+        function_attributes: FunctionAttributes,
+        ty: Type,
+        callee: Value,
+        args: []const Value,
+        name: []const u8,
+    ) if (build_options.have_llvm) Allocator.Error!Value else Value {
+        const ret_ty = ty.functionReturn(self.builder);
+        assert(ty.isFunction(self.builder));
+        assert(callee.typeOfWip(self).isPointer(self.builder));
+        const params = ty.functionParameters(self.builder);
+        for (params, args[0..params.len]) |param, arg_val| assert(param == arg_val.typeOfWip(self));
+
+        try self.ensureUnusedExtraCapacity(1, Instruction.Call, args.len);
+        const instruction = try self.addInst(switch (ret_ty) {
+            .void => null,
+            else => name,
+        }, .{
+            .tag = .call,
+            .data = self.addExtraAssumeCapacity(Instruction.Call{
+                .info = .{ .call_conv = call_conv },
+                .attributes = function_attributes,
+                .ty = ty,
+                .callee = callee,
+                .args_len = @intCast(args.len),
+            }),
+        });
+        self.extra.appendSliceAssumeCapacity(@ptrCast(args));
+        if (self.builder.useLibLlvm()) {
+            const ExpectedContents = [expected_args_len]*llvm.Value;
+            var stack align(@alignOf(ExpectedContents)) =
+                std.heap.stackFallback(@sizeOf(ExpectedContents), self.builder.gpa);
+            const allocator = stack.get();
+
+            const llvm_args = try allocator.alloc(*llvm.Value, args.len);
+            defer allocator.free(llvm_args);
+            for (llvm_args, args) |*llvm_arg, arg_val| llvm_arg.* = arg_val.toLlvm(self);
+
+            switch (kind) {
+                .normal,
+                .musttail,
+                .notail,
+                .tail,
+                => self.llvm.builder.setFastMath(false),
+                .fast,
+                .musttail_fast,
+                .notail_fast,
+                .tail_fast,
+                => self.llvm.builder.setFastMath(true),
+            }
+            const llvm_instruction = self.llvm.builder.buildCall(
+                ty.toLlvm(self.builder),
+                callee.toLlvm(self),
+                llvm_args.ptr,
+                @intCast(llvm_args.len),
+                switch (ret_ty) {
+                    .void => "",
+                    else => instruction.llvmName(self),
+                },
+            );
+            llvm_instruction.setInstructionCallConv(@enumFromInt(@intFromEnum(call_conv)));
+            llvm_instruction.setTailCallKind(switch (kind) {
+                .normal, .fast => .None,
+                .musttail, .musttail_fast => .MustTail,
+                .notail, .notail_fast => .NoTail,
+                .tail, .tail_fast => .Tail,
+            });
+            for (0.., function_attributes.slice(self.builder)) |index, attributes| {
+                const attribute_index = @as(llvm.AttributeIndex, @intCast(index)) -% 1;
+                for (attributes.slice(self.builder)) |attribute| llvm_instruction.addCallSiteAttribute(
+                    attribute_index,
+                    attribute.toLlvm(self.builder),
+                );
+            }
+            self.llvm.instructions.appendAssumeCapacity(llvm_instruction);
+        }
+        return instruction.toValue();
+    }
+
     pub fn vaArg(self: *WipFunction, list: Value, ty: Type, name: []const u8) Allocator.Error!Value {
         try self.ensureUnusedExtraCapacity(1, Instruction.VaArg, 0);
         const instruction = try self.addInst(name, .{
@@ -3246,8 +4322,17 @@ pub const WipFunction = struct {
                     const value = @field(extra, field.name);
                     wip_extra.items[wip_extra.index] = switch (field.type) {
                         u32 => value,
-                        Alignment, AtomicOrdering, Block.Index, Type, Value => @intFromEnum(value),
-                        MemoryAccessInfo, Instruction.Alloca.Info => @bitCast(value),
+                        Alignment,
+                        AtomicOrdering,
+                        Block.Index,
+                        FunctionAttributes,
+                        Type,
+                        Value,
+                        => @intFromEnum(value),
+                        MemoryAccessInfo,
+                        Instruction.Alloca.Info,
+                        Instruction.Call.Info,
+                        => @bitCast(value),
                         else => @compileError("bad field type: " ++ @typeName(field.type)),
                     };
                     wip_extra.index += 1;
@@ -3256,13 +4341,14 @@ pub const WipFunction = struct {
             }
 
             fn appendSlice(wip_extra: *@This(), slice: anytype) void {
-                if (@typeInfo(@TypeOf(slice)).Pointer.child == Value) @compileError("use appendValues");
+                if (@typeInfo(@TypeOf(slice)).Pointer.child == Value)
+                    @compileError("use appendMappedValues");
                 const data: []const u32 = @ptrCast(slice);
                 @memcpy(wip_extra.items[wip_extra.index..][0..data.len], data);
                 wip_extra.index += @intCast(data.len);
             }
 
-            fn appendValues(wip_extra: *@This(), vals: []const Value, ctx: anytype) void {
+            fn appendMappedValues(wip_extra: *@This(), vals: []const Value, ctx: anytype) void {
                 for (wip_extra.items[wip_extra.index..][0..vals.len], vals) |*extra, val|
                     extra.* = @intFromEnum(ctx.map(val));
                 wip_extra.index += @intCast(vals.len);
@@ -3494,6 +4580,26 @@ pub const WipFunction = struct {
                             .@"else" = extra.@"else",
                         });
                     },
+                    .call,
+                    .@"call fast",
+                    .@"musttail call",
+                    .@"musttail call fast",
+                    .@"notail call",
+                    .@"notail call fast",
+                    .@"tail call",
+                    .@"tail call fast",
+                    => {
+                        var extra = self.extraDataTrail(Instruction.Call, instruction.data);
+                        const args = extra.trail.next(extra.data.args_len, Value, self);
+                        instruction.data = wip_extra.addExtra(Instruction.Call{
+                            .info = extra.data.info,
+                            .attributes = extra.data.attributes,
+                            .ty = extra.data.ty,
+                            .callee = instructions.map(extra.data.callee),
+                            .args_len = extra.data.args_len,
+                        });
+                        wip_extra.appendMappedValues(args, instructions);
+                    },
                     .extractvalue => {
                         var extra = self.extraDataTrail(Instruction.ExtractValue, instruction.data);
                         const indices = extra.trail.next(extra.data.indices_len, u32, self);
@@ -3517,7 +4623,7 @@ pub const WipFunction = struct {
                             .base = instructions.map(extra.data.base),
                             .indices_len = extra.data.indices_len,
                         });
-                        wip_extra.appendValues(indices, instructions);
+                        wip_extra.appendMappedValues(indices, instructions);
                     },
                     .insertelement => {
                         const extra = self.extraData(Instruction.InsertElement, instruction.data);
@@ -3559,7 +4665,7 @@ pub const WipFunction = struct {
                         instruction.data = wip_extra.addExtra(Instruction.Phi{
                             .type = extra.data.type,
                         });
-                        wip_extra.appendValues(incoming_vals, instructions);
+                        wip_extra.appendMappedValues(incoming_vals, instructions);
                         wip_extra.appendSlice(incoming_blocks);
                     },
                     .select,
@@ -3932,8 +5038,17 @@ pub const WipFunction = struct {
             const value = @field(extra, field.name);
             self.extra.appendAssumeCapacity(switch (field.type) {
                 u32 => value,
-                Alignment, AtomicOrdering, Block.Index, Type, Value => @intFromEnum(value),
-                MemoryAccessInfo, Instruction.Alloca.Info => @bitCast(value),
+                Alignment,
+                AtomicOrdering,
+                Block.Index,
+                FunctionAttributes,
+                Type,
+                Value,
+                => @intFromEnum(value),
+                MemoryAccessInfo,
+                Instruction.Alloca.Info,
+                Instruction.Call.Info,
+                => @bitCast(value),
                 else => @compileError("bad field type: " ++ @typeName(field.type)),
             });
         }
@@ -3971,8 +5086,17 @@ pub const WipFunction = struct {
         inline for (fields, self.extra.items[index..][0..fields.len]) |field, value|
             @field(result, field.name) = switch (field.type) {
                 u32 => value,
-                Alignment, AtomicOrdering, Block.Index, Type, Value => @enumFromInt(value),
-                MemoryAccessInfo, Instruction.Alloca.Info => @bitCast(value),
+                Alignment,
+                AtomicOrdering,
+                Block.Index,
+                FunctionAttributes,
+                Type,
+                Value,
+                => @enumFromInt(value),
+                MemoryAccessInfo,
+                Instruction.Alloca.Info,
+                Instruction.Call.Info,
+                => @bitCast(value),
                 else => @compileError("bad field type: " ++ @typeName(field.type)),
             };
         return .{
@@ -4294,7 +5418,7 @@ pub const Constant = enum(u32) {
                     .string,
                     .string_null,
                     => builder.arrayTypeAssumeCapacity(
-                        @as(String, @enumFromInt(item.data)).toSlice(builder).?.len +
+                        @as(String, @enumFromInt(item.data)).slice(builder).?.len +
                             @intFromBool(item.tag == .string_null),
                         .i8,
                     ),
@@ -4821,8 +5945,8 @@ pub fn init(options: Options) InitError!Builder {
         .target_triple = .none,
 
         .string_map = .{},
-        .string_bytes = .{},
         .string_indices = .{},
+        .string_bytes = .{},
 
         .types = .{},
         .next_unnamed_type = @enumFromInt(0),
@@ -4831,6 +5955,11 @@ pub fn init(options: Options) InitError!Builder {
         .type_items = .{},
         .type_extra = .{},
 
+        .attributes = .{},
+        .attributes_map = .{},
+        .attributes_indices = .{},
+        .attributes_extra = .{},
+
         .globals = .{},
         .next_unnamed_global = @enumFromInt(0),
         .next_replaced_global = .none,
@@ -4844,7 +5973,18 @@ pub fn init(options: Options) InitError!Builder {
         .constant_extra = .{},
         .constant_limbs = .{},
     };
-    if (self.useLibLlvm()) self.llvm = .{ .context = llvm.Context.create() };
+    if (self.useLibLlvm()) self.llvm = .{
+        .context = llvm.Context.create(),
+        .module = null,
+        .target = null,
+        .di_builder = null,
+        .di_compile_unit = null,
+        .attribute_kind_ids = null,
+        .attributes = .{},
+        .types = .{},
+        .globals = .{},
+        .constants = .{},
+    };
     errdefer self.deinit();
 
     try self.string_indices.append(self.gpa, 0);
@@ -4853,7 +5993,7 @@ pub fn init(options: Options) InitError!Builder {
     if (options.name.len > 0) self.source_filename = try self.string(options.name);
     self.initializeLLVMTarget(options.target.cpu.arch);
     if (self.useLibLlvm()) self.llvm.module = llvm.Module.createWithName(
-        (self.source_filename.toSlice(&self) orelse "").ptr,
+        (self.source_filename.slice(&self) orelse "").ptr,
         self.llvm.context,
     );
 
@@ -4864,20 +6004,20 @@ pub fn init(options: Options) InitError!Builder {
             var error_message: [*:0]const u8 = undefined;
             var target: *llvm.Target = undefined;
             if (llvm.Target.getFromTriple(
-                self.target_triple.toSlice(&self).?,
+                self.target_triple.slice(&self).?,
                 &target,
                 &error_message,
             ).toBool()) {
                 defer llvm.disposeMessage(error_message);
 
                 log.err("LLVM failed to parse '{s}': {s}", .{
-                    self.target_triple.toSlice(&self).?,
+                    self.target_triple.slice(&self).?,
                     error_message,
                 });
                 return InitError.InvalidLlvmTriple;
             }
             self.llvm.target = target;
-            self.llvm.module.?.setTarget(self.target_triple.toSlice(&self).?);
+            self.llvm.module.?.setTarget(self.target_triple.slice(&self).?);
         }
     }
 
@@ -4902,6 +6042,16 @@ pub fn init(options: Options) InitError!Builder {
             assert(self.ptrTypeAssumeCapacity(@enumFromInt(addr_space)) == .ptr);
     }
 
+    {
+        if (self.useLibLlvm()) {
+            self.llvm.attribute_kind_ids = try self.gpa.create([Attribute.Kind.len]c_uint);
+            @memset(self.llvm.attribute_kind_ids.?, 0);
+        }
+        try self.attributes_indices.append(self.gpa, 0);
+        assert(try self.attrs(&.{}) == .none);
+        assert(try self.fnAttrs(&.{}) == .none);
+    }
+
     assert(try self.intConst(.i1, 0) == .false);
     assert(try self.intConst(.i1, 1) == .true);
     assert(try self.noneConst(.token) == .none);
@@ -4911,8 +6061,8 @@ pub fn init(options: Options) InitError!Builder {
 
 pub fn deinit(self: *Builder) void {
     self.string_map.deinit(self.gpa);
-    self.string_bytes.deinit(self.gpa);
     self.string_indices.deinit(self.gpa);
+    self.string_bytes.deinit(self.gpa);
 
     self.types.deinit(self.gpa);
     self.next_unique_type_id.deinit(self.gpa);
@@ -4920,6 +6070,11 @@ pub fn deinit(self: *Builder) void {
     self.type_items.deinit(self.gpa);
     self.type_extra.deinit(self.gpa);
 
+    self.attributes.deinit(self.gpa);
+    self.attributes_map.deinit(self.gpa);
+    self.attributes_indices.deinit(self.gpa);
+    self.attributes_extra.deinit(self.gpa);
+
     self.globals.deinit(self.gpa);
     self.next_unique_global_id.deinit(self.gpa);
     self.aliases.deinit(self.gpa);
@@ -4936,6 +6091,8 @@ pub fn deinit(self: *Builder) void {
         self.llvm.constants.deinit(self.gpa);
         self.llvm.globals.deinit(self.gpa);
         self.llvm.types.deinit(self.gpa);
+        self.llvm.attributes.deinit(self.gpa);
+        if (self.llvm.attribute_kind_ids) |attribute_kind_ids| self.gpa.destroy(attribute_kind_ids);
         if (self.llvm.di_builder) |di_builder| di_builder.dispose();
         if (self.llvm.module) |module| module.dispose();
         self.llvm.context.dispose();
@@ -5230,7 +6387,7 @@ pub fn structType(
 
 pub fn opaqueType(self: *Builder, name: String) Allocator.Error!Type {
     try self.string_map.ensureUnusedCapacity(self.gpa, 1);
-    if (name.toSlice(self)) |id| {
+    if (name.slice(self)) |id| {
         const count: usize = comptime std.fmt.count("{d}" ++ .{0}, .{std.math.maxInt(u32)});
         try self.string_bytes.ensureUnusedCapacity(self.gpa, id.len + count);
     }
@@ -5268,6 +6425,99 @@ pub fn namedTypeSetBody(
     }
 }
 
+pub fn attr(self: *Builder, attribute: Attribute) Allocator.Error!Attribute.Index {
+    try self.attributes.ensureUnusedCapacity(self.gpa, 1);
+    if (self.useLibLlvm()) try self.llvm.attributes.ensureUnusedCapacity(self.gpa, 1);
+
+    const gop = self.attributes.getOrPutAssumeCapacity(attribute.toStorage());
+    if (!gop.found_existing) {
+        gop.value_ptr.* = {};
+        if (self.useLibLlvm()) self.llvm.attributes.appendAssumeCapacity(switch (attribute) {
+            else => llvm_attr: {
+                const kind_id = &self.llvm.attribute_kind_ids.?[@intFromEnum(attribute)];
+                if (kind_id.* == 0) {
+                    const name = @tagName(attribute);
+                    kind_id.* = llvm.getEnumAttributeKindForName(name.ptr, name.len);
+                    assert(kind_id.* != 0);
+                }
+                break :llvm_attr switch (attribute) {
+                    else => switch (attribute) {
+                        inline else => |value| self.llvm.context.createEnumAttribute(
+                            kind_id.*,
+                            switch (@TypeOf(value)) {
+                                void => 0,
+                                u32 => value,
+                                Attribute.FpClass,
+                                Attribute.AllocKind,
+                                Attribute.Memory,
+                                => @as(u32, @bitCast(value)),
+                                Alignment => value.toByteUnits() orelse 0,
+                                Attribute.AllocSize,
+                                Attribute.VScaleRange,
+                                => @bitCast(value.toLlvm()),
+                                Attribute.UwTable => @intFromEnum(value),
+                                else => @compileError(
+                                    "bad payload type: " ++ @typeName(@TypeOf(value)),
+                                ),
+                            },
+                        ),
+                        .byval,
+                        .byref,
+                        .preallocated,
+                        .inalloca,
+                        .sret,
+                        .elementtype,
+                        .string,
+                        .none,
+                        => unreachable,
+                    },
+                    .byval,
+                    .byref,
+                    .preallocated,
+                    .inalloca,
+                    .sret,
+                    .elementtype,
+                    => |ty| self.llvm.context.createTypeAttribute(kind_id.*, ty.toLlvm(self)),
+                    .string, .none => unreachable,
+                };
+            },
+            .string => |string_attr| llvm_attr: {
+                const kind = string_attr.kind.slice(self).?;
+                const value = string_attr.value.slice(self).?;
+                break :llvm_attr self.llvm.context.createStringAttribute(
+                    kind.ptr,
+                    @intCast(kind.len),
+                    value.ptr,
+                    @intCast(value.len),
+                );
+            },
+            .none => unreachable,
+        });
+    }
+    return @enumFromInt(gop.index);
+}
+
+pub fn attrs(self: *Builder, attributes: []Attribute.Index) Allocator.Error!Attributes {
+    std.sort.heap(Attribute.Index, attributes, self, struct {
+        pub fn lessThan(builder: *const Builder, lhs: Attribute.Index, rhs: Attribute.Index) bool {
+            const lhs_kind = lhs.getKind(builder);
+            const rhs_kind = rhs.getKind(builder);
+            assert(lhs_kind != rhs_kind);
+            return @intFromEnum(lhs_kind) < @intFromEnum(rhs_kind);
+        }
+    }.lessThan);
+    return @enumFromInt(try self.attrGeneric(@ptrCast(attributes)));
+}
+
+pub fn fnAttrs(self: *Builder, fn_attributes: []const Attributes) Allocator.Error!FunctionAttributes {
+    return @enumFromInt(try self.attrGeneric(@ptrCast(
+        fn_attributes[0..if (std.mem.lastIndexOfNone(Attributes, fn_attributes, &.{.none})) |last|
+            last + 1
+        else
+            0],
+    )));
+}
+
 pub fn addGlobal(self: *Builder, name: String, global: Global) Allocator.Error!Global.Index {
     assert(!name.isAnon());
     try self.ensureUnusedTypeCapacity(1, NoExtra, 0);
@@ -5295,7 +6545,7 @@ pub fn addGlobalAssumeCapacity(self: *Builder, name: String, global: Global) Glo
 
         const unique_gop = self.next_unique_global_id.getOrPutAssumeCapacity(name);
         if (!unique_gop.found_existing) unique_gop.value_ptr.* = 2;
-        id = self.fmtAssumeCapacity("{s}.{d}", .{ name.toSlice(self).?, unique_gop.value_ptr.* });
+        id = self.fmtAssumeCapacity("{s}.{d}", .{ name.slice(self).?, unique_gop.value_ptr.* });
         unique_gop.value_ptr.* += 1;
     }
 }
@@ -5309,8 +6559,9 @@ pub fn intConst(self: *Builder, ty: Type, value: anytype) Allocator.Error!Consta
         switch (@typeInfo(@TypeOf(value))) {
             .Int => |info| std.math.big.int.calcTwosCompLimbCount(info.bits),
             .ComptimeInt => std.math.big.int.calcLimbLen(value),
-            else => @compileError("intConst expected an integral value, got " ++
-                @typeName(@TypeOf(value))),
+            else => @compileError(
+                "intConst expected an integral value, got " ++ @typeName(@TypeOf(value)),
+            ),
         }
     ]std.math.big.Limb = undefined;
     return self.bigIntConst(ty, std.math.big.int.Mutable.init(&limbs, value).toConst());
@@ -5770,7 +7021,7 @@ pub fn printUnbuffered(
         \\; ModuleID = '{s}'
         \\source_filename = {"}
         \\
-    , .{ self.source_filename.toSlice(self).?, self.source_filename.fmt(self) });
+    , .{ self.source_filename.slice(self).?, self.source_filename.fmt(self) });
     if (self.data_layout != .none) try writer.print(
         \\target datalayout = {"}
         \\
@@ -5780,11 +7031,13 @@ pub fn printUnbuffered(
         \\
     , .{self.target_triple.fmt(self)});
     try writer.writeByte('\n');
+
     for (self.types.keys(), self.types.values()) |id, ty| try writer.print(
         \\%{} = type {}
         \\
     , .{ id.fmt(self), ty.fmt(self) });
     try writer.writeByte('\n');
+
     for (self.variables.items) |variable| {
         if (variable.global.getReplacement(self) != .none) continue;
         const global = variable.global.ptrConst(self);
@@ -5808,28 +7061,42 @@ pub fn printUnbuffered(
         });
     }
     try writer.writeByte('\n');
+
+    var attribute_groups: std.AutoArrayHashMapUnmanaged(Attributes, void) = .{};
+    defer attribute_groups.deinit(self.gpa);
     for (0.., self.functions.items) |function_i, function| {
         const function_index: Function.Index = @enumFromInt(function_i);
         if (function.global.getReplacement(self) != .none) continue;
         const global = function.global.ptrConst(self);
         const params_len = global.type.functionParameters(self).len;
+        const function_attributes = function.attributes.func(self);
+        if (function_attributes != .none) try writer.print(
+            \\; Function Attrs:{}
+            \\
+        , .{function_attributes.fmt(self)});
         try writer.print(
-            \\{s}{}{}{}{} {} {}(
+            \\{s}{}{}{}{}{}{"} {} {}(
         , .{
             if (function.instructions.len > 0) "define" else "declare",
             global.linkage,
             global.preemption,
             global.visibility,
             global.dll_storage_class,
+            function.call_conv,
+            function.attributes.ret(self).fmt(self),
             global.type.functionReturn(self).fmt(self),
             function.global.fmt(self),
         });
         for (0..params_len) |arg| {
             if (arg > 0) try writer.writeAll(", ");
+            try writer.print(
+                \\{%}{"}
+            , .{
+                global.type.functionParameters(self)[arg].fmt(self),
+                function.attributes.param(arg, self).fmt(self),
+            });
             if (function.instructions.len > 0)
-                try writer.print("{%}", .{function.arg(@intCast(arg)).fmt(function_index, self)})
-            else
-                try writer.print("{%}", .{global.type.functionParameters(self)[arg].fmt(self)});
+                try writer.print(" {}", .{function.arg(@intCast(arg)).fmt(function_index, self)});
         }
         switch (global.type.functionKind(self)) {
             .normal => {},
@@ -5838,7 +7105,11 @@ pub fn printUnbuffered(
                 try writer.writeAll("...");
             },
         }
-        try writer.print("){}{}", .{ global.unnamed_addr, function.alignment });
+        try writer.print("){}{}", .{ global.unnamed_addr, global.addr_space });
+        if (function_attributes != .none) try writer.print(" #{d}", .{
+            (try attribute_groups.getOrPutValue(self.gpa, function_attributes, {})).index,
+        });
+        try writer.print("{}", .{function.alignment});
         if (function.instructions.len > 0) {
             var block_incoming_len: u32 = undefined;
             try writer.writeAll(" {\n");
@@ -5992,6 +7263,48 @@ pub fn printUnbuffered(
                             extra.@"else".toInst(&function).fmt(function_index, self),
                         });
                     },
+                    .call,
+                    .@"call fast",
+                    .@"musttail call",
+                    .@"musttail call fast",
+                    .@"notail call",
+                    .@"notail call fast",
+                    .@"tail call",
+                    .@"tail call fast",
+                    => |tag| {
+                        var extra =
+                            function.extraDataTrail(Function.Instruction.Call, instruction.data);
+                        const args = extra.trail.next(extra.data.args_len, Value, &function);
+                        try writer.writeAll("  ");
+                        const ret_ty = extra.data.ty.functionReturn(self);
+                        switch (ret_ty) {
+                            .void => {},
+                            else => try writer.print("%{} = ", .{
+                                instruction_index.name(&function).fmt(self),
+                            }),
+                            .none => unreachable,
+                        }
+                        try writer.print("{s}{}{}{} {%} {}(", .{
+                            @tagName(tag),
+                            extra.data.info.call_conv,
+                            extra.data.attributes.ret(self).fmt(self),
+                            extra.data.callee.typeOf(function_index, self).pointerAddrSpace(self),
+                            switch (extra.data.ty.functionKind(self)) {
+                                .normal => ret_ty,
+                                .vararg => extra.data.ty,
+                            }.fmt(self),
+                            extra.data.callee.fmt(function_index, self),
+                        });
+                        for (0.., args) |arg_index, arg| {
+                            if (arg_index > 0) try writer.writeAll(", ");
+                            try writer.print("{%}{} {}", .{
+                                arg.typeOf(function_index, self).fmt(self),
+                                extra.data.attributes.param(arg_index, self).fmt(self),
+                                arg.fmt(function_index, self),
+                            });
+                        }
+                        try writer.print("){}\n", .{extra.data.attributes.func(self).fmt(self)});
+                    },
                     .extractelement => |tag| {
                         const extra =
                             function.extraData(Function.Instruction.ExtractElement, instruction.data);
@@ -6218,6 +7531,12 @@ pub fn printUnbuffered(
         }
         try writer.writeAll("\n\n");
     }
+
+    for (0.., attribute_groups.keys()) |attribute_group_index, attribute_group|
+        try writer.print(
+            \\attribute #{d} = {{{"} }}
+            \\
+        , .{ attribute_group_index, attribute_group.fmt(self) });
 }
 
 pub inline fn useLibLlvm(self: *const Builder) bool {
@@ -6238,7 +7557,7 @@ fn isValidIdentifier(id: []const u8) bool {
 fn ensureUnusedGlobalCapacity(self: *Builder, name: String) Allocator.Error!void {
     if (self.useLibLlvm()) try self.llvm.globals.ensureUnusedCapacity(self.gpa, 1);
     try self.string_map.ensureUnusedCapacity(self.gpa, 1);
-    if (name.toSlice(self)) |id| {
+    if (name.slice(self)) |id| {
         const count: usize = comptime std.fmt.count("{d}" ++ .{0}, .{std.math.maxInt(u32)});
         try self.string_bytes.ensureUnusedCapacity(self.gpa, id.len + count);
     }
@@ -6528,14 +7847,14 @@ fn opaqueTypeAssumeCapacity(self: *Builder, name: String) Type {
             const result: Type = @enumFromInt(gop.index);
             type_gop.value_ptr.* = result;
             if (self.useLibLlvm()) self.llvm.types.appendAssumeCapacity(
-                self.llvm.context.structCreateNamed(id.toSlice(self) orelse ""),
+                self.llvm.context.structCreateNamed(id.slice(self) orelse ""),
             );
             return result;
         }
 
         const unique_gop = self.next_unique_type_id.getOrPutAssumeCapacity(name);
         if (!unique_gop.found_existing) unique_gop.value_ptr.* = 2;
-        id = self.fmtAssumeCapacity("{s}.{d}", .{ name.toSlice(self).?, unique_gop.value_ptr.* });
+        id = self.fmtAssumeCapacity("{s}.{d}", .{ name.slice(self).?, unique_gop.value_ptr.* });
         unique_gop.value_ptr.* += 1;
     }
 }
@@ -6636,6 +7955,30 @@ fn typeExtraData(self: *const Builder, comptime T: type, index: Type.Item.ExtraI
     return self.typeExtraDataTrail(T, index).data;
 }
 
+fn attrGeneric(self: *Builder, data: []const u32) Allocator.Error!u32 {
+    try self.attributes_map.ensureUnusedCapacity(self.gpa, 1);
+    try self.attributes_indices.ensureUnusedCapacity(self.gpa, 1);
+    try self.attributes_extra.ensureUnusedCapacity(self.gpa, data.len);
+
+    const Adapter = struct {
+        builder: *const Builder,
+        pub fn hash(_: @This(), key: []const u32) u32 {
+            return @truncate(std.hash.Wyhash.hash(1, std.mem.sliceAsBytes(key)));
+        }
+        pub fn eql(ctx: @This(), lhs_key: []const u32, _: void, rhs_index: usize) bool {
+            const start = ctx.builder.attributes_indices.items[rhs_index];
+            const end = ctx.builder.attributes_indices.items[rhs_index + 1];
+            return std.mem.eql(u32, lhs_key, ctx.builder.attributes_extra.items[start..end]);
+        }
+    };
+    const gop = self.attributes_map.getOrPutAssumeCapacityAdapted(data, Adapter{ .builder = self });
+    if (!gop.found_existing) {
+        self.attributes_extra.appendSliceAssumeCapacity(data);
+        self.attributes_indices.appendAssumeCapacity(@intCast(self.attributes_extra.items.len));
+    }
+    return @intCast(gop.index);
+}
+
 fn bigIntConstAssumeCapacity(
     self: *Builder,
     ty: Type,
@@ -7073,7 +8416,7 @@ fn arrayConstAssumeCapacity(
 }
 
 fn stringConstAssumeCapacity(self: *Builder, val: String) Constant {
-    const slice = val.toSlice(self).?;
+    const slice = val.slice(self).?;
     const ty = self.arrayTypeAssumeCapacity(slice.len, .i8);
     if (std.mem.allEqual(u8, slice, 0)) return self.zeroInitConstAssumeCapacity(ty);
     const result = self.getOrPutConstantNoExtraAssumeCapacity(
@@ -7086,7 +8429,7 @@ fn stringConstAssumeCapacity(self: *Builder, val: String) Constant {
 }
 
 fn stringNullConstAssumeCapacity(self: *Builder, val: String) Constant {
-    const slice = val.toSlice(self).?;
+    const slice = val.slice(self).?;
     const ty = self.arrayTypeAssumeCapacity(slice.len + 1, .i8);
     if (std.mem.allEqual(u8, slice, 0)) return self.zeroInitConstAssumeCapacity(ty);
     const result = self.getOrPutConstantNoExtraAssumeCapacity(
src/codegen/llvm.zig
@@ -359,7 +359,7 @@ const DataLayoutBuilder = struct {
                     .macho => 'o', // Mach-O mangling: Private symbols get `L` prefix.
                     // Other symbols get a `_` prefix.
                     .coff => switch (self.target.os.tag) {
-                        .windows => switch (self.target.cpu.arch) {
+                        .uefi, .windows => switch (self.target.cpu.arch) {
                             .x86 => 'x', // Windows x86 COFF mangling: Private symbols get the usual
                             // prefix. Regular C symbols get a `_` prefix. Functions with `__stdcall`,
                             //`__fastcall`, and `__vectorcall` have custom mangling that appends `@N`
@@ -794,7 +794,7 @@ pub const Object = struct {
                 builder.llvm.di_compile_unit = builder.llvm.di_builder.?.createCompileUnit(
                     DW.LANG.C99,
                     builder.llvm.di_builder.?.createFile(options.root_name, compile_unit_dir_z),
-                    producer.toSlice(&builder).?,
+                    producer.slice(&builder).?,
                     options.optimize_mode != .Debug,
                     "", // flags
                     0, // runtime version
@@ -830,7 +830,7 @@ pub const Object = struct {
 
             target_machine = llvm.TargetMachine.create(
                 builder.llvm.target.?,
-                builder.target_triple.toSlice(&builder).?,
+                builder.target_triple.slice(&builder).?,
                 if (options.target.cpu.model.llvm_name) |s| s.ptr else null,
                 options.llvm_cpu_features,
                 opt_level,
@@ -861,7 +861,7 @@ pub const Object = struct {
                 defer llvm.disposeMessage(rep);
                 std.testing.expectEqualStrings(
                     std.mem.span(rep),
-                    builder.data_layout.toSlice(&builder).?,
+                    builder.data_layout.slice(&builder).?,
                 ) catch unreachable;
             }
         }
@@ -963,7 +963,7 @@ pub const Object = struct {
 
             llvm_error.* = try o.builder.structConst(llvm_slice_ty, &.{
                 global_index.toConst(),
-                try o.builder.intConst(llvm_usize_ty, name.toSlice(&o.builder).?.len),
+                try o.builder.intConst(llvm_usize_ty, name.slice(&o.builder).?.len),
             });
         }
 
@@ -1223,6 +1223,7 @@ pub const Object = struct {
         const func = mod.funcInfo(func_index);
         const decl_index = func.owner_decl;
         const decl = mod.declPtr(decl_index);
+        const fn_info = mod.typeToFunc(decl.ty).?;
         const target = mod.getTarget();
         const ip = &mod.intern_pool;
 
@@ -1237,28 +1238,43 @@ pub const Object = struct {
         const global = function.ptrConst(&o.builder).global;
         const llvm_func = global.toLlvm(&o.builder);
 
+        var attributes = try function.ptrConst(&o.builder).attributes.toWip(&o.builder);
+        defer attributes.deinit(&o.builder);
+
         if (func.analysis(ip).is_noinline) {
+            try attributes.addFnAttr(.@"noinline", &o.builder);
             o.addFnAttr(llvm_func, "noinline");
         } else {
+            _ = try attributes.removeFnAttr(.@"noinline");
             Object.removeFnAttr(llvm_func, "noinline");
         }
 
         if (func.analysis(ip).stack_alignment.toByteUnitsOptional()) |alignment| {
+            try attributes.addFnAttr(.{ .alignstack = Builder.Alignment.fromByteUnits(alignment) }, &o.builder);
+            try attributes.addFnAttr(.@"noinline", &o.builder);
             o.addFnAttrInt(llvm_func, "alignstack", alignment);
             o.addFnAttr(llvm_func, "noinline");
         } else {
+            _ = try attributes.removeFnAttr(.alignstack);
             Object.removeFnAttr(llvm_func, "alignstack");
         }
 
         if (func.analysis(ip).is_cold) {
+            try attributes.addFnAttr(.cold, &o.builder);
             o.addFnAttr(llvm_func, "cold");
         } else {
+            _ = try attributes.removeFnAttr(.cold);
             Object.removeFnAttr(llvm_func, "cold");
         }
 
         // TODO: disable this if safety is off for the function scope
         const ssp_buf_size = mod.comp.bin_file.options.stack_protector;
         if (ssp_buf_size != 0) {
+            try attributes.addFnAttr(.sspstrong, &o.builder);
+            try attributes.addFnAttr(.{ .string = .{
+                .kind = try o.builder.string("stack-protector-buffer-size"),
+                .value = try o.builder.fmt("{d}", .{ssp_buf_size}),
+            } }, &o.builder);
             var buf: [12]u8 = undefined;
             const arg = std.fmt.bufPrintZ(&buf, "{d}", .{ssp_buf_size}) catch unreachable;
             o.addFnAttr(llvm_func, "sspstrong");
@@ -1267,8 +1283,16 @@ pub const Object = struct {
 
         // TODO: disable this if safety is off for the function scope
         if (mod.comp.bin_file.options.stack_check) {
+            try attributes.addFnAttr(.{ .string = .{
+                .kind = try o.builder.string("probe-stack"),
+                .value = try o.builder.string("__zig_probe_stack"),
+            } }, &o.builder);
             o.addFnAttrString(llvm_func, "probe-stack", "__zig_probe_stack");
         } else if (target.os.tag == .uefi) {
+            try attributes.addFnAttr(.{ .string = .{
+                .kind = try o.builder.string("no-stack-arg-probe"),
+                .value = .empty,
+            } }, &o.builder);
             o.addFnAttrString(llvm_func, "no-stack-arg-probe", "");
         }
 
@@ -1286,18 +1310,22 @@ pub const Object = struct {
         var llvm_arg_i: u32 = 0;
 
         // This gets the LLVM values from the function and stores them in `dg.args`.
-        const fn_info = mod.typeToFunc(decl.ty).?;
         const sret = firstParamSRet(fn_info, mod);
         const ret_ptr: Builder.Value = if (sret) param: {
             const param = wip.arg(llvm_arg_i);
             llvm_arg_i += 1;
             break :param param;
         } else .none;
-        const gpa = o.gpa;
 
         if (ccAbiPromoteInt(fn_info.cc, mod, fn_info.return_type.toType())) |s| switch (s) {
-            .signed => o.addAttr(llvm_func, 0, "signext"),
-            .unsigned => o.addAttr(llvm_func, 0, "zeroext"),
+            .signed => {
+                try attributes.addRetAttr(.signext, &o.builder);
+                o.addAttr(llvm_func, 0, "signext");
+            },
+            .unsigned => {
+                try attributes.addRetAttr(.zeroext, &o.builder);
+                o.addAttr(llvm_func, 0, "zeroext");
+            },
         };
 
         const err_return_tracing = fn_info.return_type.toType().isError(mod) and
@@ -1312,6 +1340,7 @@ pub const Object = struct {
         // This is the list of args we will use that correspond directly to the AIR arg
         // instructions. Depending on the calling convention, this list is not necessarily
         // a bijection with the actual LLVM parameters of the function.
+        const gpa = o.gpa;
         var args: std.ArrayListUnmanaged(Builder.Value) = .{};
         defer args.deinit(gpa);
 
@@ -1337,7 +1366,7 @@ pub const Object = struct {
                         } else {
                             args.appendAssumeCapacity(param);
 
-                            o.addByValParamAttrs(llvm_func, param_ty, param_index, fn_info, @intCast(llvm_arg_i));
+                            try o.addByValParamAttrsOld(&attributes, llvm_func, param_ty, param_index, fn_info, llvm_arg_i);
                         }
                         llvm_arg_i += 1;
                     },
@@ -1347,7 +1376,7 @@ pub const Object = struct {
                         const param = wip.arg(llvm_arg_i);
                         const alignment = Builder.Alignment.fromByteUnits(param_ty.abiAlignment(mod));
 
-                        o.addByRefParamAttrs(llvm_func, @intCast(llvm_arg_i), @intCast(alignment.toByteUnits() orelse 0), it.byval_attr, param_llvm_ty);
+                        try o.addByRefParamAttrsOld(&attributes, llvm_func, llvm_arg_i, alignment, it.byval_attr, param_llvm_ty);
                         llvm_arg_i += 1;
 
                         if (isByRef(param_ty, mod)) {
@@ -1362,7 +1391,8 @@ pub const Object = struct {
                         const param = wip.arg(llvm_arg_i);
                         const alignment = Builder.Alignment.fromByteUnits(param_ty.abiAlignment(mod));
 
-                        o.addArgAttr(llvm_func, @intCast(llvm_arg_i), "noundef");
+                        try attributes.addParamAttr(llvm_arg_i, .noundef, &o.builder);
+                        o.addArgAttr(llvm_func, llvm_arg_i, "noundef");
                         llvm_arg_i += 1;
 
                         if (isByRef(param_ty, mod)) {
@@ -1398,21 +1428,28 @@ pub const Object = struct {
 
                         if (math.cast(u5, it.zig_index - 1)) |i| {
                             if (@as(u1, @truncate(fn_info.noalias_bits >> i)) != 0) {
-                                o.addArgAttr(llvm_func, @intCast(llvm_arg_i), "noalias");
+                                try attributes.addParamAttr(llvm_arg_i, .@"noalias", &o.builder);
+                                o.addArgAttr(llvm_func, llvm_arg_i, "noalias");
                             }
                         }
                         if (param_ty.zigTypeTag(mod) != .Optional) {
-                            o.addArgAttr(llvm_func, @intCast(llvm_arg_i), "nonnull");
+                            try attributes.addParamAttr(llvm_arg_i, .nonnull, &o.builder);
+                            o.addArgAttr(llvm_func, llvm_arg_i, "nonnull");
                         }
                         if (ptr_info.flags.is_const) {
-                            o.addArgAttr(llvm_func, @intCast(llvm_arg_i), "readonly");
+                            try attributes.addParamAttr(llvm_arg_i, .readonly, &o.builder);
+                            o.addArgAttr(llvm_func, llvm_arg_i, "readonly");
                         }
-                        const elem_align = ptr_info.flags.alignment.toByteUnitsOptional() orelse
-                            @max(ptr_info.child.toType().abiAlignment(mod), 1);
-                        o.addArgAttrInt(llvm_func, @intCast(llvm_arg_i), "align", elem_align);
-                        const ptr_param = wip.arg(llvm_arg_i + 0);
-                        const len_param = wip.arg(llvm_arg_i + 1);
-                        llvm_arg_i += 2;
+                        const elem_align = Builder.Alignment.fromByteUnits(
+                            ptr_info.flags.alignment.toByteUnitsOptional() orelse
+                                @max(ptr_info.child.toType().abiAlignment(mod), 1),
+                        );
+                        try attributes.addParamAttr(llvm_arg_i, .{ .@"align" = elem_align }, &o.builder);
+                        o.addArgAttrInt(llvm_func, llvm_arg_i, "align", elem_align.toByteUnits() orelse 0);
+                        const ptr_param = wip.arg(llvm_arg_i);
+                        llvm_arg_i += 1;
+                        const len_param = wip.arg(llvm_arg_i);
+                        llvm_arg_i += 1;
 
                         const slice_llvm_ty = try o.lowerType(param_ty);
                         args.appendAssumeCapacity(
@@ -1482,6 +1519,8 @@ pub const Object = struct {
             }
         }
 
+        function.ptr(&o.builder).attributes = try attributes.finish(&o.builder);
+
         var di_file: ?*llvm.DIFile = null;
         var di_scope: ?*llvm.DIScope = null;
 
@@ -1618,7 +1657,7 @@ pub const Object = struct {
                 llvm_global.setDLLStorageClass(.Default);
             }
             if (self.di_map.get(decl)) |di_node| {
-                const decl_name_slice = decl_name.toSlice(&self.builder).?;
+                const decl_name_slice = decl_name.slice(&self.builder).?;
                 if (try decl.isFunction(mod)) {
                     const di_func: *llvm.DISubprogram = @ptrCast(di_node);
                     const linkage_name = llvm.MDString.get(self.builder.llvm.context, decl_name_slice.ptr, decl_name_slice.len);
@@ -1655,7 +1694,7 @@ pub const Object = struct {
                 llvm_global.setDLLStorageClass(.DLLExport);
             }
             if (self.di_map.get(decl)) |di_node| {
-                const exp_name_slice = exp_name.toSlice(&self.builder).?;
+                const exp_name_slice = exp_name.slice(&self.builder).?;
                 if (try decl.isFunction(mod)) {
                     const di_func: *llvm.DISubprogram = @ptrCast(di_node);
                     const linkage_name = llvm.MDString.get(self.builder.llvm.context, exp_name_slice.ptr, exp_name_slice.len);
@@ -2816,7 +2855,7 @@ pub const Object = struct {
         const fqn = try o.builder.string(ip.stringToSlice(try decl.getFullyQualifiedName(mod)));
 
         const llvm_addrspace = toLlvmAddressSpace(decl.@"addrspace", target);
-        const llvm_fn = o.llvm_module.addFunctionInAddressSpace(fqn.toSlice(&o.builder).?, fn_type.toLlvm(&o.builder), @intFromEnum(llvm_addrspace));
+        const llvm_fn = o.llvm_module.addFunctionInAddressSpace(fqn.slice(&o.builder).?, fn_type.toLlvm(&o.builder), @intFromEnum(llvm_addrspace));
 
         var global = Builder.Global{
             .type = fn_type,
@@ -2826,6 +2865,9 @@ pub const Object = struct {
             .global = @enumFromInt(o.builder.globals.count()),
         };
 
+        var attributes: Builder.FunctionAttributes.Wip = .{};
+        defer attributes.deinit(&o.builder);
+
         const is_extern = decl.isExtern(mod);
         if (!is_extern) {
             global.linkage = .internal;
@@ -2834,43 +2876,64 @@ pub const Object = struct {
             llvm_fn.setUnnamedAddr(.True);
         } else {
             if (target.isWasm()) {
+                try attributes.addFnAttr(.{ .string = .{
+                    .kind = try o.builder.string("wasm-import-name"),
+                    .value = try o.builder.string(ip.stringToSlice(decl.name)),
+                } }, &o.builder);
                 o.addFnAttrString(llvm_fn, "wasm-import-name", ip.stringToSlice(decl.name));
                 if (ip.stringToSliceUnwrap(decl.getOwnedExternFunc(mod).?.lib_name)) |lib_name| {
                     if (!std.mem.eql(u8, lib_name, "c")) {
+                        try attributes.addFnAttr(.{ .string = .{
+                            .kind = try o.builder.string("wasm-import-module"),
+                            .value = try o.builder.string(lib_name),
+                        } }, &o.builder);
                         o.addFnAttrString(llvm_fn, "wasm-import-module", lib_name);
                     }
                 }
             }
         }
 
+        var llvm_arg_i: u32 = 0;
         if (sret) {
-            o.addArgAttr(llvm_fn, 0, "nonnull"); // Sret pointers must not be address 0
-            o.addArgAttr(llvm_fn, 0, "noalias");
+            // Sret pointers must not be address 0
+            try attributes.addParamAttr(llvm_arg_i, .nonnull, &o.builder);
+            try attributes.addParamAttr(llvm_arg_i, .@"noalias", &o.builder);
+            o.addArgAttr(llvm_fn, llvm_arg_i, "nonnull"); // Sret pointers must not be address 0
+            o.addArgAttr(llvm_fn, llvm_arg_i, "noalias");
+
+            const raw_llvm_ret_ty = try o.lowerType(fn_info.return_type.toType());
+            try attributes.addParamAttr(llvm_arg_i, .{ .sret = raw_llvm_ret_ty }, &o.builder);
+            llvm_fn.addSretAttr(raw_llvm_ret_ty.toLlvm(&o.builder));
 
-            const raw_llvm_ret_ty = (try o.lowerType(fn_info.return_type.toType())).toLlvm(&o.builder);
-            llvm_fn.addSretAttr(raw_llvm_ret_ty);
+            llvm_arg_i += 1;
         }
 
         const err_return_tracing = fn_info.return_type.toType().isError(mod) and
             mod.comp.bin_file.options.error_return_tracing;
 
         if (err_return_tracing) {
-            o.addArgAttr(llvm_fn, @intFromBool(sret), "nonnull");
+            try attributes.addParamAttr(llvm_arg_i, .nonnull, &o.builder);
+            o.addArgAttr(llvm_fn, llvm_arg_i, "nonnull");
+            llvm_arg_i += 1;
         }
 
         switch (fn_info.cc) {
             .Unspecified, .Inline => {
+                function.call_conv = .fastcc;
                 llvm_fn.setFunctionCallConv(.Fast);
             },
             .Naked => {
+                try attributes.addFnAttr(.naked, &o.builder);
                 o.addFnAttr(llvm_fn, "naked");
             },
             .Async => {
+                function.call_conv = .fastcc;
                 llvm_fn.setFunctionCallConv(.Fast);
                 @panic("TODO: LLVM backend lower async function");
             },
             else => {
-                llvm_fn.setFunctionCallConv(toLlvmCallConv(fn_info.cc, target));
+                function.call_conv = toLlvmCallConv(fn_info.cc, target);
+                llvm_fn.setFunctionCallConv(@enumFromInt(@intFromEnum(function.call_conv)));
             },
         }
 
@@ -2880,9 +2943,10 @@ pub const Object = struct {
         }
 
         // Function attributes that are independent of analysis results of the function body.
-        o.addCommonFnAttributes(llvm_fn);
+        try o.addCommonFnAttributes(&attributes, llvm_fn);
 
         if (fn_info.return_type == .noreturn_type) {
+            try attributes.addFnAttr(.noreturn, &o.builder);
             o.addFnAttr(llvm_fn, "noreturn");
         }
 
@@ -2890,23 +2954,24 @@ pub const Object = struct {
         // because functions with bodies are handled in `updateFunc`.
         if (is_extern) {
             var it = iterateParamTypes(o, fn_info);
-            it.llvm_index += @intFromBool(sret);
-            it.llvm_index += @intFromBool(err_return_tracing);
+            it.llvm_index = llvm_arg_i;
             while (try it.next()) |lowering| switch (lowering) {
                 .byval => {
                     const param_index = it.zig_index - 1;
                     const param_ty = fn_info.param_types.get(ip)[param_index].toType();
                     if (!isByRef(param_ty, mod)) {
-                        o.addByValParamAttrs(llvm_fn, param_ty, param_index, fn_info, it.llvm_index - 1);
+                        try o.addByValParamAttrsOld(&attributes, llvm_fn, param_ty, param_index, fn_info, it.llvm_index - 1);
                     }
                 },
                 .byref => {
                     const param_ty = fn_info.param_types.get(ip)[it.zig_index - 1];
                     const param_llvm_ty = try o.lowerType(param_ty.toType());
-                    const alignment = param_ty.toType().abiAlignment(mod);
-                    o.addByRefParamAttrs(llvm_fn, it.llvm_index - 1, alignment, it.byval_attr, param_llvm_ty);
+                    const alignment =
+                        Builder.Alignment.fromByteUnits(param_ty.toType().abiAlignment(mod));
+                    try o.addByRefParamAttrsOld(&attributes, llvm_fn, it.llvm_index - 1, alignment, it.byval_attr, param_llvm_ty);
                 },
                 .byref_mut => {
+                    try attributes.addParamAttr(it.llvm_index - 1, .noundef, &o.builder);
                     o.addArgAttr(llvm_fn, it.llvm_index - 1, "noundef");
                 },
                 // No attributes needed for these.
@@ -2924,25 +2989,42 @@ pub const Object = struct {
             };
         }
 
+        function.attributes = try attributes.finish(&o.builder);
+
         try o.builder.llvm.globals.append(o.gpa, llvm_fn);
         gop.value_ptr.* = try o.builder.addGlobal(fqn, global);
         try o.builder.functions.append(o.gpa, function);
         return global.kind.function;
     }
 
-    fn addCommonFnAttributes(o: *Object, llvm_fn: *llvm.Value) void {
+    fn addCommonFnAttributes(
+        o: *Object,
+        attributes: *Builder.FunctionAttributes.Wip,
+        llvm_fn: *llvm.Value,
+    ) Allocator.Error!void {
         const comp = o.module.comp;
 
         if (!comp.bin_file.options.red_zone) {
+            try attributes.addFnAttr(.noredzone, &o.builder);
             o.addFnAttr(llvm_fn, "noredzone");
         }
         if (comp.bin_file.options.omit_frame_pointer) {
+            try attributes.addFnAttr(.{ .string = .{
+                .kind = try o.builder.string("frame-pointer"),
+                .value = try o.builder.string("none"),
+            } }, &o.builder);
             o.addFnAttrString(llvm_fn, "frame-pointer", "none");
         } else {
+            try attributes.addFnAttr(.{ .string = .{
+                .kind = try o.builder.string("frame-pointer"),
+                .value = try o.builder.string("all"),
+            } }, &o.builder);
             o.addFnAttrString(llvm_fn, "frame-pointer", "all");
         }
+        try attributes.addFnAttr(.nounwind, &o.builder);
         o.addFnAttr(llvm_fn, "nounwind");
         if (comp.unwind_tables) {
+            try attributes.addFnAttr(.{ .uwtable = Builder.Attribute.UwTable.default }, &o.builder);
             o.addFnAttrInt(llvm_fn, "uwtable", 2);
         }
         if (comp.bin_file.options.skip_linker_dependencies or
@@ -2953,22 +3035,38 @@ pub const Object = struct {
             // and llvm detects that the body is equivalent to memcpy, it may replace the
             // body of memcpy with a call to memcpy, which would then cause a stack
             // overflow instead of performing memcpy.
+            try attributes.addFnAttr(.nobuiltin, &o.builder);
             o.addFnAttr(llvm_fn, "nobuiltin");
         }
         if (comp.bin_file.options.optimize_mode == .ReleaseSmall) {
+            try attributes.addFnAttr(.minsize, &o.builder);
+            try attributes.addFnAttr(.optsize, &o.builder);
             o.addFnAttr(llvm_fn, "minsize");
             o.addFnAttr(llvm_fn, "optsize");
         }
         if (comp.bin_file.options.tsan) {
+            try attributes.addFnAttr(.sanitize_thread, &o.builder);
             o.addFnAttr(llvm_fn, "sanitize_thread");
         }
         if (comp.getTarget().cpu.model.llvm_name) |s| {
+            try attributes.addFnAttr(.{ .string = .{
+                .kind = try o.builder.string("target-cpu"),
+                .value = try o.builder.string(s),
+            } }, &o.builder);
             llvm_fn.addFunctionAttr("target-cpu", s);
         }
         if (comp.bin_file.options.llvm_cpu_features) |s| {
+            try attributes.addFnAttr(.{ .string = .{
+                .kind = try o.builder.string("target-features"),
+                .value = try o.builder.string(std.mem.span(s)),
+            } }, &o.builder);
             llvm_fn.addFunctionAttr("target-features", s);
         }
         if (comp.getTarget().cpu.arch.isBpf()) {
+            try attributes.addFnAttr(.{ .string = .{
+                .kind = try o.builder.string("no-builtins"),
+                .value = .empty,
+            } }, &o.builder);
             llvm_fn.addFunctionAttr("no-builtins", "");
         }
     }
@@ -3002,7 +3100,7 @@ pub const Object = struct {
             fqn;
         const llvm_global = o.llvm_module.addGlobalInAddressSpace(
             global.type.toLlvm(&o.builder),
-            fqn.toSlice(&o.builder).?,
+            fqn.slice(&o.builder).?,
             @intFromEnum(global.addr_space),
         );
 
@@ -4403,47 +4501,114 @@ pub const Object = struct {
 
     fn addByValParamAttrs(
         o: *Object,
+        attributes: *Builder.FunctionAttributes.Wip,
+        param_ty: Type,
+        param_index: u32,
+        fn_info: InternPool.Key.FuncType,
+        llvm_arg_i: u32,
+    ) Allocator.Error!void {
+        const mod = o.module;
+        if (param_ty.isPtrAtRuntime(mod)) {
+            const ptr_info = param_ty.ptrInfo(mod);
+            if (math.cast(u5, param_index)) |i| {
+                if (@as(u1, @truncate(fn_info.noalias_bits >> i)) != 0) {
+                    try attributes.addParamAttr(llvm_arg_i, .@"noalias", &o.builder);
+                }
+            }
+            if (!param_ty.isPtrLikeOptional(mod) and !ptr_info.flags.is_allowzero) {
+                try attributes.addParamAttr(llvm_arg_i, .nonnull, &o.builder);
+            }
+            if (ptr_info.flags.is_const) {
+                try attributes.addParamAttr(llvm_arg_i, .readonly, &o.builder);
+            }
+            const elem_align = Builder.Alignment.fromByteUnits(
+                ptr_info.flags.alignment.toByteUnitsOptional() orelse
+                    @max(ptr_info.child.toType().abiAlignment(mod), 1),
+            );
+            try attributes.addParamAttr(llvm_arg_i, .{ .@"align" = elem_align }, &o.builder);
+        } else if (ccAbiPromoteInt(fn_info.cc, mod, param_ty)) |s| switch (s) {
+            .signed => try attributes.addParamAttr(llvm_arg_i, .signext, &o.builder),
+            .unsigned => try attributes.addParamAttr(llvm_arg_i, .zeroext, &o.builder),
+        };
+    }
+
+    fn addByRefParamAttrs(
+        o: *Object,
+        attributes: *Builder.FunctionAttributes.Wip,
+        llvm_arg_i: u32,
+        alignment: Builder.Alignment,
+        byval_attr: bool,
+        param_llvm_ty: Builder.Type,
+    ) Allocator.Error!void {
+        try attributes.addParamAttr(llvm_arg_i, .nonnull, &o.builder);
+        try attributes.addParamAttr(llvm_arg_i, .readonly, &o.builder);
+        try attributes.addParamAttr(llvm_arg_i, .{ .@"align" = alignment }, &o.builder);
+        if (byval_attr) {
+            try attributes.addParamAttr(llvm_arg_i, .{ .byval = param_llvm_ty }, &o.builder);
+        }
+    }
+
+    fn addByValParamAttrsOld(
+        o: *Object,
+        attributes: *Builder.FunctionAttributes.Wip,
         llvm_fn: *llvm.Value,
         param_ty: Type,
         param_index: u32,
         fn_info: InternPool.Key.FuncType,
         llvm_arg_i: u32,
-    ) void {
+    ) Allocator.Error!void {
         const mod = o.module;
         if (param_ty.isPtrAtRuntime(mod)) {
             const ptr_info = param_ty.ptrInfo(mod);
             if (math.cast(u5, param_index)) |i| {
                 if (@as(u1, @truncate(fn_info.noalias_bits >> i)) != 0) {
+                    try attributes.addParamAttr(llvm_arg_i, .@"noalias", &o.builder);
                     o.addArgAttr(llvm_fn, llvm_arg_i, "noalias");
                 }
             }
             if (!param_ty.isPtrLikeOptional(mod) and !ptr_info.flags.is_allowzero) {
+                try attributes.addParamAttr(llvm_arg_i, .nonnull, &o.builder);
                 o.addArgAttr(llvm_fn, llvm_arg_i, "nonnull");
             }
             if (ptr_info.flags.is_const) {
+                try attributes.addParamAttr(llvm_arg_i, .readonly, &o.builder);
                 o.addArgAttr(llvm_fn, llvm_arg_i, "readonly");
             }
-            const elem_align = ptr_info.flags.alignment.toByteUnitsOptional() orelse
-                @max(ptr_info.child.toType().abiAlignment(mod), 1);
-            o.addArgAttrInt(llvm_fn, llvm_arg_i, "align", elem_align);
+            const elem_align = Builder.Alignment.fromByteUnits(
+                ptr_info.flags.alignment.toByteUnitsOptional() orelse
+                    @max(ptr_info.child.toType().abiAlignment(mod), 1),
+            );
+            try attributes.addParamAttr(llvm_arg_i, .{ .@"align" = elem_align }, &o.builder);
+            o.addArgAttrInt(llvm_fn, llvm_arg_i, "align", elem_align.toByteUnits() orelse 0);
         } else if (ccAbiPromoteInt(fn_info.cc, mod, param_ty)) |s| switch (s) {
-            .signed => o.addArgAttr(llvm_fn, llvm_arg_i, "signext"),
-            .unsigned => o.addArgAttr(llvm_fn, llvm_arg_i, "zeroext"),
+            .signed => {
+                try attributes.addParamAttr(llvm_arg_i, .signext, &o.builder);
+                o.addArgAttr(llvm_fn, llvm_arg_i, "signext");
+            },
+            .unsigned => {
+                try attributes.addParamAttr(llvm_arg_i, .zeroext, &o.builder);
+                o.addArgAttr(llvm_fn, llvm_arg_i, "zeroext");
+            },
         };
     }
 
-    fn addByRefParamAttrs(
+    fn addByRefParamAttrsOld(
         o: *Object,
+        attributes: *Builder.FunctionAttributes.Wip,
         llvm_fn: *llvm.Value,
         llvm_arg_i: u32,
-        alignment: u32,
+        alignment: Builder.Alignment,
         byval_attr: bool,
         param_llvm_ty: Builder.Type,
-    ) void {
+    ) Allocator.Error!void {
+        try attributes.addParamAttr(llvm_arg_i, .nonnull, &o.builder);
+        try attributes.addParamAttr(llvm_arg_i, .readonly, &o.builder);
+        try attributes.addParamAttr(llvm_arg_i, .{ .@"align" = alignment }, &o.builder);
         o.addArgAttr(llvm_fn, llvm_arg_i, "nonnull");
         o.addArgAttr(llvm_fn, llvm_arg_i, "readonly");
-        o.addArgAttrInt(llvm_fn, llvm_arg_i, "align", alignment);
+        o.addArgAttrInt(llvm_fn, llvm_arg_i, "align", alignment.toByteUnits() orelse 0);
         if (byval_attr) {
+            try attributes.addParamAttr(llvm_arg_i, .{ .byval = param_llvm_ty }, &o.builder);
             llvm_fn.addByValAttr(llvm_arg_i, param_llvm_ty.toLlvm(&o.builder));
         }
     }
@@ -4841,10 +5006,10 @@ pub const FuncGen = struct {
                 .slice_ptr      => try self.airSliceField(inst, 0),
                 .slice_len      => try self.airSliceField(inst, 1),
 
-                .call              => try self.airCall(inst, .Auto),
-                .call_always_tail  => try self.airCall(inst, .AlwaysTail),
-                .call_never_tail   => try self.airCall(inst, .NeverTail),
-                .call_never_inline => try self.airCall(inst, .NeverInline),
+                .call              => try self.airCall(inst, .auto),
+                .call_always_tail  => try self.airCall(inst, .always_tail),
+                .call_never_tail   => try self.airCall(inst, .never_tail),
+                .call_never_inline => try self.airCall(inst, .never_inline),
 
                 .ptr_slice_ptr_ptr => try self.airPtrSliceFieldPtr(inst, 0),
                 .ptr_slice_len_ptr => try self.airPtrSliceFieldPtr(inst, 1),
@@ -4953,7 +5118,15 @@ pub const FuncGen = struct {
         }
     }
 
-    fn airCall(self: *FuncGen, inst: Air.Inst.Index, attr: llvm.CallAttr) !Builder.Value {
+    pub const CallAttr = enum {
+        Auto,
+        NeverTail,
+        NeverInline,
+        AlwaysTail,
+        AlwaysInline,
+    };
+
+    fn airCall(self: *FuncGen, inst: Air.Inst.Index, modifier: std.builtin.CallModifier) !Builder.Value {
         const pl_op = self.air.instructions.items(.data)[inst].pl_op;
         const extra = self.air.extraData(Air.Call, pl_op.payload);
         const args: []const Air.Inst.Ref = @ptrCast(self.air.extra[extra.end..][0..extra.data.args_len]);
@@ -4972,14 +5145,25 @@ pub const FuncGen = struct {
         const target = mod.getTarget();
         const sret = firstParamSRet(fn_info, mod);
 
-        var llvm_args = std.ArrayList(*llvm.Value).init(self.gpa);
+        var llvm_args = std.ArrayList(Builder.Value).init(self.gpa);
         defer llvm_args.deinit();
 
+        var attributes: Builder.FunctionAttributes.Wip = .{};
+        defer attributes.deinit(&o.builder);
+
+        switch (modifier) {
+            .auto, .never_tail, .always_tail => {},
+            .never_inline => try attributes.addFnAttr(.@"noinline", &o.builder),
+            .async_kw, .no_async, .always_inline, .compile_time => unreachable,
+        }
+
         const ret_ptr = if (!sret) null else blk: {
             const llvm_ret_ty = try o.lowerType(return_type);
+            try attributes.addParamAttr(0, .{ .sret = llvm_ret_ty }, &o.builder);
+
             const alignment = Builder.Alignment.fromByteUnits(return_type.abiAlignment(mod));
             const ret_ptr = try self.buildAlloca(llvm_ret_ty, alignment);
-            try llvm_args.append(ret_ptr.toLlvm(&self.wip));
+            try llvm_args.append(ret_ptr);
             break :blk ret_ptr;
         };
 
@@ -4987,7 +5171,7 @@ pub const FuncGen = struct {
             o.module.comp.bin_file.options.error_return_tracing;
         if (err_return_tracing) {
             assert(self.err_ret_trace != .none);
-            try llvm_args.append(self.err_ret_trace.toLlvm(&self.wip));
+            try llvm_args.append(self.err_ret_trace);
         }
 
         var it = iterateParamTypes(o, fn_info);
@@ -5001,9 +5185,9 @@ pub const FuncGen = struct {
                 if (isByRef(param_ty, mod)) {
                     const alignment = Builder.Alignment.fromByteUnits(param_ty.abiAlignment(mod));
                     const loaded = try self.wip.load(.normal, llvm_param_ty, llvm_arg, alignment, "");
-                    try llvm_args.append(loaded.toLlvm(&self.wip));
+                    try llvm_args.append(loaded);
                 } else {
-                    try llvm_args.append(llvm_arg.toLlvm(&self.wip));
+                    try llvm_args.append(llvm_arg);
                 }
             },
             .byref => {
@@ -5011,13 +5195,13 @@ pub const FuncGen = struct {
                 const param_ty = self.typeOf(arg);
                 const llvm_arg = try self.resolveInst(arg);
                 if (isByRef(param_ty, mod)) {
-                    try llvm_args.append(llvm_arg.toLlvm(&self.wip));
+                    try llvm_args.append(llvm_arg);
                 } else {
                     const alignment = Builder.Alignment.fromByteUnits(param_ty.abiAlignment(mod));
                     const param_llvm_ty = llvm_arg.typeOfWip(&self.wip);
                     const arg_ptr = try self.buildAlloca(param_llvm_ty, alignment);
                     _ = try self.wip.store(.normal, llvm_arg, arg_ptr, alignment);
-                    try llvm_args.append(arg_ptr.toLlvm(&self.wip));
+                    try llvm_args.append(arg_ptr);
                 }
             },
             .byref_mut => {
@@ -5034,7 +5218,7 @@ pub const FuncGen = struct {
                 } else {
                     _ = try self.wip.store(.normal, llvm_arg, arg_ptr, alignment);
                 }
-                try llvm_args.append(arg_ptr.toLlvm(&self.wip));
+                try llvm_args.append(arg_ptr);
             },
             .abi_sized_int => {
                 const arg = args[it.zig_index - 1];
@@ -5045,7 +5229,7 @@ pub const FuncGen = struct {
                 if (isByRef(param_ty, mod)) {
                     const alignment = Builder.Alignment.fromByteUnits(param_ty.abiAlignment(mod));
                     const loaded = try self.wip.load(.normal, int_llvm_ty, llvm_arg, alignment, "");
-                    try llvm_args.append(loaded.toLlvm(&self.wip));
+                    try llvm_args.append(loaded);
                 } else {
                     // LLVM does not allow bitcasting structs so we must allocate
                     // a local, store as one type, and then load as another type.
@@ -5056,7 +5240,7 @@ pub const FuncGen = struct {
                     const int_ptr = try self.buildAlloca(int_llvm_ty, alignment);
                     _ = try self.wip.store(.normal, llvm_arg, int_ptr, alignment);
                     const loaded = try self.wip.load(.normal, int_llvm_ty, int_ptr, alignment, "");
-                    try llvm_args.append(loaded.toLlvm(&self.wip));
+                    try llvm_args.append(loaded);
                 }
             },
             .slice => {
@@ -5064,7 +5248,7 @@ pub const FuncGen = struct {
                 const llvm_arg = try self.resolveInst(arg);
                 const ptr = try self.wip.extractValue(llvm_arg, &.{0}, "");
                 const len = try self.wip.extractValue(llvm_arg, &.{1}, "");
-                try llvm_args.appendSlice(&.{ ptr.toLlvm(&self.wip), len.toLlvm(&self.wip) });
+                try llvm_args.appendSlice(&.{ ptr, len });
             },
             .multiple_llvm_types => {
                 const arg = args[it.zig_index - 1];
@@ -5086,14 +5270,14 @@ pub const FuncGen = struct {
                         Builder.Alignment.fromByteUnits(@divExact(target.ptrBitWidth(), 8));
                     const field_ptr = try self.wip.gepStruct(llvm_ty, arg_ptr, i, "");
                     const loaded = try self.wip.load(.normal, field_ty, field_ptr, alignment, "");
-                    llvm_args.appendAssumeCapacity(loaded.toLlvm(&self.wip));
+                    llvm_args.appendAssumeCapacity(loaded);
                 }
             },
             .as_u16 => {
                 const arg = args[it.zig_index - 1];
                 const llvm_arg = try self.resolveInst(arg);
                 const casted = try self.wip.cast(.bitcast, llvm_arg, .i16, "");
-                try llvm_args.append(casted.toLlvm(&self.wip));
+                try llvm_args.append(casted);
             },
             .float_array => |count| {
                 const arg = args[it.zig_index - 1];
@@ -5110,7 +5294,7 @@ pub const FuncGen = struct {
                 const array_ty = try o.builder.arrayType(count, float_ty);
 
                 const loaded = try self.wip.load(.normal, array_ty, llvm_arg, alignment, "");
-                try llvm_args.append(loaded.toLlvm(&self.wip));
+                try llvm_args.append(loaded);
             },
             .i32_array, .i64_array => |arr_len| {
                 const elem_size: u8 = if (lowering == .i32_array) 32 else 64;
@@ -5127,24 +5311,10 @@ pub const FuncGen = struct {
                 const array_ty =
                     try o.builder.arrayType(arr_len, try o.builder.intType(@intCast(elem_size)));
                 const loaded = try self.wip.load(.normal, array_ty, llvm_arg, alignment, "");
-                try llvm_args.append(loaded.toLlvm(&self.wip));
+                try llvm_args.append(loaded);
             },
         };
 
-        const llvm_fn_ty = try o.lowerType(zig_fn_ty);
-        const call = (try self.wip.unimplemented(llvm_fn_ty.functionReturn(&o.builder), "")).finish(
-            self.builder.buildCall(
-                llvm_fn_ty.toLlvm(&o.builder),
-                llvm_fn.toLlvm(&self.wip),
-                llvm_args.items.ptr,
-                @intCast(llvm_args.items.len),
-                toLlvmCallConv(fn_info.cc, target),
-                attr,
-                "",
-            ),
-            &self.wip,
-        );
-
         if (callee_ty.zigTypeTag(mod) == .Pointer) {
             // Add argument attributes for function pointer calls.
             it = iterateParamTypes(o, fn_info);
@@ -5155,19 +5325,17 @@ pub const FuncGen = struct {
                     const param_index = it.zig_index - 1;
                     const param_ty = fn_info.param_types.get(ip)[param_index].toType();
                     if (!isByRef(param_ty, mod)) {
-                        o.addByValParamAttrs(call.toLlvm(&self.wip), param_ty, param_index, fn_info, it.llvm_index - 1);
+                        try o.addByValParamAttrs(&attributes, param_ty, param_index, fn_info, it.llvm_index - 1);
                     }
                 },
                 .byref => {
                     const param_index = it.zig_index - 1;
                     const param_ty = fn_info.param_types.get(ip)[param_index].toType();
                     const param_llvm_ty = try o.lowerType(param_ty);
-                    const alignment = param_ty.abiAlignment(mod);
-                    o.addByRefParamAttrs(call.toLlvm(&self.wip), it.llvm_index - 1, alignment, it.byval_attr, param_llvm_ty);
-                },
-                .byref_mut => {
-                    o.addArgAttr(call.toLlvm(&self.wip), it.llvm_index - 1, "noundef");
+                    const alignment = Builder.Alignment.fromByteUnits(param_ty.abiAlignment(mod));
+                    try o.addByRefParamAttrs(&attributes, it.llvm_index - 1, alignment, it.byval_attr, param_llvm_ty);
                 },
+                .byref_mut => try attributes.addParamAttr(it.llvm_index - 1, .noundef, &o.builder),
                 // No attributes needed for these.
                 .no_bits,
                 .abi_sized_int,
@@ -5186,23 +5354,40 @@ pub const FuncGen = struct {
 
                     if (math.cast(u5, it.zig_index - 1)) |i| {
                         if (@as(u1, @truncate(fn_info.noalias_bits >> i)) != 0) {
-                            o.addArgAttr(call.toLlvm(&self.wip), llvm_arg_i, "noalias");
+                            try attributes.addParamAttr(llvm_arg_i, .@"noalias", &o.builder);
                         }
                     }
                     if (param_ty.zigTypeTag(mod) != .Optional) {
-                        o.addArgAttr(call.toLlvm(&self.wip), llvm_arg_i, "nonnull");
+                        try attributes.addParamAttr(llvm_arg_i, .nonnull, &o.builder);
                     }
                     if (ptr_info.flags.is_const) {
-                        o.addArgAttr(call.toLlvm(&self.wip), llvm_arg_i, "readonly");
+                        try attributes.addParamAttr(llvm_arg_i, .readonly, &o.builder);
                     }
-                    const elem_align = ptr_info.flags.alignment.toByteUnitsOptional() orelse
-                        @max(ptr_info.child.toType().abiAlignment(mod), 1);
-                    o.addArgAttrInt(call.toLlvm(&self.wip), llvm_arg_i, "align", elem_align);
+                    const elem_align = Builder.Alignment.fromByteUnits(
+                        ptr_info.flags.alignment.toByteUnitsOptional() orelse
+                            @max(ptr_info.child.toType().abiAlignment(mod), 1),
+                    );
+                    try attributes.addParamAttr(llvm_arg_i, .{ .@"align" = elem_align }, &o.builder);
                 },
             };
         }
 
-        if (fn_info.return_type == .noreturn_type and attr != .AlwaysTail) {
+        const call = try self.wip.call(
+            switch (modifier) {
+                .auto, .never_inline => .normal,
+                .never_tail => .notail,
+                .always_tail => .musttail,
+                .async_kw, .no_async, .always_inline, .compile_time => unreachable,
+            },
+            toLlvmCallConv(fn_info.cc, target),
+            try attributes.finish(&o.builder),
+            try o.lowerType(zig_fn_ty),
+            llvm_fn,
+            llvm_args.items,
+            "",
+        );
+
+        if (fn_info.return_type == .noreturn_type and modifier != .always_tail) {
             return .none;
         }
 
@@ -5211,9 +5396,7 @@ pub const FuncGen = struct {
         }
 
         const llvm_ret_ty = try o.lowerType(return_type);
-
         if (ret_ptr) |rp| {
-            call.toLlvm(&self.wip).setCallSret(llvm_ret_ty.toLlvm(&o.builder));
             if (isByRef(return_type, mod)) {
                 return rp;
             } else {
@@ -5269,25 +5452,24 @@ pub const FuncGen = struct {
         //   ptr null,                                               ; stack trace
         //   ptr @2,                                                 ; addr (null ?usize)
         // )
-        const args = [4]*llvm.Value{
-            msg_ptr.toLlvm(&o.builder),
-            (try o.builder.intConst(llvm_usize, msg_len)).toLlvm(&o.builder),
-            (try o.builder.nullConst(.ptr)).toLlvm(&o.builder),
-            null_opt_addr_global.toLlvm(&o.builder),
-        };
         const panic_func = mod.funcInfo(mod.panic_func_index);
         const panic_decl = mod.declPtr(panic_func.owner_decl);
         const fn_info = mod.typeToFunc(panic_decl.ty).?;
         const panic_global = try o.resolveLlvmFunction(panic_func.owner_decl);
-        _ = (try fg.wip.unimplemented(.void, "")).finish(fg.builder.buildCall(
-            (try o.lowerType(panic_decl.ty)).toLlvm(&o.builder),
-            panic_global.toLlvm(&o.builder),
-            &args,
-            args.len,
+        _ = try fg.wip.call(
+            .normal,
             toLlvmCallConv(fn_info.cc, target),
-            .Auto,
+            .none,
+            panic_global.typeOf(&o.builder),
+            panic_global.toValue(&o.builder),
+            &.{
+                msg_ptr.toValue(),
+                try o.builder.intValue(llvm_usize, msg_len),
+                try o.builder.nullValue(.ptr),
+                null_opt_addr_global.toValue(),
+            },
             "",
-        ), &fg.wip);
+        );
         _ = try fg.wip.@"unreachable"();
     }
 
@@ -5395,7 +5577,7 @@ pub const FuncGen = struct {
             o.llvm_module.addFunction(llvm_fn_name, llvm_fn_ty.toLlvm(&o.builder));
 
         const args: [2]*llvm.Value = .{ dest_list.toLlvm(&self.wip), src_list.toLlvm(&self.wip) };
-        _ = (try self.wip.unimplemented(.void, "")).finish(self.builder.buildCall(
+        _ = (try self.wip.unimplemented(.void, "")).finish(self.builder.buildCallOld(
             llvm_fn_ty.toLlvm(&o.builder),
             llvm_fn,
             &args,
@@ -5422,7 +5604,7 @@ pub const FuncGen = struct {
             o.llvm_module.addFunction(llvm_fn_name, llvm_fn_ty.toLlvm(&o.builder));
 
         const args: [1]*llvm.Value = .{list.toLlvm(&self.wip)};
-        _ = (try self.wip.unimplemented(.void, "")).finish(self.builder.buildCall(
+        _ = (try self.wip.unimplemented(.void, "")).finish(self.builder.buildCallOld(
             llvm_fn_ty.toLlvm(&o.builder),
             llvm_fn,
             &args,
@@ -5449,7 +5631,7 @@ pub const FuncGen = struct {
             o.llvm_module.addFunction(llvm_fn_name, llvm_fn_ty.toLlvm(&o.builder));
 
         const args: [1]*llvm.Value = .{list.toLlvm(&self.wip)};
-        _ = (try self.wip.unimplemented(.void, "")).finish(self.builder.buildCall(
+        _ = (try self.wip.unimplemented(.void, "")).finish(self.builder.buildCallOld(
             llvm_fn_ty.toLlvm(&o.builder),
             llvm_fn,
             &args,
@@ -5495,16 +5677,15 @@ pub const FuncGen = struct {
         const un_op = self.air.instructions.items(.data)[inst].un_op;
         const operand = try self.resolveInst(un_op);
         const llvm_fn = try self.getCmpLtErrorsLenFunction();
-        const args: [1]*llvm.Value = .{operand.toLlvm(&self.wip)};
-        return (try self.wip.unimplemented(.i1, "")).finish(self.builder.buildCall(
-            llvm_fn.typeOf(&o.builder).toLlvm(&o.builder),
-            llvm_fn.toLlvm(&o.builder),
-            &args,
-            args.len,
-            .Fast,
-            .Auto,
+        return self.wip.call(
+            .normal,
+            .fastcc,
+            .none,
+            llvm_fn.typeOf(&o.builder),
+            llvm_fn.toValue(&o.builder),
+            &.{operand},
             "",
-        ), &self.wip);
+        );
     }
 
     fn cmp(
@@ -5953,16 +6134,15 @@ pub const FuncGen = struct {
         }
 
         const libc_fn = try self.getLibcFunction(fn_name, &.{param_type}, dest_llvm_ty);
-        const params = [1]*llvm.Value{extended.toLlvm(&self.wip)};
-        return (try self.wip.unimplemented(dest_llvm_ty, "")).finish(self.builder.buildCall(
-            libc_fn.typeOf(&o.builder).toLlvm(&o.builder),
-            libc_fn.toLlvm(&o.builder),
-            &params,
-            params.len,
-            .C,
-            .Auto,
+        return self.wip.call(
+            .normal,
+            .ccc,
+            .none,
+            libc_fn.typeOf(&o.builder),
+            libc_fn.toValue(&o.builder),
+            &.{extended},
             "",
-        ), &self.wip);
+        );
     }
 
     fn airIntFromFloat(self: *FuncGen, inst: Air.Inst.Index, want_fast_math: bool) !Builder.Value {
@@ -6013,16 +6193,15 @@ pub const FuncGen = struct {
 
         const operand_llvm_ty = try o.lowerType(operand_ty);
         const libc_fn = try self.getLibcFunction(fn_name, &.{operand_llvm_ty}, libc_ret_ty);
-        const params = [1]*llvm.Value{operand.toLlvm(&self.wip)};
-        var result = (try self.wip.unimplemented(libc_ret_ty, "")).finish(self.builder.buildCall(
-            libc_fn.typeOf(&o.builder).toLlvm(&o.builder),
-            libc_fn.toLlvm(&o.builder),
-            &params,
-            params.len,
-            .C,
-            .Auto,
+        var result = try self.wip.call(
+            .normal,
+            .ccc,
+            .none,
+            libc_fn.typeOf(&o.builder),
+            libc_fn.toValue(&o.builder),
+            &.{operand},
             "",
-        ), &self.wip);
+        );
 
         if (libc_ret_ty != ret_ty) result = try self.wip.cast(.bitcast, result, ret_ty, "");
         if (ret_ty != dest_llvm_ty) result = try self.wip.cast(.trunc, result, dest_llvm_ty, "");
@@ -6843,6 +7022,9 @@ pub const FuncGen = struct {
             }
         }
 
+        var attributes: Builder.FunctionAttributes.Wip = .{};
+        defer attributes.deinit(&o.builder);
+
         const ret_llvm_ty = switch (return_count) {
             0 => .void,
             1 => llvm_ret_types[0],
@@ -6861,7 +7043,7 @@ pub const FuncGen = struct {
             .ATT,
             .False,
         );
-        const call = (try self.wip.unimplemented(ret_llvm_ty, "")).finish(self.builder.buildCall(
+        const call = (try self.wip.unimplemented(ret_llvm_ty, "")).finish(self.builder.buildCallOld(
             llvm_fn_ty.toLlvm(&o.builder),
             asm_fn,
             llvm_param_values.ptr,
@@ -6872,6 +7054,7 @@ pub const FuncGen = struct {
         ), &self.wip);
         for (llvm_param_attrs[0..param_count], 0..) |llvm_elem_ty, i| {
             if (llvm_elem_ty != .none) {
+                try attributes.addParamAttr(i, .{ .elementtype = llvm_elem_ty }, &o.builder);
                 llvm.setCallElemTypeAttr(call.toLlvm(&self.wip), i, llvm_elem_ty.toLlvm(&o.builder));
             }
         }
@@ -7287,7 +7470,7 @@ pub const FuncGen = struct {
         const args: [1]*llvm.Value = .{
             (try o.builder.intConst(.i32, index)).toLlvm(&o.builder),
         };
-        return (try self.wip.unimplemented(.i32, "")).finish(self.builder.buildCall(
+        return (try self.wip.unimplemented(.i32, "")).finish(self.builder.buildCallOld(
             (try o.builder.fnType(.i32, &.{.i32}, .normal)).toLlvm(&o.builder),
             llvm_fn,
             &args,
@@ -7308,7 +7491,7 @@ pub const FuncGen = struct {
             (try o.builder.intConst(.i32, index)).toLlvm(&o.builder),
             operand.toLlvm(&self.wip),
         };
-        return (try self.wip.unimplemented(.i32, "")).finish(self.builder.buildCall(
+        return (try self.wip.unimplemented(.i32, "")).finish(self.builder.buildCallOld(
             (try o.builder.fnType(.i32, &.{ .i32, .i32 }, .normal)).toLlvm(&o.builder),
             llvm_fn,
             &args,
@@ -7425,7 +7608,7 @@ pub const FuncGen = struct {
         });
         const llvm_fn_ty = try o.builder.fnType(llvm_ret_ty, &.{ llvm_inst_ty, llvm_inst_ty }, .normal);
         const llvm_fn = try fg.getIntrinsic(intrinsic_name, &.{llvm_inst_ty});
-        const result_struct = (try fg.wip.unimplemented(llvm_ret_ty, "")).finish(fg.builder.buildCall(
+        const result_struct = (try fg.wip.unimplemented(llvm_ret_ty, "")).finish(fg.builder.buildCallOld(
             llvm_fn_ty.toLlvm(&o.builder),
             llvm_fn,
             &[_]*llvm.Value{ lhs.toLlvm(&fg.wip), rhs.toLlvm(&fg.wip) },
@@ -7768,7 +7951,7 @@ pub const FuncGen = struct {
         );
         const llvm_fn_ty = try o.builder.fnType(llvm_ret_ty, &.{ llvm_lhs_ty, llvm_lhs_ty }, .normal);
         const result_struct = (try self.wip.unimplemented(llvm_ret_ty, "")).finish(
-            self.builder.buildCall(
+            self.builder.buildCallOld(
                 llvm_fn_ty.toLlvm(&o.builder),
                 llvm_fn,
                 &[_]*llvm.Value{ lhs.toLlvm(&self.wip), rhs.toLlvm(&self.wip) },
@@ -7818,29 +8001,23 @@ pub const FuncGen = struct {
         const o = self.dg.object;
         assert(args_vectors.len <= 3);
 
-        const llvm_fn_ty = llvm_fn.typeOf(&o.builder);
-        const llvm_scalar_ty = llvm_fn_ty.functionReturn(&o.builder);
-
         var i: usize = 0;
         var result = result_vector;
         while (i < vector_len) : (i += 1) {
             const index_i32 = try o.builder.intValue(.i32, i);
 
-            var args: [3]*llvm.Value = undefined;
+            var args: [3]Builder.Value = undefined;
             for (args[0..args_vectors.len], args_vectors) |*arg_elem, arg_vector| {
-                arg_elem.* = (try self.wip.extractElement(arg_vector, index_i32, "")).toLlvm(&self.wip);
+                arg_elem.* = try self.wip.extractElement(arg_vector, index_i32, "");
             }
-            const result_elem = (try self.wip.unimplemented(llvm_scalar_ty, "")).finish(
-                self.builder.buildCall(
-                    llvm_fn_ty.toLlvm(&o.builder),
-                    llvm_fn.toLlvm(&o.builder),
-                    &args,
-                    @intCast(args_vectors.len),
-                    .C,
-                    .Auto,
-                    "",
-                ),
-                &self.wip,
+            const result_elem = try self.wip.call(
+                .normal,
+                .ccc,
+                .none,
+                llvm_fn.typeOf(&o.builder),
+                llvm_fn.toValue(&o.builder),
+                args[0..args_vectors.len],
+                "",
             );
             result = try self.wip.insertElement(result, result_elem, index_i32, "");
         }
@@ -7861,7 +8038,7 @@ pub const FuncGen = struct {
         };
 
         const fn_type = try o.builder.fnType(return_type, param_types, .normal);
-        const f = o.llvm_module.addFunction(fn_name.toSlice(&o.builder).?, fn_type.toLlvm(&o.builder));
+        const f = o.llvm_module.addFunction(fn_name.slice(&o.builder).?, fn_type.toLlvm(&o.builder));
 
         var global = Builder.Global{
             .type = fn_type,
@@ -7942,20 +8119,15 @@ pub const FuncGen = struct {
             return self.wip.icmp(int_cond, result, zero_vector, "");
         }
 
-        const llvm_fn_ty = libc_fn.typeOf(&o.builder);
-        const llvm_params = [2]*llvm.Value{ params[0].toLlvm(&self.wip), params[1].toLlvm(&self.wip) };
-        const result = (try self.wip.unimplemented(
-            llvm_fn_ty.functionReturn(&o.builder),
-            "",
-        )).finish(self.builder.buildCall(
-            libc_fn.typeOf(&o.builder).toLlvm(&o.builder),
-            libc_fn.toLlvm(&o.builder),
-            &llvm_params,
-            llvm_params.len,
-            .C,
-            .Auto,
+        const result = try self.wip.call(
+            .normal,
+            .ccc,
+            .none,
+            libc_fn.typeOf(&o.builder),
+            libc_fn.toValue(&o.builder),
+            &params,
             "",
-        ), &self.wip);
+        );
         return self.wip.icmp(int_cond, result, zero.toValue(), "");
     }
 
@@ -8085,7 +8257,7 @@ pub const FuncGen = struct {
         );
         var llvm_params: [params_len]*llvm.Value = undefined;
         for (&llvm_params, params) |*llvm_param, param| llvm_param.* = param.toLlvm(&self.wip);
-        return (try self.wip.unimplemented(llvm_ty, "")).finish(self.builder.buildCall(
+        return (try self.wip.unimplemented(llvm_ty, "")).finish(self.builder.buildCallOld(
             llvm_fn_ty.toLlvm(&o.builder),
             llvm_fn,
             &llvm_params,
@@ -8311,17 +8483,16 @@ pub const FuncGen = struct {
                 compilerRtFloatAbbrev(src_bits), compilerRtFloatAbbrev(dest_bits),
             });
 
-            const llvm_fn = try self.getLibcFunction(fn_name, &.{operand_llvm_ty}, dest_llvm_ty);
-            const params = [1]*llvm.Value{operand.toLlvm(&self.wip)};
-            return (try self.wip.unimplemented(dest_llvm_ty, "")).finish(self.builder.buildCall(
-                llvm_fn.typeOf(&o.builder).toLlvm(&o.builder),
-                llvm_fn.toLlvm(&o.builder),
-                &params,
-                params.len,
-                .C,
-                .Auto,
+            const libc_fn = try self.getLibcFunction(fn_name, &.{operand_llvm_ty}, dest_llvm_ty);
+            return self.wip.call(
+                .normal,
+                .ccc,
+                .none,
+                libc_fn.typeOf(&o.builder),
+                libc_fn.toValue(&o.builder),
+                &.{operand},
                 "",
-            ), &self.wip);
+            );
         }
     }
 
@@ -8346,17 +8517,16 @@ pub const FuncGen = struct {
                 compilerRtFloatAbbrev(src_bits), compilerRtFloatAbbrev(dest_bits),
             });
 
-            const llvm_fn = try self.getLibcFunction(fn_name, &.{operand_llvm_ty}, dest_llvm_ty);
-            const params = [1]*llvm.Value{operand.toLlvm(&self.wip)};
-            return (try self.wip.unimplemented(dest_llvm_ty, "")).finish(self.builder.buildCall(
-                llvm_fn.typeOf(&o.builder).toLlvm(&o.builder),
-                llvm_fn.toLlvm(&o.builder),
-                &params,
-                params.len,
-                .C,
-                .Auto,
+            const libc_fn = try self.getLibcFunction(fn_name, &.{operand_llvm_ty}, dest_llvm_ty);
+            return self.wip.call(
+                .normal,
+                .ccc,
+                .none,
+                libc_fn.typeOf(&o.builder),
+                libc_fn.toValue(&o.builder),
+                &.{operand},
                 "",
-            ), &self.wip);
+            );
         }
     }
 
@@ -8657,7 +8827,7 @@ pub const FuncGen = struct {
         _ = inst;
         const o = self.dg.object;
         const llvm_fn = try self.getIntrinsic("llvm.trap", &.{});
-        _ = (try self.wip.unimplemented(.void, "")).finish(self.builder.buildCall(
+        _ = (try self.wip.unimplemented(.void, "")).finish(self.builder.buildCallOld(
             (try o.builder.fnType(.void, &.{}, .normal)).toLlvm(&o.builder),
             llvm_fn,
             undefined,
@@ -8674,7 +8844,7 @@ pub const FuncGen = struct {
         _ = inst;
         const o = self.dg.object;
         const llvm_fn = try self.getIntrinsic("llvm.debugtrap", &.{});
-        _ = (try self.wip.unimplemented(.void, "")).finish(self.builder.buildCall(
+        _ = (try self.wip.unimplemented(.void, "")).finish(self.builder.buildCallOld(
             (try o.builder.fnType(.void, &.{}, .normal)).toLlvm(&o.builder),
             llvm_fn,
             undefined,
@@ -8701,7 +8871,7 @@ pub const FuncGen = struct {
         const params = [_]*llvm.Value{
             (try o.builder.intConst(.i32, 0)).toLlvm(&o.builder),
         };
-        const ptr_val = (try self.wip.unimplemented(.ptr, "")).finish(self.builder.buildCall(
+        const ptr_val = (try self.wip.unimplemented(.ptr, "")).finish(self.builder.buildCallOld(
             (try o.builder.fnType(.ptr, &.{.i32}, .normal)).toLlvm(&o.builder),
             llvm_fn,
             &params,
@@ -8727,7 +8897,7 @@ pub const FuncGen = struct {
             (try o.builder.intConst(.i32, 0)).toLlvm(&o.builder),
         };
         const ptr_val = (try self.wip.unimplemented(llvm_fn_ty.functionReturn(&o.builder), "")).finish(
-            self.builder.buildCall(
+            self.builder.buildCallOld(
                 llvm_fn_ty.toLlvm(&o.builder),
                 llvm_fn,
                 &params,
@@ -9256,7 +9426,7 @@ pub const FuncGen = struct {
             Builder.Constant.false.toLlvm(&o.builder),
         };
         const wrong_size_result = (try self.wip.unimplemented(llvm_operand_ty, "")).finish(
-            self.builder.buildCall(
+            self.builder.buildCallOld(
                 llvm_fn_ty.toLlvm(&o.builder),
                 fn_val,
                 &params,
@@ -9283,7 +9453,7 @@ pub const FuncGen = struct {
 
         const params = [_]*llvm.Value{operand.toLlvm(&self.wip)};
         const wrong_size_result = (try self.wip.unimplemented(llvm_operand_ty, "")).finish(
-            self.builder.buildCall(
+            self.builder.buildCallOld(
                 llvm_fn_ty.toLlvm(&o.builder),
                 fn_val,
                 &params,
@@ -9331,7 +9501,7 @@ pub const FuncGen = struct {
 
         const params = [_]*llvm.Value{operand.toLlvm(&self.wip)};
         const wrong_size_result = (try self.wip.unimplemented(llvm_operand_ty, "")).finish(
-            self.builder.buildCall(
+            self.builder.buildCallOld(
                 llvm_fn_ty.toLlvm(&o.builder),
                 fn_val,
                 &params,
@@ -9389,16 +9559,15 @@ pub const FuncGen = struct {
         const enum_ty = self.typeOf(un_op);
 
         const llvm_fn = try self.getIsNamedEnumValueFunction(enum_ty);
-        const params = [_]*llvm.Value{operand.toLlvm(&self.wip)};
-        return (try self.wip.unimplemented(.i1, "")).finish(self.builder.buildCall(
-            llvm_fn.typeOf(&o.builder).toLlvm(&o.builder),
-            llvm_fn.toLlvm(&o.builder),
-            &params,
-            params.len,
-            .Fast,
-            .Auto,
+        return self.wip.call(
+            .normal,
+            .fastcc,
+            .none,
+            llvm_fn.typeOf(&o.builder),
+            llvm_fn.toValue(&o.builder),
+            &.{operand},
             "",
-        ), &self.wip);
+        );
     }
 
     fn getIsNamedEnumValueFunction(self: *FuncGen, enum_ty: Type) !Builder.Function.Index {
@@ -9416,13 +9585,16 @@ pub const FuncGen = struct {
             fqn.fmt(&mod.intern_pool),
         });
 
+        var attributes: Builder.FunctionAttributes.Wip = .{};
+        defer attributes.deinit(&o.builder);
+
         const fn_type = try o.builder.fnType(.i1, &.{
             try o.lowerType(enum_type.tag_ty.toType()),
         }, .normal);
-        const fn_val = o.llvm_module.addFunction(llvm_fn_name.toSlice(&o.builder).?, fn_type.toLlvm(&o.builder));
+        const fn_val = o.llvm_module.addFunction(llvm_fn_name.slice(&o.builder).?, fn_type.toLlvm(&o.builder));
         fn_val.setLinkage(.Internal);
         fn_val.setFunctionCallConv(.Fast);
-        o.addCommonFnAttributes(fn_val);
+        try o.addCommonFnAttributes(&attributes, fn_val);
 
         var global = Builder.Global{
             .linkage = .internal,
@@ -9431,6 +9603,8 @@ pub const FuncGen = struct {
         };
         var function = Builder.Function{
             .global = @enumFromInt(o.builder.globals.count()),
+            .call_conv = .fastcc,
+            .attributes = try attributes.finish(&o.builder),
         };
         try o.builder.llvm.globals.append(self.gpa, fn_val);
         _ = try o.builder.addGlobal(llvm_fn_name, global);
@@ -9470,19 +9644,14 @@ pub const FuncGen = struct {
         const enum_ty = self.typeOf(un_op);
 
         const llvm_fn = try self.getEnumTagNameFunction(enum_ty);
-        const llvm_fn_ty = llvm_fn.typeOf(&o.builder);
-        const params = [_]*llvm.Value{operand.toLlvm(&self.wip)};
-        return (try self.wip.unimplemented(llvm_fn_ty.functionReturn(&o.builder), "")).finish(
-            self.builder.buildCall(
-                llvm_fn_ty.toLlvm(&o.builder),
-                llvm_fn.toLlvm(&o.builder),
-                &params,
-                params.len,
-                .Fast,
-                .Auto,
-                "",
-            ),
-            &self.wip,
+        return self.wip.call(
+            .normal,
+            .fastcc,
+            .none,
+            llvm_fn.typeOf(&o.builder),
+            llvm_fn.toValue(&o.builder),
+            &.{operand},
+            "",
         );
     }
 
@@ -9499,16 +9668,19 @@ pub const FuncGen = struct {
         const fqn = try mod.declPtr(enum_type.decl).getFullyQualifiedName(mod);
         const llvm_fn_name = try o.builder.fmt("__zig_tag_name_{}", .{fqn.fmt(&mod.intern_pool)});
 
+        var attributes: Builder.FunctionAttributes.Wip = .{};
+        defer attributes.deinit(&o.builder);
+
         const ret_ty = try o.lowerType(Type.slice_const_u8_sentinel_0);
         const usize_ty = try o.lowerType(Type.usize);
 
         const fn_type = try o.builder.fnType(ret_ty, &.{
             try o.lowerType(enum_type.tag_ty.toType()),
         }, .normal);
-        const fn_val = o.llvm_module.addFunction(llvm_fn_name.toSlice(&o.builder).?, fn_type.toLlvm(&o.builder));
+        const fn_val = o.llvm_module.addFunction(llvm_fn_name.slice(&o.builder).?, fn_type.toLlvm(&o.builder));
         fn_val.setLinkage(.Internal);
         fn_val.setFunctionCallConv(.Fast);
-        o.addCommonFnAttributes(fn_val);
+        try o.addCommonFnAttributes(&attributes, fn_val);
 
         var global = Builder.Global{
             .linkage = .internal,
@@ -9517,6 +9689,8 @@ pub const FuncGen = struct {
         };
         var function = Builder.Function{
             .global = @enumFromInt(o.builder.globals.count()),
+            .call_conv = .fastcc,
+            .attributes = try attributes.finish(&o.builder),
         };
         try o.builder.llvm.globals.append(self.gpa, fn_val);
         gop.value_ptr.* = try o.builder.addGlobal(llvm_fn_name, global);
@@ -9561,7 +9735,7 @@ pub const FuncGen = struct {
 
             const slice_val = try o.builder.structValue(ret_ty, &.{
                 global_index.toConst(),
-                try o.builder.intConst(usize_ty, name.toSlice(&o.builder).?.len),
+                try o.builder.intConst(usize_ty, name.slice(&o.builder).?.len),
             });
 
             const return_block = try wip.block(1, "Name");
@@ -9590,11 +9764,14 @@ pub const FuncGen = struct {
         // Function signature: fn (anyerror) bool
 
         const fn_type = try o.builder.fnType(.i1, &.{Builder.Type.err_int}, .normal);
-        const llvm_fn = o.llvm_module.addFunction(name.toSlice(&o.builder).?, fn_type.toLlvm(&o.builder));
+        const llvm_fn = o.llvm_module.addFunction(name.slice(&o.builder).?, fn_type.toLlvm(&o.builder));
+
+        var attributes: Builder.FunctionAttributes.Wip = .{};
+        defer attributes.deinit(&o.builder);
 
         llvm_fn.setLinkage(.Internal);
         llvm_fn.setFunctionCallConv(.Fast);
-        o.addCommonFnAttributes(llvm_fn);
+        try o.addCommonFnAttributes(&attributes, llvm_fn);
 
         var global = Builder.Global{
             .linkage = .internal,
@@ -9603,6 +9780,8 @@ pub const FuncGen = struct {
         };
         var function = Builder.Function{
             .global = @enumFromInt(o.builder.globals.count()),
+            .call_conv = .fastcc,
+            .attributes = try attributes.finish(&o.builder),
         };
 
         try o.builder.llvm.globals.append(self.gpa, llvm_fn);
@@ -9731,18 +9910,14 @@ pub const FuncGen = struct {
                 // accum = f(accum, vec[i]);
                 const accum = try self.wip.load(.normal, llvm_result_ty, accum_ptr, .default, "");
                 const element = try self.wip.extractElement(operand_vector, i, "");
-                const params = [2]*llvm.Value{ accum.toLlvm(&self.wip), element.toLlvm(&self.wip) };
-                const new_accum = (try self.wip.unimplemented(llvm_result_ty, "")).finish(
-                    self.builder.buildCall(
-                        llvm_fn.typeOf(&o.builder).toLlvm(&o.builder),
-                        llvm_fn.toLlvm(&o.builder),
-                        &params,
-                        params.len,
-                        .C,
-                        .Auto,
-                        "",
-                    ),
-                    &self.wip,
+                const new_accum = try self.wip.call(
+                    .normal,
+                    .ccc,
+                    .none,
+                    llvm_fn.typeOf(&o.builder),
+                    llvm_fn.toValue(&o.builder),
+                    &.{ accum, element },
+                    "",
                 );
                 _ = try self.wip.store(.normal, new_accum, accum_ptr, .default);
 
@@ -10190,7 +10365,7 @@ pub const FuncGen = struct {
             (try o.builder.intConst(.i32, prefetch.locality)).toLlvm(&o.builder),
             (try o.builder.intConst(.i32, @intFromEnum(prefetch.cache))).toLlvm(&o.builder),
         };
-        _ = (try self.wip.unimplemented(.void, "")).finish(self.builder.buildCall(
+        _ = (try self.wip.unimplemented(.void, "")).finish(self.builder.buildCallOld(
             llvm_fn_ty.toLlvm(&o.builder),
             fn_val,
             &params,
@@ -10222,7 +10397,7 @@ pub const FuncGen = struct {
 
         const args: [0]*llvm.Value = .{};
         const llvm_fn = try self.getIntrinsic(llvm_fn_name, &.{});
-        return (try self.wip.unimplemented(.i32, "")).finish(self.builder.buildCall(
+        return (try self.wip.unimplemented(.i32, "")).finish(self.builder.buildCallOld(
             (try o.builder.fnType(.i32, &.{}, .normal)).toLlvm(&o.builder),
             llvm_fn,
             &args,
@@ -10252,12 +10427,15 @@ pub const FuncGen = struct {
         const dimension = pl_op.payload;
         if (dimension >= 3) return o.builder.intValue(.i32, 1);
 
+        var attributes: Builder.FunctionAttributes.Wip = .{};
+        defer attributes.deinit(&o.builder);
+
         // Fetch the dispatch pointer, which points to this structure:
         // https://github.com/RadeonOpenCompute/ROCR-Runtime/blob/adae6c61e10d371f7cbc3d0e94ae2c070cab18a4/src/inc/hsa.h#L2913
         const llvm_fn = try self.getIntrinsic("llvm.amdgcn.dispatch.ptr", &.{});
         const args: [0]*llvm.Value = .{};
         const llvm_ret_ty = try o.builder.ptrType(Builder.AddrSpace.amdgpu.constant);
-        const dispatch_ptr = (try self.wip.unimplemented(llvm_ret_ty, "")).finish(self.builder.buildCall(
+        const dispatch_ptr = (try self.wip.unimplemented(llvm_ret_ty, "")).finish(self.builder.buildCallOld(
             (try o.builder.fnType(llvm_ret_ty, &.{}, .normal)).toLlvm(&o.builder),
             llvm_fn,
             &args,
@@ -10266,6 +10444,9 @@ pub const FuncGen = struct {
             .Auto,
             "",
         ), &self.wip);
+        try attributes.addRetAttr(.{
+            .@"align" = comptime Builder.Alignment.fromByteUnits(4),
+        }, &o.builder);
         o.addAttrInt(dispatch_ptr.toLlvm(&self.wip), 0, "align", 4);
 
         // Load the work_group_* member from the struct as u16.
@@ -10298,7 +10479,7 @@ pub const FuncGen = struct {
         const undef_init = try o.builder.undefConst(.ptr); // TODO: Address space
 
         const name = try o.builder.string("__zig_err_name_table");
-        const error_name_table_global = o.llvm_module.addGlobal(Builder.Type.ptr.toLlvm(&o.builder), name.toSlice(&o.builder).?);
+        const error_name_table_global = o.llvm_module.addGlobal(Builder.Type.ptr.toLlvm(&o.builder), name.slice(&o.builder).?);
         error_name_table_global.setInitializer(undef_init.toLlvm(&o.builder));
         error_name_table_global.setLinkage(.Private);
         error_name_table_global.setGlobalConstant(.True);
@@ -10751,7 +10932,7 @@ pub const FuncGen = struct {
         );
 
         const call = (try fg.wip.unimplemented(llvm_usize, "")).finish(
-            fg.builder.buildCall(fn_llvm_ty, asm_fn, &args, args.len, .C, .Auto, ""),
+            fg.builder.buildCallOld(fn_llvm_ty, asm_fn, &args, args.len, .C, .Auto, ""),
             &fg.wip,
         );
         return call;
@@ -10991,33 +11172,33 @@ fn toLlvmAtomicRmwBinOp(
     };
 }
 
-fn toLlvmCallConv(cc: std.builtin.CallingConvention, target: std.Target) llvm.CallConv {
+fn toLlvmCallConv(cc: std.builtin.CallingConvention, target: std.Target) Builder.CallConv {
     return switch (cc) {
-        .Unspecified, .Inline, .Async => .Fast,
-        .C, .Naked => .C,
-        .Stdcall => .X86_StdCall,
-        .Fastcall => .X86_FastCall,
+        .Unspecified, .Inline, .Async => .fastcc,
+        .C, .Naked => .ccc,
+        .Stdcall => .x86_stdcallcc,
+        .Fastcall => .x86_fastcallcc,
         .Vectorcall => return switch (target.cpu.arch) {
-            .x86, .x86_64 => .X86_VectorCall,
-            .aarch64, .aarch64_be, .aarch64_32 => .AArch64_VectorCall,
+            .x86, .x86_64 => .x86_vectorcallcc,
+            .aarch64, .aarch64_be, .aarch64_32 => .aarch64_vector_pcs,
             else => unreachable,
         },
-        .Thiscall => .X86_ThisCall,
-        .APCS => .ARM_APCS,
-        .AAPCS => .ARM_AAPCS,
-        .AAPCSVFP => .ARM_AAPCS_VFP,
+        .Thiscall => .x86_thiscallcc,
+        .APCS => .arm_apcscc,
+        .AAPCS => .arm_aapcscc,
+        .AAPCSVFP => .arm_aapcs_vfpcc,
         .Interrupt => return switch (target.cpu.arch) {
-            .x86, .x86_64 => .X86_INTR,
-            .avr => .AVR_INTR,
-            .msp430 => .MSP430_INTR,
+            .x86, .x86_64 => .x86_intrcc,
+            .avr => .avr_intrcc,
+            .msp430 => .msp430_intrcc,
             else => unreachable,
         },
-        .Signal => .AVR_SIGNAL,
-        .SysV => .X86_64_SysV,
-        .Win64 => .Win64,
+        .Signal => .avr_signalcc,
+        .SysV => .x86_64_sysvcc,
+        .Win64 => .win64cc,
         .Kernel => return switch (target.cpu.arch) {
-            .nvptx, .nvptx64 => .PTX_Kernel,
-            .amdgcn => .AMDGPU_KERNEL,
+            .nvptx, .nvptx64 => .ptx_kernel,
+            .amdgcn => .amdgpu_kernel,
             else => unreachable,
         },
     };
src/zig_llvm.cpp
@@ -453,6 +453,10 @@ LLVMValueRef ZigLLVMBuildCall(LLVMBuilderRef B, LLVMTypeRef Ty, LLVMValueRef Fn,
     return wrap(call_inst);
 }
 
+ZIG_EXTERN_C void ZigLLVMSetTailCallKind(LLVMValueRef Call, CallInst::TailCallKind TailCallKind) {
+    unwrap<CallInst>(Call)->setTailCallKind(TailCallKind);
+}
+
 void ZigLLVMAddAttributeAtIndex(LLVMValueRef Val, unsigned Idx, LLVMAttributeRef A) {
     if (isa<Function>(unwrap(Val))) {
         unwrap<Function>(Val)->addAttributeAtIndex(Idx, unwrap(A));
@@ -461,7 +465,6 @@ void ZigLLVMAddAttributeAtIndex(LLVMValueRef Val, unsigned Idx, LLVMAttributeRef
     }
 }
 
-
 LLVMValueRef ZigLLVMBuildMemCpy(LLVMBuilderRef B, LLVMValueRef Dst, unsigned DstAlign,
         LLVMValueRef Src, unsigned SrcAlign, LLVMValueRef Size, bool isVolatile)
 {
@@ -1116,11 +1119,6 @@ void ZigLLVMAddFunctionAttr(LLVMValueRef fn_ref, const char *attr_name, const ch
     func->addFnAttr(attr_name, attr_value);
 }
 
-void ZigLLVMAddFunctionAttrCold(LLVMValueRef fn_ref) {
-    Function *func = unwrap<Function>(fn_ref);
-    func->addFnAttr(Attribute::Cold);
-}
-
 void ZigLLVMParseCommandLineOptions(size_t argc, const char *const *argv) {
     cl::ParseCommandLineOptions(argc, argv);
 }