From aa44f8f0fd1a4af9df65a9b9bfc5fe6fa0d31b9f Mon Sep 17 00:00:00 2001 From: Jacob Young Date: Fri, 21 Jul 2023 00:51:50 -0400 Subject: llvm: convert attributes and non-intrinsic calls --- src/codegen/llvm/Builder.zig | 1447 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 1395 insertions(+), 52 deletions(-) (limited to 'src/codegen/llvm/Builder.zig') diff --git a/src/codegen/llvm/Builder.zig b/src/codegen/llvm/Builder.zig index 0bb65b2294..1026b9c88e 100644 --- a/src/codegen/llvm/Builder.zig +++ b/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( -- cgit v1.2.3