gpa: Allocator, use_lib_llvm: bool, 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) = .{}, } else void, source_filename: String, data_layout: String, target_triple: String, string_map: std.AutoArrayHashMapUnmanaged(void, void), string_bytes: std.ArrayListUnmanaged(u8), string_indices: std.ArrayListUnmanaged(u32), types: std.AutoArrayHashMapUnmanaged(String, Type), next_unnamed_type: String, next_unique_type_id: std.AutoHashMapUnmanaged(String, u32), type_map: std.AutoArrayHashMapUnmanaged(void, void), type_items: std.ArrayListUnmanaged(Type.Item), type_extra: std.ArrayListUnmanaged(u32), globals: std.AutoArrayHashMapUnmanaged(String, Global), next_unnamed_global: String, next_replaced_global: String, next_unique_global_id: std.AutoHashMapUnmanaged(String, u32), aliases: std.ArrayListUnmanaged(Alias), variables: std.ArrayListUnmanaged(Variable), functions: std.ArrayListUnmanaged(Function), constant_map: std.AutoArrayHashMapUnmanaged(void, void), constant_items: std.MultiArrayList(Constant.Item), constant_extra: std.ArrayListUnmanaged(u32), constant_limbs: std.ArrayListUnmanaged(std.math.big.Limb), pub const expected_fields_len = 32; pub const expected_gep_indices_len = 8; pub const expected_cases_len = 8; pub const expected_incoming_len = 8; pub const Options = struct { allocator: Allocator, use_lib_llvm: bool = false, strip: bool = true, name: []const u8 = &.{}, target: std.Target = builtin.target, triple: []const u8 = &.{}, }; pub const String = enum(u32) { none = std.math.maxInt(u31), empty, _, pub fn isAnon(self: String) bool { assert(self != .none); return self.toIndex() == null; } pub fn toSlice(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]; return b.string_bytes.items[start .. end - 1 :0]; } const FormatData = struct { string: String, 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 ++ "'"); assert(data.string != .none); const slice = data.string.toSlice(data.builder) orelse return writer.print("{d}", .{@intFromEnum(data.string)}); const full_slice = slice[0 .. slice.len + comptime @intFromBool( std.mem.indexOfScalar(u8, fmt_str, '@') != null, )]; const need_quotes = (comptime std.mem.indexOfScalar(u8, fmt_str, '"') != null) or !isValidIdentifier(full_slice); if (need_quotes) try writer.writeByte('"'); for (full_slice) |character| switch (character) { '\\' => try writer.writeAll("\\\\"), ' '...'"' - 1, '"' + 1...'\\' - 1, '\\' + 1...'~' => try writer.writeByte(character), else => try writer.print("\\{X:0>2}", .{character}), }; if (need_quotes) try writer.writeByte('"'); } pub fn fmt(self: String, builder: *const Builder) std.fmt.Formatter(format) { return .{ .data = .{ .string = self, .builder = builder } }; } fn fromIndex(index: ?usize) String { 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; } const Adapter = struct { builder: *const Builder, pub fn hash(_: Adapter, key: []const u8) 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).?); } }; }; pub const Type = enum(u32) { void, half, bfloat, float, double, fp128, x86_fp80, ppc_fp128, x86_amx, x86_mmx, label, token, metadata, i1, i8, i16, i29, i32, i64, i80, i128, ptr, none = std.math.maxInt(u32), _, pub const err_int = Type.i16; pub const Tag = enum(u4) { simple, function, vararg_function, integer, pointer, target, vector, scalable_vector, small_array, array, structure, packed_structure, named_structure, }; pub const Simple = enum { void, half, bfloat, float, double, fp128, x86_fp80, ppc_fp128, x86_amx, x86_mmx, label, token, metadata, }; pub const Function = struct { ret: Type, params_len: u32, //params: [params_len]Value, pub const Kind = enum { normal, vararg }; }; pub const Target = extern struct { name: String, types_len: u32, ints_len: u32, //types: [types_len]Type, //ints: [ints_len]u32, }; pub const Vector = extern struct { len: u32, child: Type, fn length(self: Vector) u32 { return self.len; } pub const Kind = enum { normal, scalable }; }; pub const Array = extern struct { len_lo: u32, len_hi: u32, child: Type, fn length(self: Array) u64 { return @as(u64, self.len_hi) << 32 | self.len_lo; } }; pub const Structure = struct { fields_len: u32, //fields: [fields_len]Type, pub const Kind = enum { normal, @"packed" }; }; pub const NamedStructure = struct { id: String, body: Type, }; pub const Item = packed struct(u32) { tag: Tag, data: ExtraIndex, pub const ExtraIndex = u28; }; pub fn tag(self: Type, builder: *const Builder) Tag { return builder.type_items.items[@intFromEnum(self)].tag; } pub fn unnamedTag(self: Type, builder: *const Builder) Tag { const item = builder.type_items.items[@intFromEnum(self)]; return switch (item.tag) { .named_structure => builder.typeExtraData(Type.NamedStructure, item.data).body .unnamedTag(builder), else => item.tag, }; } pub fn scalarTag(self: Type, builder: *const Builder) Tag { const item = builder.type_items.items[@intFromEnum(self)]; return switch (item.tag) { .vector, .scalable_vector => builder.typeExtraData(Type.Vector, item.data) .child.tag(builder), else => item.tag, }; } pub fn isFloatingPoint(self: Type) bool { return switch (self) { .half, .bfloat, .float, .double, .fp128, .x86_fp80, .ppc_fp128 => true, else => false, }; } pub fn isInteger(self: Type, builder: *const Builder) bool { return switch (self) { .i1, .i8, .i16, .i29, .i32, .i64, .i80, .i128 => true, else => switch (self.tag(builder)) { .integer => true, else => false, }, }; } pub fn isPointer(self: Type, builder: *const Builder) bool { return switch (self) { .ptr => true, else => switch (self.tag(builder)) { .pointer => true, else => false, }, }; } pub fn isFunction(self: Type, builder: *const Builder) bool { return switch (self.tag(builder)) { .function, .vararg_function => true, else => false, }; } pub fn functionKind(self: Type, builder: *const Builder) Type.Function.Kind { return switch (self.tag(builder)) { .function => .normal, .vararg_function => .vararg, else => unreachable, }; } pub fn functionParameters(self: Type, builder: *const Builder) []const Type { const item = builder.type_items.items[@intFromEnum(self)]; switch (item.tag) { .function, .vararg_function, => { const extra = builder.typeExtraDataTrail(Type.Function, item.data); return @ptrCast(builder.type_extra.items[extra.end..][0..extra.data.params_len]); }, else => unreachable, } } pub fn functionReturn(self: Type, builder: *const Builder) Type { const item = builder.type_items.items[@intFromEnum(self)]; switch (item.tag) { .function, .vararg_function, => return builder.typeExtraData(Type.Function, item.data).ret, else => unreachable, } } pub fn isVector(self: Type, builder: *const Builder) bool { return switch (self.tag(builder)) { .vector, .scalable_vector => true, else => false, }; } pub fn vectorKind(self: Type, builder: *const Builder) Type.Vector.Kind { return switch (self.tag(builder)) { .vector => .normal, .scalable_vector => .scalable, else => unreachable, }; } pub fn isStruct(self: Type, builder: *const Builder) bool { return switch (self.tag(builder)) { .structure, .packed_structure, .named_structure => true, else => false, }; } pub fn structKind(self: Type, builder: *const Builder) Type.Structure.Kind { return switch (self.unnamedTag(builder)) { .structure => .normal, .packed_structure => .@"packed", else => unreachable, }; } pub fn isAggregate(self: Type, builder: *const Builder) bool { return switch (self.tag(builder)) { .small_array, .array, .structure, .packed_structure, .named_structure => true, else => false, }; } pub fn scalarBits(self: Type, builder: *const Builder) u24 { return switch (self) { .void, .label, .token, .metadata, .none, .x86_amx => unreachable, .i1 => 1, .i8 => 8, .half, .bfloat, .i16 => 16, .i29 => 29, .float, .i32 => 32, .double, .i64, .x86_mmx => 64, .x86_fp80, .i80 => 80, .fp128, .ppc_fp128, .i128 => 128, .ptr => @panic("TODO: query data layout"), _ => { const item = builder.type_items.items[@intFromEnum(self)]; return switch (item.tag) { .simple, .function, .vararg_function, => unreachable, .integer => @intCast(item.data), .pointer => @panic("TODO: query data layout"), .target => unreachable, .vector, .scalable_vector, => builder.typeExtraData(Type.Vector, item.data).child.scalarBits(builder), .small_array, .array, .structure, .packed_structure, .named_structure, => unreachable, }; }, }; } pub fn childType(self: Type, builder: *const Builder) Type { const item = builder.type_items.items[@intFromEnum(self)]; return switch (item.tag) { .vector, .scalable_vector, .small_array, => builder.typeExtraData(Type.Vector, item.data).child, .array => builder.typeExtraData(Type.Array, item.data).child, .named_structure => builder.typeExtraData(Type.NamedStructure, item.data).body, else => unreachable, }; } pub fn scalarType(self: Type, builder: *const Builder) Type { if (self.isFloatingPoint()) return self; const item = builder.type_items.items[@intFromEnum(self)]; return switch (item.tag) { .integer, .pointer, => self, .vector, .scalable_vector, => builder.typeExtraData(Type.Vector, item.data).child, else => unreachable, }; } pub fn changeScalar(self: Type, scalar: Type, builder: *Builder) Allocator.Error!Type { try builder.ensureUnusedTypeCapacity(1, Type.Vector, 0); return self.changeScalarAssumeCapacity(scalar, builder); } pub fn changeScalarAssumeCapacity(self: Type, scalar: Type, builder: *Builder) Type { if (self.isFloatingPoint()) return scalar; const item = builder.type_items.items[@intFromEnum(self)]; return switch (item.tag) { .integer, .pointer, => scalar, inline .vector, .scalable_vector, => |kind| builder.vectorTypeAssumeCapacity( switch (kind) { .vector => .normal, .scalable_vector => .scalable, else => unreachable, }, builder.typeExtraData(Type.Vector, item.data).len, scalar, ), else => unreachable, }; } pub fn vectorLen(self: Type, builder: *const Builder) u32 { const item = builder.type_items.items[@intFromEnum(self)]; return switch (item.tag) { .vector, .scalable_vector, => builder.typeExtraData(Type.Vector, item.data).len, else => unreachable, }; } pub fn changeLength(self: Type, len: u32, builder: *Builder) Allocator.Error!Type { try builder.ensureUnusedTypeCapacity(1, Type.Array, 0); return self.changeLengthAssumeCapacity(len, builder); } pub fn changeLengthAssumeCapacity(self: Type, len: u32, builder: *Builder) Type { const item = builder.type_items.items[@intFromEnum(self)]; return switch (item.tag) { inline .vector, .scalable_vector, => |kind| builder.vectorTypeAssumeCapacity( switch (kind) { .vector => .normal, .scalable_vector => .scalable, else => unreachable, }, len, builder.typeExtraData(Type.Vector, item.data).child, ), .small_array => builder.arrayTypeAssumeCapacity( len, builder.typeExtraData(Type.Vector, item.data).child, ), .array => builder.arrayTypeAssumeCapacity( len, builder.typeExtraData(Type.Array, item.data).child, ), else => unreachable, }; } pub fn aggregateLen(self: Type, builder: *const Builder) u64 { const item = builder.type_items.items[@intFromEnum(self)]; return switch (item.tag) { .vector, .scalable_vector, .small_array, => builder.typeExtraData(Type.Vector, item.data).len, .array => builder.typeExtraData(Type.Array, item.data).length(), .structure, .packed_structure, => builder.typeExtraData(Type.Structure, item.data).fields_len, .named_structure => builder.typeExtraData(Type.NamedStructure, item.data).body .aggregateLen(builder), else => unreachable, }; } pub fn structFields(self: Type, builder: *const Builder) []const Type { const item = builder.type_items.items[@intFromEnum(self)]; switch (item.tag) { .structure, .packed_structure, => { const extra = builder.typeExtraDataTrail(Type.Structure, item.data); return @ptrCast(builder.type_extra.items[extra.end..][0..extra.data.fields_len]); }, .named_structure => return builder.typeExtraData(Type.NamedStructure, item.data).body .structFields(builder), else => unreachable, } } pub fn childTypeAt(self: Type, indices: []const u32, builder: *const Builder) Type { if (indices.len == 0) return self; const item = builder.type_items.items[@intFromEnum(self)]; return switch (item.tag) { .small_array => builder.typeExtraData(Type.Vector, item.data).child .childTypeAt(indices[1..], builder), .array => builder.typeExtraData(Type.Array, item.data).child .childTypeAt(indices[1..], builder), .structure, .packed_structure, => { const extra = builder.typeExtraDataTrail(Type.Structure, item.data); const fields: []const Type = @ptrCast(builder.type_extra.items[extra.end..][0..extra.data.fields_len]); return fields[indices[0]].childTypeAt(indices[1..], builder); }, .named_structure => builder.typeExtraData(Type.NamedStructure, item.data).body .childTypeAt(indices, builder), else => unreachable, }; } pub fn targetLayoutType(self: Type, builder: *const Builder) Type { _ = self; _ = builder; @panic("TODO: implement targetLayoutType"); } pub fn isSized(self: Type, builder: *const Builder) Allocator.Error!bool { var visited: IsSizedVisited = .{}; return self.isSizedVisited(&visited, builder); } const FormatData = struct { type: Type, builder: *const Builder, }; fn format( data: FormatData, comptime fmt_str: []const u8, fmt_opts: std.fmt.FormatOptions, writer: anytype, ) @TypeOf(writer).Error!void { assert(data.type != .none); if (comptime std.mem.eql(u8, fmt_str, "m")) { const item = data.builder.type_items.items[@intFromEnum(data.type)]; switch (item.tag) { .simple => try writer.writeAll(switch (@as(Simple, @enumFromInt(item.data))) { .void => "isVoid", .half => "f16", .bfloat => "bf16", .float => "f32", .double => "f64", .fp128 => "f128", .x86_fp80 => "f80", .ppc_fp128 => "ppcf128", .x86_amx => "x86amx", .x86_mmx => "x86mmx", .label, .token => unreachable, .metadata => "Metadata", }), .function, .vararg_function => |kind| { const extra = data.builder.typeExtraDataTrail(Type.Function, item.data); const params: []const Type = @ptrCast(data.builder.type_extra.items[extra.end..][0..extra.data.params_len]); try writer.print("f_{m}", .{extra.data.ret.fmt(data.builder)}); for (params) |param| try writer.print("{m}", .{param.fmt(data.builder)}); switch (kind) { .function => {}, .vararg_function => try writer.writeAll("vararg"), else => unreachable, } try writer.writeByte('f'); }, .integer => try writer.print("i{d}", .{item.data}), .pointer => try writer.print("p{d}", .{item.data}), .target => { const extra = data.builder.typeExtraDataTrail(Type.Target, item.data); const types: []const Type = @ptrCast(data.builder.type_extra.items[extra.end..][0..extra.data.types_len]); const ints: []const u32 = @ptrCast(data.builder.type_extra.items[extra.end + extra.data.types_len ..][0..extra.data.ints_len]); try writer.print("t{s}", .{extra.data.name.toSlice(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'); }, .vector, .scalable_vector => |kind| { const extra = data.builder.typeExtraData(Type.Vector, item.data); try writer.print("{s}v{d}{m}", .{ switch (kind) { .vector => "", .scalable_vector => "nx", else => unreachable, }, extra.len, extra.child.fmt(data.builder), }); }, inline .small_array, .array => |kind| { const extra = data.builder.typeExtraData(switch (kind) { .small_array => Type.Vector, .array => Type.Array, else => unreachable, }, item.data); try writer.print("a{d}{m}", .{ extra.length(), extra.child.fmt(data.builder) }); }, .structure, .packed_structure => { const extra = data.builder.typeExtraDataTrail(Type.Structure, item.data); const fields: []const Type = @ptrCast(data.builder.type_extra.items[extra.end..][0..extra.data.fields_len]); try writer.writeAll("sl_"); for (fields) |field| try writer.print("{m}", .{field.fmt(data.builder)}); try writer.writeByte('s'); }, .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); }, } return; } if (std.enums.tagName(Type, data.type)) |name| return writer.writeAll(name); const item = data.builder.type_items.items[@intFromEnum(data.type)]; switch (item.tag) { .simple => unreachable, .function, .vararg_function => |kind| { const extra = data.builder.typeExtraDataTrail(Type.Function, item.data); const params: []const Type = @ptrCast(data.builder.type_extra.items[extra.end..][0..extra.data.params_len]); if (!comptime std.mem.eql(u8, fmt_str, ">")) try writer.print("{%} ", .{extra.data.ret.fmt(data.builder)}); if (!comptime std.mem.eql(u8, fmt_str, "<")) { try writer.writeByte('('); for (params, 0..) |param, index| { if (index > 0) try writer.writeAll(", "); try writer.print("{%}", .{param.fmt(data.builder)}); } switch (kind) { .function => {}, .vararg_function => { if (params.len > 0) try writer.writeAll(", "); try writer.writeAll("..."); }, else => unreachable, } try writer.writeByte(')'); } }, .integer => try writer.print("i{d}", .{item.data}), .pointer => try writer.print("ptr{}", .{@as(AddrSpace, @enumFromInt(item.data))}), .target => { const extra = data.builder.typeExtraDataTrail(Type.Target, item.data); const types: []const Type = @ptrCast(data.builder.type_extra.items[extra.end..][0..extra.data.types_len]); const ints: []const u32 = @ptrCast(data.builder.type_extra.items[extra.end + extra.data.types_len ..][0..extra.data.ints_len]); try writer.print( \\target({"} , .{extra.data.name.fmt(data.builder)}); for (types) |ty| try writer.print(", {%}", .{ty.fmt(data.builder)}); for (ints) |int| try writer.print(", {d}", .{int}); try writer.writeByte(')'); }, .vector, .scalable_vector => |kind| { const extra = data.builder.typeExtraData(Type.Vector, item.data); try writer.print("<{s}{d} x {%}>", .{ switch (kind) { .vector => "", .scalable_vector => "vscale x ", else => unreachable, }, extra.len, extra.child.fmt(data.builder), }); }, inline .small_array, .array => |kind| { const extra = data.builder.typeExtraData(switch (kind) { .small_array => Type.Vector, .array => Type.Array, else => unreachable, }, item.data); try writer.print("[{d} x {%}]", .{ extra.length(), extra.child.fmt(data.builder) }); }, .structure, .packed_structure => |kind| { const extra = data.builder.typeExtraDataTrail(Type.Structure, item.data); const fields: []const Type = @ptrCast(data.builder.type_extra.items[extra.end..][0..extra.data.fields_len]); switch (kind) { .structure => {}, .packed_structure => try writer.writeByte('<'), else => unreachable, } try writer.writeAll("{ "); for (fields, 0..) |field, index| { if (index > 0) try writer.writeAll(", "); try writer.print("{%}", .{field.fmt(data.builder)}); } try writer.writeAll(" }"); switch (kind) { .structure => {}, .packed_structure => try writer.writeByte('>'), else => unreachable, } }, .named_structure => { const extra = data.builder.typeExtraData(Type.NamedStructure, item.data); if (comptime std.mem.eql(u8, fmt_str, "%")) try writer.print("%{}", .{ extra.id.fmt(data.builder), }) else switch (extra.body) { .none => try writer.writeAll("opaque"), else => try format(.{ .type = extra.body, .builder = data.builder, }, fmt_str, fmt_opts, writer), } }, } } pub fn fmt(self: Type, builder: *const Builder) std.fmt.Formatter(format) { return .{ .data = .{ .type = self, .builder = builder } }; } pub fn toLlvm(self: Type, builder: *const Builder) *llvm.Type { assert(builder.useLibLlvm()); return builder.llvm.types.items[@intFromEnum(self)]; } const IsSizedVisited = std.AutoHashMapUnmanaged(Type, void); fn isSizedVisited( self: Type, visited: *IsSizedVisited, builder: *const Builder, ) Allocator.Error!bool { return switch (self) { .void, .label, .token, .metadata, => false, .half, .bfloat, .float, .double, .fp128, .x86_fp80, .ppc_fp128, .x86_amx, .x86_mmx, .i1, .i8, .i16, .i29, .i32, .i64, .i80, .i128, .ptr, => true, .none => unreachable, _ => { const item = builder.type_items.items[@intFromEnum(self)]; return switch (item.tag) { .simple => unreachable, .function, .vararg_function, => false, .integer, .pointer, => true, .target => self.targetLayoutType(builder).isSizedVisited(visited, builder), .vector, .scalable_vector, .small_array, => builder.typeExtraData(Type.Vector, item.data) .child.isSizedVisited(visited, builder), .array => builder.typeExtraData(Type.Array, item.data) .child.isSizedVisited(visited, builder), .structure, .packed_structure, => { if (try visited.fetchPut(builder.gpa, self, {})) |_| return false; const extra = builder.typeExtraDataTrail(Type.Structure, item.data); const fields: []const Type = @ptrCast( builder.type_extra.items[extra.end..][0..extra.data.fields_len], ); for (fields) |field| { if (field.isVector(builder) and field.vectorKind(builder) == .scalable) return false; if (!try field.isSizedVisited(visited, builder)) return false; } return true; }, .named_structure => { const body = builder.typeExtraData(Type.NamedStructure, item.data).body; return body != .none and try body.isSizedVisited(visited, builder); }, }; }, }; } }; pub const Linkage = enum { external, private, internal, available_externally, linkonce, weak, common, appending, extern_weak, linkonce_odr, weak_odr, pub fn format( self: Linkage, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype, ) @TypeOf(writer).Error!void { if (self != .external) try writer.print(" {s}", .{@tagName(self)}); } }; pub const Preemption = enum { dso_preemptable, dso_local, implicit_dso_local, pub fn format( self: Preemption, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype, ) @TypeOf(writer).Error!void { if (self == .dso_local) try writer.print(" {s}", .{@tagName(self)}); } }; pub const Visibility = enum { default, hidden, protected, pub fn format( self: Visibility, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype, ) @TypeOf(writer).Error!void { if (self != .default) try writer.print(" {s}", .{@tagName(self)}); } }; pub const DllStorageClass = enum { default, dllimport, dllexport, pub fn format( self: DllStorageClass, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype, ) @TypeOf(writer).Error!void { if (self != .default) try writer.print(" {s}", .{@tagName(self)}); } }; pub const ThreadLocal = enum { default, generaldynamic, localdynamic, initialexec, localexec, pub fn format( self: ThreadLocal, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype, ) @TypeOf(writer).Error!void { if (self == .default) return; try writer.writeAll(" thread_local"); if (self != .generaldynamic) { try writer.writeByte('('); try writer.writeAll(@tagName(self)); try writer.writeByte(')'); } } }; pub const UnnamedAddr = enum { default, unnamed_addr, local_unnamed_addr, pub fn format( self: UnnamedAddr, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype, ) @TypeOf(writer).Error!void { if (self != .default) try writer.print(" {s}", .{@tagName(self)}); } }; pub const AddrSpace = enum(u24) { default, _, // See llvm/lib/Target/X86/X86.h pub const x86 = struct { pub const gs: AddrSpace = @enumFromInt(256); pub const fs: AddrSpace = @enumFromInt(257); pub const ss: AddrSpace = @enumFromInt(258); pub const ptr32_sptr: AddrSpace = @enumFromInt(270); pub const ptr32_uptr: AddrSpace = @enumFromInt(271); pub const ptr64: AddrSpace = @enumFromInt(272); }; pub const x86_64 = x86; // See llvm/lib/Target/AVR/AVR.h pub const avr = struct { pub const flash: AddrSpace = @enumFromInt(1); pub const flash1: AddrSpace = @enumFromInt(2); pub const flash2: AddrSpace = @enumFromInt(3); pub const flash3: AddrSpace = @enumFromInt(4); pub const flash4: AddrSpace = @enumFromInt(5); pub const flash5: AddrSpace = @enumFromInt(6); }; // See llvm/lib/Target/NVPTX/NVPTX.h pub const nvptx = struct { pub const generic: AddrSpace = @enumFromInt(0); pub const global: AddrSpace = @enumFromInt(1); pub const constant: AddrSpace = @enumFromInt(2); pub const shared: AddrSpace = @enumFromInt(3); pub const param: AddrSpace = @enumFromInt(4); pub const local: AddrSpace = @enumFromInt(5); }; // See llvm/lib/Target/AMDGPU/AMDGPU.h pub const amdgpu = struct { pub const flat: AddrSpace = @enumFromInt(0); pub const global: AddrSpace = @enumFromInt(1); pub const region: AddrSpace = @enumFromInt(2); pub const local: AddrSpace = @enumFromInt(3); pub const constant: AddrSpace = @enumFromInt(4); pub const private: AddrSpace = @enumFromInt(5); pub const constant_32bit: AddrSpace = @enumFromInt(6); pub const buffer_fat_pointer: AddrSpace = @enumFromInt(7); pub const param_d: AddrSpace = @enumFromInt(6); pub const param_i: AddrSpace = @enumFromInt(7); pub const constant_buffer_0: AddrSpace = @enumFromInt(8); pub const constant_buffer_1: AddrSpace = @enumFromInt(9); pub const constant_buffer_2: AddrSpace = @enumFromInt(10); pub const constant_buffer_3: AddrSpace = @enumFromInt(11); pub const constant_buffer_4: AddrSpace = @enumFromInt(12); pub const constant_buffer_5: AddrSpace = @enumFromInt(13); pub const constant_buffer_6: AddrSpace = @enumFromInt(14); pub const constant_buffer_7: AddrSpace = @enumFromInt(15); pub const constant_buffer_8: AddrSpace = @enumFromInt(16); pub const constant_buffer_9: AddrSpace = @enumFromInt(17); pub const constant_buffer_10: AddrSpace = @enumFromInt(18); pub const constant_buffer_11: AddrSpace = @enumFromInt(19); pub const constant_buffer_12: AddrSpace = @enumFromInt(20); pub const constant_buffer_13: AddrSpace = @enumFromInt(21); pub const constant_buffer_14: AddrSpace = @enumFromInt(22); pub const constant_buffer_15: AddrSpace = @enumFromInt(23); }; // See llvm/lib/Target/WebAssembly/Utils/WebAssemblyTypeUtilities.h pub const wasm = struct { pub const variable: AddrSpace = @enumFromInt(1); pub const externref: AddrSpace = @enumFromInt(10); pub const funcref: AddrSpace = @enumFromInt(20); }; pub fn format( self: AddrSpace, comptime prefix: []const u8, _: std.fmt.FormatOptions, writer: anytype, ) @TypeOf(writer).Error!void { if (self != .default) try writer.print("{s} addrspace({d})", .{ prefix, @intFromEnum(self) }); } }; pub const ExternallyInitialized = enum { default, externally_initialized, pub fn format( self: ExternallyInitialized, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype, ) @TypeOf(writer).Error!void { if (self == .default) return; try writer.writeByte(' '); try writer.writeAll(@tagName(self)); } }; pub const Alignment = enum(u6) { default = std.math.maxInt(u6), _, pub fn fromByteUnits(bytes: u64) Alignment { if (bytes == 0) return .default; assert(std.math.isPowerOfTwo(bytes)); assert(bytes <= 1 << 32); return @enumFromInt(@ctz(bytes)); } pub fn toByteUnits(self: Alignment) ?u64 { return if (self == .default) null else @as(u64, 1) << @intFromEnum(self); } pub fn format( self: Alignment, comptime prefix: []const u8, _: std.fmt.FormatOptions, writer: anytype, ) @TypeOf(writer).Error!void { try writer.print("{s} align {d}", .{ prefix, self.toByteUnits() orelse return }); } }; pub const Global = struct { linkage: Linkage = .external, preemption: Preemption = .dso_preemptable, visibility: Visibility = .default, dll_storage_class: DllStorageClass = .default, unnamed_addr: UnnamedAddr = .default, addr_space: AddrSpace = .default, externally_initialized: ExternallyInitialized = .default, type: Type, partition: String = .none, kind: union(enum) { alias: Alias.Index, variable: Variable.Index, function: Function.Index, replaced: Global.Index, }, pub const Index = enum(u32) { none = std.math.maxInt(u32), _, pub fn unwrap(self: Index, builder: *const Builder) Index { var cur = self; while (true) { const replacement = cur.getReplacement(builder); if (replacement == .none) return cur; cur = replacement; } } pub fn eql(self: Index, other: Index, builder: *const Builder) bool { return self.unwrap(builder) == other.unwrap(builder); } pub fn name(self: Index, builder: *const Builder) String { return builder.globals.keys()[@intFromEnum(self.unwrap(builder))]; } pub fn ptr(self: Index, builder: *Builder) *Global { return &builder.globals.values()[@intFromEnum(self.unwrap(builder))]; } pub fn ptrConst(self: Index, builder: *const Builder) *const Global { return &builder.globals.values()[@intFromEnum(self.unwrap(builder))]; } pub fn typeOf(self: Index, builder: *const Builder) Type { return self.ptrConst(builder).type; } pub fn toConst(self: Index) Constant { return @enumFromInt(@intFromEnum(Constant.first_global) + @intFromEnum(self)); } pub fn toLlvm(self: Index, builder: *const Builder) *llvm.Value { assert(builder.useLibLlvm()); return builder.llvm.globals.items[@intFromEnum(self.unwrap(builder))]; } const FormatData = struct { global: Index, builder: *const Builder, }; fn format( data: FormatData, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype, ) @TypeOf(writer).Error!void { try writer.print("@{}", .{ data.global.unwrap(data.builder).name(data.builder).fmt(data.builder), }); } pub fn fmt(self: Index, builder: *const Builder) std.fmt.Formatter(format) { return .{ .data = .{ .global = self, .builder = builder } }; } pub fn rename(self: Index, new_name: String, builder: *Builder) Allocator.Error!void { try builder.ensureUnusedGlobalCapacity(new_name); self.renameAssumeCapacity(new_name, builder); } pub fn takeName(self: Index, other: Index, builder: *Builder) Allocator.Error!void { try builder.ensureUnusedGlobalCapacity(.empty); self.takeNameAssumeCapacity(other, builder); } pub fn replace(self: Index, other: Index, builder: *Builder) Allocator.Error!void { try builder.ensureUnusedGlobalCapacity(.empty); self.replaceAssumeCapacity(other, builder); } fn renameAssumeCapacity(self: Index, new_name: String, builder: *Builder) void { const old_name = self.name(builder); if (new_name == old_name) return; const index = @intFromEnum(self.unwrap(builder)); if (builder.useLibLlvm()) builder.llvm.globals.appendAssumeCapacity(builder.llvm.globals.items[index]); _ = builder.addGlobalAssumeCapacity(new_name, builder.globals.values()[index]); if (builder.useLibLlvm()) _ = builder.llvm.globals.pop(); builder.globals.swapRemoveAt(index); self.updateName(builder); if (!old_name.isAnon()) return; builder.next_unnamed_global = @enumFromInt(@intFromEnum(builder.next_unnamed_global) - 1); if (builder.next_unnamed_global == old_name) return; builder.getGlobal(builder.next_unnamed_global).?.renameAssumeCapacity(old_name, builder); } fn takeNameAssumeCapacity(self: Index, other: Index, builder: *Builder) void { const other_name = other.name(builder); other.renameAssumeCapacity(.empty, builder); self.renameAssumeCapacity(other_name, builder); } 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 ""; builder.llvm.globals.items[index].setValueName2(name_slice.ptr, name_slice.len); } fn replaceAssumeCapacity(self: Index, other: Index, builder: *Builder) void { if (self.eql(other, builder)) return; builder.next_replaced_global = @enumFromInt(@intFromEnum(builder.next_replaced_global) - 1); self.renameAssumeCapacity(builder.next_replaced_global, builder); if (builder.useLibLlvm()) { const self_llvm = self.toLlvm(builder); self_llvm.replaceAllUsesWith(other.toLlvm(builder)); switch (self.ptr(builder).kind) { .alias, .variable, => self_llvm.deleteGlobal(), .function => self_llvm.deleteFunction(), .replaced => unreachable, } } self.ptr(builder).kind = .{ .replaced = other.unwrap(builder) }; } fn getReplacement(self: Index, builder: *const Builder) Index { return switch (builder.globals.values()[@intFromEnum(self)].kind) { .replaced => |replacement| replacement, else => .none, }; } }; pub fn updateAttributes(self: *Global) void { switch (self.linkage) { .private, .internal => { self.visibility = .default; self.dll_storage_class = .default; self.preemption = .implicit_dso_local; }, .extern_weak => if (self.preemption == .implicit_dso_local) { self.preemption = .dso_local; }, else => switch (self.visibility) { .default => if (self.preemption == .implicit_dso_local) { self.preemption = .dso_local; }, else => self.preemption = .implicit_dso_local, }, } } }; pub const Alias = struct { global: Global.Index, thread_local: ThreadLocal = .default, init: Constant = .no_init, pub const Index = enum(u32) { none = std.math.maxInt(u32), _, pub fn getAliasee(self: Index, builder: *const Builder) Global.Index { const aliasee = self.ptrConst(builder).init.getBase(builder); assert(aliasee != .none); return aliasee; } pub fn ptr(self: Index, builder: *Builder) *Alias { return &builder.aliases.items[@intFromEnum(self)]; } pub fn ptrConst(self: Index, builder: *const Builder) *const Alias { return &builder.aliases.items[@intFromEnum(self)]; } pub fn typeOf(self: Index, builder: *const Builder) Type { return self.ptrConst(builder).global.typeOf(builder); } pub fn toConst(self: Index, builder: *const Builder) Constant { return self.ptrConst(builder).global.toConst(); } pub fn toValue(self: Index, builder: *const Builder) Value { return self.toConst(builder).toValue(); } pub fn toLlvm(self: Index, builder: *const Builder) *llvm.Value { return self.ptrConst(builder).global.toLlvm(builder); } }; }; pub const Variable = struct { global: Global.Index, thread_local: ThreadLocal = .default, mutability: enum { global, constant } = .global, init: Constant = .no_init, section: String = .none, alignment: Alignment = .default, pub const Index = enum(u32) { none = std.math.maxInt(u32), _, pub fn ptr(self: Index, builder: *Builder) *Variable { return &builder.variables.items[@intFromEnum(self)]; } pub fn ptrConst(self: Index, builder: *const Builder) *const Variable { return &builder.variables.items[@intFromEnum(self)]; } pub fn typeOf(self: Index, builder: *const Builder) Type { return self.ptrConst(builder).global.typeOf(builder); } pub fn toConst(self: Index, builder: *const Builder) Constant { return self.ptrConst(builder).global.toConst(); } pub fn toValue(self: Index, builder: *const Builder) Value { return self.toConst(builder).toValue(); } pub fn toLlvm(self: Index, builder: *const Builder) *llvm.Value { return self.ptrConst(builder).global.toLlvm(builder); } }; }; pub const Function = struct { global: Global.Index, section: String = .none, alignment: Alignment = .default, blocks: []const Block = &.{}, instructions: std.MultiArrayList(Instruction) = .{}, names: [*]const String = &[0]String{}, metadata: ?[*]const Metadata = null, extra: []const u32 = &.{}, pub const Index = enum(u32) { none = std.math.maxInt(u32), _, pub fn ptr(self: Index, builder: *Builder) *Function { return &builder.functions.items[@intFromEnum(self)]; } pub fn ptrConst(self: Index, builder: *const Builder) *const Function { return &builder.functions.items[@intFromEnum(self)]; } pub fn typeOf(self: Index, builder: *const Builder) Type { return self.ptrConst(builder).global.typeOf(builder); } pub fn toConst(self: Index, builder: *const Builder) Constant { return self.ptrConst(builder).global.toConst(); } pub fn toValue(self: Index, builder: *const Builder) Value { return self.toConst(builder).toValue(); } pub fn toLlvm(self: Index, builder: *const Builder) *llvm.Value { return self.ptrConst(builder).global.toLlvm(builder); } }; pub const Block = struct { instruction: Instruction.Index, pub const Index = WipFunction.Block.Index; }; pub const Instruction = struct { tag: Tag, data: u32, pub const Tag = enum(u8) { add, @"add nsw", @"add nuw", @"add nuw nsw", addrspacecast, alloca, @"alloca inalloca", @"and", arg, ashr, @"ashr exact", bitcast, block, br, br_cond, extractelement, extractvalue, fadd, @"fadd fast", @"fcmp false", @"fcmp fast false", @"fcmp fast oeq", @"fcmp fast oge", @"fcmp fast ogt", @"fcmp fast ole", @"fcmp fast olt", @"fcmp fast one", @"fcmp fast ord", @"fcmp fast true", @"fcmp fast ueq", @"fcmp fast uge", @"fcmp fast ugt", @"fcmp fast ule", @"fcmp fast ult", @"fcmp fast une", @"fcmp fast uno", @"fcmp oeq", @"fcmp oge", @"fcmp ogt", @"fcmp ole", @"fcmp olt", @"fcmp one", @"fcmp ord", @"fcmp true", @"fcmp ueq", @"fcmp uge", @"fcmp ugt", @"fcmp ule", @"fcmp ult", @"fcmp une", @"fcmp uno", fdiv, @"fdiv fast", fence, fmul, @"fmul fast", fneg, @"fneg fast", fpext, fptosi, fptoui, fptrunc, frem, @"frem fast", fsub, @"fsub fast", getelementptr, @"getelementptr inbounds", @"icmp eq", @"icmp ne", @"icmp sge", @"icmp sgt", @"icmp sle", @"icmp slt", @"icmp uge", @"icmp ugt", @"icmp ule", @"icmp ult", insertelement, insertvalue, inttoptr, @"llvm.maxnum.", @"llvm.minnum.", @"llvm.sadd.sat.", @"llvm.smax.", @"llvm.smin.", @"llvm.smul.fix.sat.", @"llvm.sshl.sat.", @"llvm.ssub.sat.", @"llvm.uadd.sat.", @"llvm.umax.", @"llvm.umin.", @"llvm.umul.fix.sat.", @"llvm.ushl.sat.", @"llvm.usub.sat.", load, @"load atomic", @"load atomic volatile", @"load volatile", lshr, @"lshr exact", mul, @"mul nsw", @"mul nuw", @"mul nuw nsw", @"or", phi, @"phi fast", ptrtoint, ret, @"ret void", sdiv, @"sdiv exact", select, @"select fast", sext, shl, @"shl nsw", @"shl nuw", @"shl nuw nsw", shufflevector, sitofp, srem, store, @"store atomic", @"store atomic volatile", @"store volatile", sub, @"sub nsw", @"sub nuw", @"sub nuw nsw", @"switch", trunc, udiv, @"udiv exact", urem, uitofp, unimplemented, @"unreachable", va_arg, xor, zext, }; pub const Index = enum(u32) { none = std.math.maxInt(u31), _, pub fn name(self: Instruction.Index, function: *const Function) String { return function.names[@intFromEnum(self)]; } pub fn toValue(self: Instruction.Index) Value { return @enumFromInt(@intFromEnum(self)); } pub fn isTerminatorWip(self: Instruction.Index, wip: *const WipFunction) bool { return switch (wip.instructions.items(.tag)[@intFromEnum(self)]) { .br, .br_cond, .ret, .@"ret void", .@"unreachable", => true, else => false, }; } pub fn hasResultWip(self: Instruction.Index, wip: *const WipFunction) bool { return switch (wip.instructions.items(.tag)[@intFromEnum(self)]) { .br, .br_cond, .fence, .ret, .@"ret void", .store, .@"store atomic", .@"store atomic volatile", .@"store volatile", .@"unreachable", => false, else => true, }; } pub fn typeOfWip(self: Instruction.Index, wip: *const WipFunction) Type { const instruction = wip.instructions.get(@intFromEnum(self)); return switch (instruction.tag) { .add, .@"add nsw", .@"add nuw", .@"add nuw nsw", .@"and", .ashr, .@"ashr exact", .fadd, .@"fadd fast", .fdiv, .@"fdiv fast", .fmul, .@"fmul fast", .frem, .@"frem fast", .fsub, .@"fsub fast", .@"llvm.maxnum.", .@"llvm.minnum.", .@"llvm.sadd.sat.", .@"llvm.smax.", .@"llvm.smin.", .@"llvm.smul.fix.sat.", .@"llvm.sshl.sat.", .@"llvm.ssub.sat.", .@"llvm.uadd.sat.", .@"llvm.umax.", .@"llvm.umin.", .@"llvm.umul.fix.sat.", .@"llvm.ushl.sat.", .@"llvm.usub.sat.", .lshr, .@"lshr exact", .mul, .@"mul nsw", .@"mul nuw", .@"mul nuw nsw", .@"or", .sdiv, .@"sdiv exact", .shl, .@"shl nsw", .@"shl nuw", .@"shl nuw nsw", .srem, .sub, .@"sub nsw", .@"sub nuw", .@"sub nuw nsw", .udiv, .@"udiv exact", .urem, .xor, => wip.extraData(Binary, instruction.data).lhs.typeOfWip(wip), .addrspacecast, .bitcast, .fpext, .fptosi, .fptoui, .fptrunc, .inttoptr, .ptrtoint, .sext, .sitofp, .trunc, .uitofp, .zext, => wip.extraData(Cast, instruction.data).type, .alloca, .@"alloca inalloca", => wip.builder.ptrTypeAssumeCapacity( wip.extraData(Alloca, instruction.data).info.addr_space, ), .arg => wip.function.typeOf(wip.builder) .functionParameters(wip.builder)[instruction.data], .block => .label, .br, .br_cond, .fence, .ret, .@"ret void", .store, .@"store atomic", .@"store atomic volatile", .@"store volatile", .@"switch", .@"unreachable", => .none, .extractelement => wip.extraData(ExtractElement, instruction.data) .val.typeOfWip(wip).childType(wip.builder), .extractvalue => { const extra = wip.extraDataTrail(ExtractValue, instruction.data); const indices: []const u32 = wip.extra.items[extra.end..][0..extra.data.indices_len]; return extra.data.val.typeOfWip(wip).childTypeAt(indices, wip.builder); }, .@"fcmp false", .@"fcmp fast false", .@"fcmp fast oeq", .@"fcmp fast oge", .@"fcmp fast ogt", .@"fcmp fast ole", .@"fcmp fast olt", .@"fcmp fast one", .@"fcmp fast ord", .@"fcmp fast true", .@"fcmp fast ueq", .@"fcmp fast uge", .@"fcmp fast ugt", .@"fcmp fast ule", .@"fcmp fast ult", .@"fcmp fast une", .@"fcmp fast uno", .@"fcmp oeq", .@"fcmp oge", .@"fcmp ogt", .@"fcmp ole", .@"fcmp olt", .@"fcmp one", .@"fcmp ord", .@"fcmp true", .@"fcmp ueq", .@"fcmp uge", .@"fcmp ugt", .@"fcmp ule", .@"fcmp ult", .@"fcmp une", .@"fcmp uno", .@"icmp eq", .@"icmp ne", .@"icmp sge", .@"icmp sgt", .@"icmp sle", .@"icmp slt", .@"icmp uge", .@"icmp ugt", .@"icmp ule", .@"icmp ult", => wip.extraData(Binary, instruction.data).lhs.typeOfWip(wip) .changeScalarAssumeCapacity(.i1, wip.builder), .fneg, .@"fneg fast", => @as(Value, @enumFromInt(instruction.data)).typeOfWip(wip), .getelementptr, .@"getelementptr inbounds", => { const extra = wip.extraDataTrail(GetElementPtr, instruction.data); const indices: []const Value = @ptrCast(wip.extra.items[extra.end..][0..extra.data.indices_len]); const base_ty = extra.data.base.typeOfWip(wip); if (!base_ty.isVector(wip.builder)) for (indices) |index| { const index_ty = index.typeOfWip(wip); if (!index_ty.isVector(wip.builder)) continue; return index_ty.changeScalarAssumeCapacity(base_ty, wip.builder); }; return base_ty; }, .insertelement => wip.extraData(InsertElement, instruction.data).val.typeOfWip(wip), .insertvalue => wip.extraData(InsertValue, instruction.data).val.typeOfWip(wip), .load, .@"load atomic", .@"load atomic volatile", .@"load volatile", => wip.extraData(Load, instruction.data).type, .phi, .@"phi fast", => wip.extraData(WipPhi, instruction.data).type, .select, .@"select fast", => wip.extraData(Select, instruction.data).lhs.typeOfWip(wip), .shufflevector => { const extra = wip.extraData(ShuffleVector, instruction.data); return extra.lhs.typeOfWip(wip).changeLengthAssumeCapacity( extra.mask.typeOfWip(wip).vectorLen(wip.builder), wip.builder, ); }, .unimplemented => @enumFromInt(instruction.data), .va_arg => wip.extraData(VaArg, instruction.data).type, }; } pub fn typeOf( self: Instruction.Index, function_index: Function.Index, builder: *Builder, ) Type { const function = function_index.ptrConst(builder); const instruction = function.instructions.get(@intFromEnum(self)); return switch (instruction.tag) { .add, .@"add nsw", .@"add nuw", .@"add nuw nsw", .@"and", .ashr, .@"ashr exact", .fadd, .@"fadd fast", .fdiv, .@"fdiv fast", .fmul, .@"fmul fast", .frem, .@"frem fast", .fsub, .@"fsub fast", .@"llvm.maxnum.", .@"llvm.minnum.", .@"llvm.sadd.sat.", .@"llvm.smax.", .@"llvm.smin.", .@"llvm.smul.fix.sat.", .@"llvm.sshl.sat.", .@"llvm.ssub.sat.", .@"llvm.uadd.sat.", .@"llvm.umax.", .@"llvm.umin.", .@"llvm.umul.fix.sat.", .@"llvm.ushl.sat.", .@"llvm.usub.sat.", .lshr, .@"lshr exact", .mul, .@"mul nsw", .@"mul nuw", .@"mul nuw nsw", .@"or", .sdiv, .@"sdiv exact", .shl, .@"shl nsw", .@"shl nuw", .@"shl nuw nsw", .srem, .sub, .@"sub nsw", .@"sub nuw", .@"sub nuw nsw", .udiv, .@"udiv exact", .urem, .xor, => function.extraData(Binary, instruction.data).lhs.typeOf(function_index, builder), .addrspacecast, .bitcast, .fpext, .fptosi, .fptoui, .fptrunc, .inttoptr, .ptrtoint, .sext, .sitofp, .trunc, .uitofp, .zext, => function.extraData(Cast, instruction.data).type, .alloca, .@"alloca inalloca", => builder.ptrTypeAssumeCapacity( function.extraData(Alloca, instruction.data).info.addr_space, ), .arg => function.global.typeOf(builder) .functionParameters(builder)[instruction.data], .block => .label, .br, .br_cond, .fence, .ret, .@"ret void", .store, .@"store atomic", .@"store atomic volatile", .@"store volatile", .@"switch", .@"unreachable", => .none, .extractelement => function.extraData(ExtractElement, instruction.data) .val.typeOf(function_index, builder).childType(builder), .extractvalue => { const extra = function.extraDataTrail(ExtractValue, instruction.data); const indices: []const u32 = function.extra[extra.end..][0..extra.data.indices_len]; return extra.data.val.typeOf(function_index, builder) .childTypeAt(indices, builder); }, .@"fcmp false", .@"fcmp fast false", .@"fcmp fast oeq", .@"fcmp fast oge", .@"fcmp fast ogt", .@"fcmp fast ole", .@"fcmp fast olt", .@"fcmp fast one", .@"fcmp fast ord", .@"fcmp fast true", .@"fcmp fast ueq", .@"fcmp fast uge", .@"fcmp fast ugt", .@"fcmp fast ule", .@"fcmp fast ult", .@"fcmp fast une", .@"fcmp fast uno", .@"fcmp oeq", .@"fcmp oge", .@"fcmp ogt", .@"fcmp ole", .@"fcmp olt", .@"fcmp one", .@"fcmp ord", .@"fcmp true", .@"fcmp ueq", .@"fcmp uge", .@"fcmp ugt", .@"fcmp ule", .@"fcmp ult", .@"fcmp une", .@"fcmp uno", .@"icmp eq", .@"icmp ne", .@"icmp sge", .@"icmp sgt", .@"icmp sle", .@"icmp slt", .@"icmp uge", .@"icmp ugt", .@"icmp ule", .@"icmp ult", => function.extraData(Binary, instruction.data).lhs.typeOf(function_index, builder) .changeScalarAssumeCapacity(.i1, builder), .fneg, .@"fneg fast", => @as(Value, @enumFromInt(instruction.data)).typeOf(function_index, builder), .getelementptr, .@"getelementptr inbounds", => { const extra = function.extraDataTrail(GetElementPtr, instruction.data); const indices: []const Value = @ptrCast(function.extra[extra.end..][0..extra.data.indices_len]); const base_ty = extra.data.base.typeOf(function_index, builder); if (!base_ty.isVector(builder)) for (indices) |index| { const index_ty = index.typeOf(function_index, builder); if (!index_ty.isVector(builder)) continue; return index_ty.changeScalarAssumeCapacity(base_ty, builder); }; return base_ty; }, .insertelement => function.extraData(InsertElement, instruction.data) .val.typeOf(function_index, builder), .insertvalue => function.extraData(InsertValue, instruction.data) .val.typeOf(function_index, builder), .load, .@"load atomic", .@"load atomic volatile", .@"load volatile", => function.extraData(Load, instruction.data).type, .phi, .@"phi fast", => { const extra = function.extraDataTrail(Phi, instruction.data); const incoming_vals: []const Value = @ptrCast(function.extra[extra.end..][0..extra.data.incoming_len]); return incoming_vals[0].typeOf(function_index, builder); }, .select, .@"select fast", => function.extraData(Select, instruction.data).lhs.typeOf(function_index, builder), .shufflevector => { const extra = function.extraData(ShuffleVector, instruction.data); return extra.lhs.typeOf(function_index, builder).changeLengthAssumeCapacity( extra.mask.typeOf(function_index, builder).vectorLen(builder), builder, ); }, .unimplemented => @enumFromInt(instruction.data), .va_arg => function.extraData(VaArg, instruction.data).type, }; } const FormatData = struct { instruction: Instruction.Index, function: Function.Index, builder: *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 ++ "'"); if (comptime std.mem.indexOfScalar(u8, fmt_str, ',') != null) { if (data.instruction == .none) return; try writer.writeByte(','); } if (comptime std.mem.indexOfScalar(u8, fmt_str, ' ') != null) { if (data.instruction == .none) return; try writer.writeByte(' '); } if (comptime std.mem.indexOfScalar(u8, fmt_str, '%') != null) try writer.print( "{%} ", .{data.instruction.typeOf(data.function, data.builder).fmt(data.builder)}, ); assert(data.instruction != .none); try writer.print("%{}", .{ data.instruction.name(data.function.ptrConst(data.builder)).fmt(data.builder), }); } pub fn fmt( self: Instruction.Index, function: Function.Index, builder: *Builder, ) std.fmt.Formatter(format) { return .{ .data = .{ .instruction = self, .function = function, .builder = builder } }; } pub fn toLlvm(self: Instruction.Index, wip: *const WipFunction) *llvm.Value { assert(wip.builder.useLibLlvm()); return wip.llvm.instructions.items[@intFromEnum(self)]; } fn llvmName(self: Instruction.Index, wip: *const WipFunction) [*:0]const u8 { return if (wip.builder.strip) "" else wip.names.items[@intFromEnum(self)].toSlice(wip.builder).?; } }; pub const ExtraIndex = u32; pub const BrCond = struct { cond: Value, then: Block.Index, @"else": Block.Index, }; pub const Switch = struct { val: Value, default: Block.Index, cases_len: u32, //case_vals: [cases_len]Constant, //case_blocks: [cases_len]Block.Index, }; pub const Binary = struct { lhs: Value, rhs: Value, }; pub const ExtractElement = struct { val: Value, index: Value, }; pub const InsertElement = struct { val: Value, elem: Value, index: Value, }; pub const ShuffleVector = struct { lhs: Value, rhs: Value, mask: Value, }; pub const ExtractValue = struct { val: Value, indices_len: u32, //indices: [indices_len]u32, }; pub const InsertValue = struct { val: Value, elem: Value, indices_len: u32, //indices: [indices_len]u32, }; pub const Alloca = struct { type: Type, len: Value, info: Info, pub const Kind = enum { normal, inalloca }; pub const Info = packed struct(u32) { alignment: Alignment, addr_space: AddrSpace, _: u2 = undefined, }; }; pub const Load = struct { type: Type, ptr: Value, info: MemoryAccessInfo, }; pub const Store = struct { val: Value, ptr: Value, info: MemoryAccessInfo, }; pub const GetElementPtr = struct { type: Type, base: Value, indices_len: u32, //indices: [indices_len]Value, pub const Kind = Constant.GetElementPtr.Kind; }; pub const Cast = struct { val: Value, type: Type, pub const Signedness = Constant.Cast.Signedness; }; pub const WipPhi = struct { type: Type, //incoming_vals: [block.incoming]Value, //incoming_blocks: [block.incoming]Block.Index, }; pub const Phi = struct { incoming_len: u32, //incoming_vals: [incoming_len]Value, //incoming_blocks: [incoming_len]Block.Index, }; pub const Select = struct { cond: Value, lhs: Value, rhs: Value, }; pub const VaArg = struct { list: Value, type: Type, }; }; pub fn deinit(self: *Function, gpa: Allocator) void { gpa.free(self.extra); if (self.metadata) |metadata| gpa.free(metadata[0..self.instructions.len]); gpa.free(self.names[0..self.instructions.len]); self.instructions.deinit(gpa); self.* = undefined; } pub fn arg(self: *const Function, index: u32) Value { const argument = self.instructions.get(index); assert(argument.tag == .arg); assert(argument.data == index); const argument_index: Instruction.Index = @enumFromInt(index); return argument_index.toValue(); } fn extraDataTrail( self: *const Function, comptime T: type, index: Instruction.ExtraIndex, ) struct { data: T, end: Instruction.ExtraIndex } { var result: T = undefined; const fields = @typeInfo(T).Struct.fields; 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), else => @compileError("bad field type: " ++ @typeName(field.type)), }; return .{ .data = result, .end = index + @as(Type.Item.ExtraIndex, @intCast(fields.len)) }; } fn extraData(self: *const Function, comptime T: type, index: Instruction.ExtraIndex) T { return self.extraDataTrail(T, index).data; } }; pub const WipFunction = struct { builder: *Builder, function: Function.Index, llvm: if (build_options.have_llvm) struct { builder: *llvm.Builder, blocks: std.ArrayListUnmanaged(*llvm.BasicBlock), instructions: std.ArrayListUnmanaged(*llvm.Value), } else void, cursor: Cursor, blocks: std.ArrayListUnmanaged(Block), instructions: std.MultiArrayList(Instruction), names: std.ArrayListUnmanaged(String), metadata: std.ArrayListUnmanaged(Metadata), extra: std.ArrayListUnmanaged(u32), pub const Cursor = struct { block: Block.Index, instruction: u32 = 0 }; pub const Block = struct { name: String, incoming: u32, branches: u32 = 0, instructions: std.ArrayListUnmanaged(Instruction.Index), const Index = enum(u32) { entry, _, pub fn ptr(self: Index, wip: *WipFunction) *Block { return &wip.blocks.items[@intFromEnum(self)]; } pub fn ptrConst(self: Index, wip: *const WipFunction) *const Block { return &wip.blocks.items[@intFromEnum(self)]; } pub fn toInst(self: Index, function: *const Function) Instruction.Index { return function.blocks[@intFromEnum(self)].instruction; } pub fn toLlvm(self: Index, wip: *const WipFunction) *llvm.BasicBlock { assert(wip.builder.useLibLlvm()); return wip.llvm.blocks.items[@intFromEnum(self)]; } }; }; pub const Instruction = Function.Instruction; pub fn init(builder: *Builder, function: Function.Index) Allocator.Error!WipFunction { if (builder.useLibLlvm()) { const llvm_function = function.toLlvm(builder); while (llvm_function.getFirstBasicBlock()) |bb| bb.deleteBasicBlock(); } var self = WipFunction{ .builder = builder, .function = function, .llvm = if (builder.useLibLlvm()) .{ .builder = builder.llvm.context.createBuilder(), .blocks = .{}, .instructions = .{}, } else undefined, .cursor = undefined, .blocks = .{}, .instructions = .{}, .names = .{}, .metadata = .{}, .extra = .{}, }; errdefer self.deinit(); const params_len = function.typeOf(self.builder).functionParameters(self.builder).len; try self.ensureUnusedExtraCapacity(params_len, NoExtra, 0); try self.instructions.ensureUnusedCapacity(self.builder.gpa, params_len); if (!self.builder.strip) try self.names.ensureUnusedCapacity(self.builder.gpa, params_len); if (self.builder.useLibLlvm()) try self.llvm.instructions.ensureUnusedCapacity(self.builder.gpa, params_len); for (0..params_len) |param_index| { self.instructions.appendAssumeCapacity(.{ .tag = .arg, .data = @intCast(param_index) }); if (!self.builder.strip) self.names.appendAssumeCapacity(.empty); // TODO: param names if (self.builder.useLibLlvm()) self.llvm.instructions.appendAssumeCapacity( function.toLlvm(self.builder).getParam(@intCast(param_index)), ); } return self; } pub fn arg(self: *const WipFunction, index: u32) Value { const argument = self.instructions.get(index); assert(argument.tag == .arg); assert(argument.data == index); const argument_index: Instruction.Index = @enumFromInt(index); return argument_index.toValue(); } pub fn block(self: *WipFunction, incoming: u32, name: []const u8) Allocator.Error!Block.Index { try self.blocks.ensureUnusedCapacity(self.builder.gpa, 1); if (self.builder.useLibLlvm()) try self.llvm.blocks.ensureUnusedCapacity(self.builder.gpa, 1); const index: Block.Index = @enumFromInt(self.blocks.items.len); const final_name = if (self.builder.strip) .empty else try self.builder.string(name); self.blocks.appendAssumeCapacity(.{ .name = final_name, .incoming = incoming, .instructions = .{}, }); if (self.builder.useLibLlvm()) self.llvm.blocks.appendAssumeCapacity( self.builder.llvm.context.appendBasicBlock( self.function.toLlvm(self.builder), final_name.toSlice(self.builder).?, ), ); return index; } pub fn ret(self: *WipFunction, val: Value) Allocator.Error!Instruction.Index { assert(val.typeOfWip(self) == self.function.typeOf(self.builder).functionReturn(self.builder)); try self.ensureUnusedExtraCapacity(1, NoExtra, 0); const instruction = try self.addInst(null, .{ .tag = .ret, .data = @intFromEnum(val) }); if (self.builder.useLibLlvm()) self.llvm.instructions.appendAssumeCapacity( self.llvm.builder.buildRet(val.toLlvm(self)), ); return instruction; } pub fn retVoid(self: *WipFunction) Allocator.Error!Instruction.Index { try self.ensureUnusedExtraCapacity(1, NoExtra, 0); const instruction = try self.addInst(null, .{ .tag = .@"ret void", .data = undefined }); if (self.builder.useLibLlvm()) self.llvm.instructions.appendAssumeCapacity( self.llvm.builder.buildRetVoid(), ); return instruction; } pub fn br(self: *WipFunction, dest: Block.Index) Allocator.Error!Instruction.Index { try self.ensureUnusedExtraCapacity(1, NoExtra, 0); const instruction = try self.addInst(null, .{ .tag = .br, .data = @intFromEnum(dest) }); dest.ptr(self).branches += 1; if (self.builder.useLibLlvm()) self.llvm.instructions.appendAssumeCapacity( self.llvm.builder.buildBr(dest.toLlvm(self)), ); return instruction; } pub fn brCond( self: *WipFunction, cond: Value, then: Block.Index, @"else": Block.Index, ) Allocator.Error!Instruction.Index { assert(cond.typeOfWip(self) == .i1); try self.ensureUnusedExtraCapacity(1, Instruction.BrCond, 0); const instruction = try self.addInst(null, .{ .tag = .br_cond, .data = self.addExtraAssumeCapacity(Instruction.BrCond{ .cond = cond, .then = then, .@"else" = @"else", }), }); then.ptr(self).branches += 1; @"else".ptr(self).branches += 1; if (self.builder.useLibLlvm()) self.llvm.instructions.appendAssumeCapacity( self.llvm.builder.buildCondBr(cond.toLlvm(self), then.toLlvm(self), @"else".toLlvm(self)), ); return instruction; } pub const WipSwitch = struct { index: u32, instruction: Instruction.Index, pub fn addCase( self: *WipSwitch, val: Constant, dest: Block.Index, wip: *WipFunction, ) Allocator.Error!void { const instruction = wip.instructions.get(@intFromEnum(self.instruction)); const extra = wip.extraDataTrail(Instruction.Switch, instruction.data); const case_vals: []Constant = @ptrCast(wip.extra.items[extra.end..][0..extra.data.cases_len]); const case_dests: []Block.Index = @ptrCast(wip.extra.items[extra.end + extra.data.cases_len ..][0..extra.data.cases_len]); assert(val.typeOf(wip.builder) == extra.data.val.typeOfWip(wip)); case_vals[self.index] = val; case_dests[self.index] = dest; self.index += 1; dest.ptr(wip).branches += 1; if (wip.builder.useLibLlvm()) self.instruction.toLlvm(wip).addCase(val.toLlvm(wip.builder), dest.toLlvm(wip)); } pub fn finish(self: WipSwitch, wip: *WipFunction) void { const instruction = wip.instructions.get(@intFromEnum(self.instruction)); const extra = wip.extraData(Instruction.Switch, instruction.data); assert(self.index == extra.cases_len); } }; pub fn @"switch"( self: *WipFunction, val: Value, default: Block.Index, cases_len: u32, ) Allocator.Error!WipSwitch { try self.ensureUnusedExtraCapacity(1, Instruction.Switch, cases_len * 2); const instruction = try self.addInst(null, .{ .tag = .@"switch", .data = self.addExtraAssumeCapacity(Instruction.Switch{ .val = val, .default = default, .cases_len = cases_len, }), }); _ = self.extra.addManyAsSliceAssumeCapacity(cases_len * 2); default.ptr(self).branches += 1; if (self.builder.useLibLlvm()) self.llvm.instructions.appendAssumeCapacity( self.llvm.builder.buildSwitch(val.toLlvm(self), default.toLlvm(self), @intCast(cases_len)), ); return .{ .index = 0, .instruction = instruction }; } pub fn @"unreachable"(self: *WipFunction) Allocator.Error!Instruction.Index { try self.ensureUnusedExtraCapacity(1, NoExtra, 0); const instruction = try self.addInst(null, .{ .tag = .@"unreachable", .data = undefined }); if (self.builder.useLibLlvm()) self.llvm.instructions.appendAssumeCapacity( self.llvm.builder.buildUnreachable(), ); return instruction; } pub fn un( self: *WipFunction, tag: Instruction.Tag, val: Value, name: []const u8, ) Allocator.Error!Value { switch (tag) { .fneg, .@"fneg fast", => assert(val.typeOfWip(self).scalarType(self.builder).isFloatingPoint()), else => unreachable, } try self.ensureUnusedExtraCapacity(1, NoExtra, 0); const instruction = try self.addInst(name, .{ .tag = tag, .data = @intFromEnum(val) }); if (self.builder.useLibLlvm()) { switch (tag) { .fneg => self.llvm.builder.setFastMath(false), .@"fneg fast" => self.llvm.builder.setFastMath(true), else => unreachable, } self.llvm.instructions.appendAssumeCapacity(switch (tag) { .fneg, .@"fneg fast" => &llvm.Builder.buildFNeg, else => unreachable, }(self.llvm.builder, val.toLlvm(self), instruction.llvmName(self))); } return instruction.toValue(); } pub fn not(self: *WipFunction, val: Value, name: []const u8) Allocator.Error!Value { const ty = val.typeOfWip(self); const all_ones = try self.builder.splatValue( ty, try self.builder.intConst(ty.scalarType(self.builder), -1), ); return self.bin(.xor, val, all_ones, name); } pub fn neg(self: *WipFunction, val: Value, name: []const u8) Allocator.Error!Value { return self.bin(.sub, try self.builder.zeroInitValue(val.typeOfWip(self)), val, name); } pub fn bin( self: *WipFunction, tag: Instruction.Tag, lhs: Value, rhs: Value, name: []const u8, ) Allocator.Error!Value { switch (tag) { .add, .@"add nsw", .@"add nuw", .@"and", .ashr, .@"ashr exact", .fadd, .@"fadd fast", .fdiv, .@"fdiv fast", .fmul, .@"fmul fast", .frem, .@"frem fast", .fsub, .@"fsub fast", .@"llvm.maxnum.", .@"llvm.minnum.", .@"llvm.sadd.sat.", .@"llvm.smax.", .@"llvm.smin.", .@"llvm.smul.fix.sat.", .@"llvm.sshl.sat.", .@"llvm.ssub.sat.", .@"llvm.uadd.sat.", .@"llvm.umax.", .@"llvm.umin.", .@"llvm.umul.fix.sat.", .@"llvm.ushl.sat.", .@"llvm.usub.sat.", .lshr, .@"lshr exact", .mul, .@"mul nsw", .@"mul nuw", .@"or", .sdiv, .@"sdiv exact", .shl, .@"shl nsw", .@"shl nuw", .srem, .sub, .@"sub nsw", .@"sub nuw", .udiv, .@"udiv exact", .urem, .xor, => assert(lhs.typeOfWip(self) == rhs.typeOfWip(self)), else => unreachable, } try self.ensureUnusedExtraCapacity(1, Instruction.Binary, 0); const instruction = try self.addInst(name, .{ .tag = tag, .data = self.addExtraAssumeCapacity(Instruction.Binary{ .lhs = lhs, .rhs = rhs }), }); if (self.builder.useLibLlvm()) { switch (tag) { .fadd, .fdiv, .fmul, .frem, .fsub, => self.llvm.builder.setFastMath(false), .@"fadd fast", .@"fdiv fast", .@"fmul fast", .@"frem fast", .@"fsub fast", => self.llvm.builder.setFastMath(true), else => {}, } self.llvm.instructions.appendAssumeCapacity(switch (tag) { .add => &llvm.Builder.buildAdd, .@"add nsw" => &llvm.Builder.buildNSWAdd, .@"add nuw" => &llvm.Builder.buildNUWAdd, .@"and" => &llvm.Builder.buildAnd, .ashr => &llvm.Builder.buildAShr, .@"ashr exact" => &llvm.Builder.buildAShrExact, .fadd, .@"fadd fast" => &llvm.Builder.buildFAdd, .fdiv, .@"fdiv fast" => &llvm.Builder.buildFDiv, .fmul, .@"fmul fast" => &llvm.Builder.buildFMul, .frem, .@"frem fast" => &llvm.Builder.buildFRem, .fsub, .@"fsub fast" => &llvm.Builder.buildFSub, .@"llvm.maxnum." => &llvm.Builder.buildMaxNum, .@"llvm.minnum." => &llvm.Builder.buildMinNum, .@"llvm.sadd.sat." => &llvm.Builder.buildSAddSat, .@"llvm.smax." => &llvm.Builder.buildSMax, .@"llvm.smin." => &llvm.Builder.buildSMin, .@"llvm.smul.fix.sat." => &llvm.Builder.buildSMulFixSat, .@"llvm.sshl.sat." => &llvm.Builder.buildSShlSat, .@"llvm.ssub.sat." => &llvm.Builder.buildSSubSat, .@"llvm.uadd.sat." => &llvm.Builder.buildUAddSat, .@"llvm.umax." => &llvm.Builder.buildUMax, .@"llvm.umin." => &llvm.Builder.buildUMin, .@"llvm.umul.fix.sat." => &llvm.Builder.buildUMulFixSat, .@"llvm.ushl.sat." => &llvm.Builder.buildUShlSat, .@"llvm.usub.sat." => &llvm.Builder.buildUSubSat, .lshr => &llvm.Builder.buildLShr, .@"lshr exact" => &llvm.Builder.buildLShrExact, .mul => &llvm.Builder.buildMul, .@"mul nsw" => &llvm.Builder.buildNSWMul, .@"mul nuw" => &llvm.Builder.buildNUWMul, .@"or" => &llvm.Builder.buildOr, .sdiv => &llvm.Builder.buildSDiv, .@"sdiv exact" => &llvm.Builder.buildExactSDiv, .shl => &llvm.Builder.buildShl, .@"shl nsw" => &llvm.Builder.buildNSWShl, .@"shl nuw" => &llvm.Builder.buildNUWShl, .srem => &llvm.Builder.buildSRem, .sub => &llvm.Builder.buildSub, .@"sub nsw" => &llvm.Builder.buildNSWSub, .@"sub nuw" => &llvm.Builder.buildNUWSub, .udiv => &llvm.Builder.buildUDiv, .@"udiv exact" => &llvm.Builder.buildExactUDiv, .urem => &llvm.Builder.buildURem, .xor => &llvm.Builder.buildXor, else => unreachable, }(self.llvm.builder, lhs.toLlvm(self), rhs.toLlvm(self), instruction.llvmName(self))); } return instruction.toValue(); } pub fn extractElement( self: *WipFunction, val: Value, index: Value, name: []const u8, ) Allocator.Error!Value { assert(val.typeOfWip(self).isVector(self.builder)); assert(index.typeOfWip(self).isInteger(self.builder)); try self.ensureUnusedExtraCapacity(1, Instruction.ExtractElement, 0); const instruction = try self.addInst(name, .{ .tag = .extractelement, .data = self.addExtraAssumeCapacity(Instruction.ExtractElement{ .val = val, .index = index, }), }); if (self.builder.useLibLlvm()) self.llvm.instructions.appendAssumeCapacity( self.llvm.builder.buildExtractElement( val.toLlvm(self), index.toLlvm(self), instruction.llvmName(self), ), ); return instruction.toValue(); } pub fn insertElement( self: *WipFunction, val: Value, elem: Value, index: Value, name: []const u8, ) Allocator.Error!Value { assert(val.typeOfWip(self).scalarType(self.builder) == elem.typeOfWip(self)); assert(index.typeOfWip(self).isInteger(self.builder)); try self.ensureUnusedExtraCapacity(1, Instruction.InsertElement, 0); const instruction = try self.addInst(name, .{ .tag = .insertelement, .data = self.addExtraAssumeCapacity(Instruction.InsertElement{ .val = val, .elem = elem, .index = index, }), }); if (self.builder.useLibLlvm()) self.llvm.instructions.appendAssumeCapacity( self.llvm.builder.buildInsertElement( val.toLlvm(self), elem.toLlvm(self), index.toLlvm(self), instruction.llvmName(self), ), ); return instruction.toValue(); } pub fn shuffleVector( self: *WipFunction, lhs: Value, rhs: Value, mask: Value, name: []const u8, ) Allocator.Error!Value { assert(lhs.typeOfWip(self).isVector(self.builder)); assert(lhs.typeOfWip(self) == rhs.typeOfWip(self)); assert(mask.typeOfWip(self).scalarType(self.builder).isInteger(self.builder)); _ = try self.ensureUnusedExtraCapacity(1, Instruction.ShuffleVector, 0); const instruction = try self.addInst(name, .{ .tag = .shufflevector, .data = self.addExtraAssumeCapacity(Instruction.ShuffleVector{ .lhs = lhs, .rhs = rhs, .mask = mask, }), }); if (self.builder.useLibLlvm()) self.llvm.instructions.appendAssumeCapacity( self.llvm.builder.buildShuffleVector( lhs.toLlvm(self), rhs.toLlvm(self), mask.toLlvm(self), instruction.llvmName(self), ), ); return instruction.toValue(); } pub fn splatVector( self: *WipFunction, ty: Type, elem: Value, name: []const u8, ) Allocator.Error!Value { const scalar_ty = try ty.changeLength(1, self.builder); const mask_ty = try ty.changeScalar(.i32, self.builder); const zero = try self.builder.intConst(.i32, 0); const poison = try self.builder.poisonValue(scalar_ty); const mask = try self.builder.splatValue(mask_ty, zero); const scalar = try self.insertElement(poison, elem, zero.toValue(), name); return self.shuffleVector(scalar, poison, mask, name); } pub fn extractValue( self: *WipFunction, val: Value, indices: []const u32, name: []const u8, ) Allocator.Error!Value { assert(indices.len > 0); _ = val.typeOfWip(self).childTypeAt(indices, self.builder); try self.ensureUnusedExtraCapacity(1, Instruction.ExtractValue, indices.len); const instruction = try self.addInst(name, .{ .tag = .extractvalue, .data = self.addExtraAssumeCapacity(Instruction.ExtractValue{ .val = val, .indices_len = @intCast(indices.len), }), }); self.extra.appendSliceAssumeCapacity(indices); if (self.builder.useLibLlvm()) { const llvm_name = instruction.llvmName(self); var cur = val.toLlvm(self); for (indices) |index| cur = self.llvm.builder.buildExtractValue(cur, @intCast(index), llvm_name); self.llvm.instructions.appendAssumeCapacity(cur); } return instruction.toValue(); } pub fn insertValue( self: *WipFunction, val: Value, elem: Value, indices: []const u32, name: []const u8, ) Allocator.Error!Value { assert(indices.len > 0); assert(val.typeOfWip(self).childTypeAt(indices, self.builder) == elem.typeOfWip(self)); try self.ensureUnusedExtraCapacity(1, Instruction.InsertValue, indices.len); const instruction = try self.addInst(name, .{ .tag = .insertvalue, .data = self.addExtraAssumeCapacity(Instruction.InsertValue{ .val = val, .elem = elem, .indices_len = @intCast(indices.len), }), }); self.extra.appendSliceAssumeCapacity(indices); if (self.builder.useLibLlvm()) { const ExpectedContents = [expected_gep_indices_len]*llvm.Value; var stack align(@alignOf(ExpectedContents)) = std.heap.stackFallback(@sizeOf(ExpectedContents), self.builder.gpa); const allocator = stack.get(); const llvm_name = instruction.llvmName(self); const llvm_vals = try allocator.alloc(*llvm.Value, indices.len); defer allocator.free(llvm_vals); llvm_vals[0] = val.toLlvm(self); for (llvm_vals[1..], llvm_vals[0 .. llvm_vals.len - 1], indices[0 .. indices.len - 1]) | *cur_val, prev_val, index, | cur_val.* = self.llvm.builder.buildExtractValue(prev_val, @intCast(index), llvm_name); var depth: usize = llvm_vals.len; var cur = elem.toLlvm(self); while (depth > 0) { depth -= 1; cur = self.llvm.builder.buildInsertValue( llvm_vals[depth], cur, @intCast(indices[depth]), llvm_name, ); } self.llvm.instructions.appendAssumeCapacity(cur); } return instruction.toValue(); } pub fn buildAggregate( self: *WipFunction, ty: Type, elems: []const Value, name: []const u8, ) Allocator.Error!Value { assert(ty.aggregateLen(self.builder) == elems.len); var cur = try self.builder.poisonValue(ty); for (elems, 0..) |elem, index| cur = try self.insertValue(cur, elem, &[_]u32{@intCast(index)}, name); return cur; } pub fn alloca( self: *WipFunction, kind: Instruction.Alloca.Kind, ty: Type, len: Value, alignment: Alignment, addr_space: AddrSpace, name: []const u8, ) Allocator.Error!Value { assert(len == .none or len.typeOfWip(self).isInteger(self.builder)); _ = try self.builder.ptrType(addr_space); try self.ensureUnusedExtraCapacity(1, Instruction.Alloca, 0); const instruction = try self.addInst(name, .{ .tag = switch (kind) { .normal => .alloca, .inalloca => .@"alloca inalloca", }, .data = self.addExtraAssumeCapacity(Instruction.Alloca{ .type = ty, .len = len, .info = .{ .alignment = alignment, .addr_space = addr_space }, }), }); if (self.builder.useLibLlvm()) { const llvm_instruction = self.llvm.builder.buildAllocaInAddressSpace( ty.toLlvm(self.builder), @intFromEnum(addr_space), instruction.llvmName(self), ); if (alignment.toByteUnits()) |a| llvm_instruction.setAlignment(@intCast(a)); self.llvm.instructions.appendAssumeCapacity(llvm_instruction); } return instruction.toValue(); } pub fn load( self: *WipFunction, kind: MemoryAccessKind, ty: Type, ptr: Value, alignment: Alignment, name: []const u8, ) Allocator.Error!Value { return self.loadAtomic(kind, ty, ptr, .system, .none, alignment, name); } pub fn loadAtomic( self: *WipFunction, kind: MemoryAccessKind, ty: Type, ptr: Value, scope: SyncScope, ordering: AtomicOrdering, alignment: Alignment, name: []const u8, ) Allocator.Error!Value { assert(ptr.typeOfWip(self).isPointer(self.builder)); try self.ensureUnusedExtraCapacity(1, Instruction.Load, 0); const instruction = try self.addInst(name, .{ .tag = switch (ordering) { .none => switch (kind) { .normal => .load, .@"volatile" => .@"load volatile", }, else => switch (kind) { .normal => .@"load atomic", .@"volatile" => .@"load atomic volatile", }, }, .data = self.addExtraAssumeCapacity(Instruction.Load{ .type = ty, .ptr = ptr, .info = .{ .scope = switch (ordering) { .none => .system, else => scope, }, .ordering = ordering, .alignment = alignment }, }), }); if (self.builder.useLibLlvm()) { const llvm_instruction = self.llvm.builder.buildLoad( ty.toLlvm(self.builder), ptr.toLlvm(self), instruction.llvmName(self), ); if (ordering != .none) llvm_instruction.setOrdering(@enumFromInt(@intFromEnum(ordering))); if (alignment.toByteUnits()) |a| llvm_instruction.setAlignment(@intCast(a)); self.llvm.instructions.appendAssumeCapacity(llvm_instruction); } return instruction.toValue(); } pub fn store( self: *WipFunction, kind: MemoryAccessKind, val: Value, ptr: Value, alignment: Alignment, ) Allocator.Error!Instruction.Index { return self.storeAtomic(kind, val, ptr, .system, .none, alignment); } pub fn storeAtomic( self: *WipFunction, kind: MemoryAccessKind, val: Value, ptr: Value, scope: SyncScope, ordering: AtomicOrdering, alignment: Alignment, ) Allocator.Error!Instruction.Index { assert(ptr.typeOfWip(self).isPointer(self.builder)); try self.ensureUnusedExtraCapacity(1, Instruction.Store, 0); const instruction = try self.addInst(null, .{ .tag = switch (ordering) { .none => switch (kind) { .normal => .store, .@"volatile" => .@"store volatile", }, else => switch (kind) { .normal => .@"store atomic", .@"volatile" => .@"store atomic volatile", }, }, .data = self.addExtraAssumeCapacity(Instruction.Store{ .val = val, .ptr = ptr, .info = .{ .scope = switch (ordering) { .none => .system, else => scope, }, .ordering = ordering, .alignment = alignment }, }), }); if (self.builder.useLibLlvm()) { const llvm_instruction = self.llvm.builder.buildStore(val.toLlvm(self), ptr.toLlvm(self)); switch (kind) { .normal => {}, .@"volatile" => llvm_instruction.setVolatile(.True), } if (ordering != .none) llvm_instruction.setOrdering(@enumFromInt(@intFromEnum(ordering))); if (alignment.toByteUnits()) |a| llvm_instruction.setAlignment(@intCast(a)); self.llvm.instructions.appendAssumeCapacity(llvm_instruction); } return instruction; } pub fn fence( self: *WipFunction, scope: SyncScope, ordering: AtomicOrdering, ) Allocator.Error!Instruction.Index { assert(ordering != .none); try self.ensureUnusedExtraCapacity(1, NoExtra, 0); const instruction = try self.addInst(null, .{ .tag = .fence, .data = @bitCast(MemoryAccessInfo{ .scope = scope, .ordering = ordering, .alignment = undefined, }), }); if (self.builder.useLibLlvm()) self.llvm.instructions.appendAssumeCapacity( self.llvm.builder.buildFence( @enumFromInt(@intFromEnum(ordering)), llvm.Bool.fromBool(scope == .singlethread), "", ), ); return instruction; } pub fn gep( self: *WipFunction, kind: Instruction.GetElementPtr.Kind, ty: Type, base: Value, indices: []const Value, name: []const u8, ) Allocator.Error!Value { const base_ty = base.typeOfWip(self); const base_is_vector = base_ty.isVector(self.builder); const VectorInfo = struct { kind: Type.Vector.Kind, len: u32, fn init(vector_ty: Type, builder: *const Builder) @This() { return .{ .kind = vector_ty.vectorKind(builder), .len = vector_ty.vectorLen(builder) }; } }; var vector_info: ?VectorInfo = if (base_is_vector) VectorInfo.init(base_ty, self.builder) else null; for (indices) |index| { const index_ty = index.typeOfWip(self); switch (index_ty.tag(self.builder)) { .integer => {}, .vector, .scalable_vector => { const index_info = VectorInfo.init(index_ty, self.builder); if (vector_info) |info| assert(std.meta.eql(info, index_info)) else vector_info = index_info; }, else => unreachable, } } if (!base_is_vector) if (vector_info) |info| switch (info.kind) { inline else => |vector_kind| _ = try self.builder.vectorType( vector_kind, info.len, base_ty, ), }; try self.ensureUnusedExtraCapacity(1, Instruction.GetElementPtr, indices.len); const instruction = try self.addInst(name, .{ .tag = switch (kind) { .normal => .getelementptr, .inbounds => .@"getelementptr inbounds", }, .data = self.addExtraAssumeCapacity(Instruction.GetElementPtr{ .type = ty, .base = base, .indices_len = @intCast(indices.len), }), }); self.extra.appendSliceAssumeCapacity(@ptrCast(indices)); if (self.builder.useLibLlvm()) { const ExpectedContents = [expected_gep_indices_len]*llvm.Value; var stack align(@alignOf(ExpectedContents)) = std.heap.stackFallback(@sizeOf(ExpectedContents), self.builder.gpa); const allocator = stack.get(); const llvm_indices = try allocator.alloc(*llvm.Value, indices.len); defer allocator.free(llvm_indices); for (llvm_indices, indices) |*llvm_index, index| llvm_index.* = index.toLlvm(self); self.llvm.instructions.appendAssumeCapacity(switch (kind) { .normal => &llvm.Builder.buildGEP, .inbounds => &llvm.Builder.buildInBoundsGEP, }( self.llvm.builder, ty.toLlvm(self.builder), base.toLlvm(self), llvm_indices.ptr, @intCast(llvm_indices.len), instruction.llvmName(self), )); } return instruction.toValue(); } pub fn gepStruct( self: *WipFunction, ty: Type, base: Value, index: usize, name: []const u8, ) Allocator.Error!Value { assert(ty.isStruct(self.builder)); return self.gep(.inbounds, ty, base, &.{ try self.builder.intValue(.i32, 0), try self.builder.intValue(.i32, index), }, name); } pub fn conv( self: *WipFunction, signedness: Instruction.Cast.Signedness, val: Value, ty: Type, name: []const u8, ) Allocator.Error!Value { const val_ty = val.typeOfWip(self); if (val_ty == ty) return val; return self.cast(self.builder.convTag(Instruction.Tag, signedness, val_ty, ty), val, ty, name); } pub fn cast( self: *WipFunction, tag: Instruction.Tag, val: Value, ty: Type, name: []const u8, ) Allocator.Error!Value { switch (tag) { .addrspacecast, .bitcast, .fpext, .fptosi, .fptoui, .fptrunc, .inttoptr, .ptrtoint, .sext, .sitofp, .trunc, .uitofp, .zext, => {}, else => unreachable, } if (val.typeOfWip(self) == ty) return val; try self.ensureUnusedExtraCapacity(1, Instruction.Cast, 0); const instruction = try self.addInst(name, .{ .tag = tag, .data = self.addExtraAssumeCapacity(Instruction.Cast{ .val = val, .type = ty, }), }); if (self.builder.useLibLlvm()) self.llvm.instructions.appendAssumeCapacity(switch (tag) { .addrspacecast => &llvm.Builder.buildAddrSpaceCast, .bitcast => &llvm.Builder.buildBitCast, .fpext => &llvm.Builder.buildFPExt, .fptosi => &llvm.Builder.buildFPToSI, .fptoui => &llvm.Builder.buildFPToUI, .fptrunc => &llvm.Builder.buildFPTrunc, .inttoptr => &llvm.Builder.buildIntToPtr, .ptrtoint => &llvm.Builder.buildPtrToInt, .sext => &llvm.Builder.buildSExt, .sitofp => &llvm.Builder.buildSIToFP, .trunc => &llvm.Builder.buildTrunc, .uitofp => &llvm.Builder.buildUIToFP, .zext => &llvm.Builder.buildZExt, else => unreachable, }(self.llvm.builder, val.toLlvm(self), ty.toLlvm(self.builder), instruction.llvmName(self))); return instruction.toValue(); } pub fn icmp( self: *WipFunction, cond: IntegerCondition, lhs: Value, rhs: Value, name: []const u8, ) Allocator.Error!Value { return self.cmpTag(switch (cond) { inline else => |tag| @field(Instruction.Tag, "icmp " ++ @tagName(tag)), }, @intFromEnum(cond), lhs, rhs, name); } pub fn fcmp( self: *WipFunction, cond: FloatCondition, lhs: Value, rhs: Value, name: []const u8, ) Allocator.Error!Value { return self.cmpTag(switch (cond) { inline else => |tag| @field(Instruction.Tag, "fcmp " ++ @tagName(tag)), }, @intFromEnum(cond), lhs, rhs, name); } pub fn fcmpFast( self: *WipFunction, cond: FloatCondition, lhs: Value, rhs: Value, name: []const u8, ) Allocator.Error!Value { return self.cmpTag(switch (cond) { inline else => |tag| @field(Instruction.Tag, "fcmp fast " ++ @tagName(tag)), }, @intFromEnum(cond), lhs, rhs, name); } pub const WipPhi = struct { block: Block.Index, instruction: Instruction.Index, pub fn toValue(self: WipPhi) Value { return self.instruction.toValue(); } pub fn finish( self: WipPhi, vals: []const Value, blocks: []const Block.Index, wip: *WipFunction, ) if (build_options.have_llvm) Allocator.Error!void else void { const incoming_len = self.block.ptrConst(wip).incoming; assert(vals.len == incoming_len and blocks.len == incoming_len); const instruction = wip.instructions.get(@intFromEnum(self.instruction)); const extra = wip.extraDataTrail(Instruction.WipPhi, instruction.data); for (vals) |val| assert(val.typeOfWip(wip) == extra.data.type); const incoming_vals: []Value = @ptrCast(wip.extra.items[extra.end..][0..incoming_len]); const incoming_blocks: []Block.Index = @ptrCast(wip.extra.items[extra.end + incoming_len ..][0..incoming_len]); @memcpy(incoming_vals, vals); @memcpy(incoming_blocks, blocks); if (wip.builder.useLibLlvm()) { const ExpectedContents = extern struct { [expected_incoming_len]*llvm.Value, [expected_incoming_len]*llvm.BasicBlock, }; var stack align(@alignOf(ExpectedContents)) = std.heap.stackFallback(@sizeOf(ExpectedContents), wip.builder.gpa); const allocator = stack.get(); const llvm_vals = try allocator.alloc(*llvm.Value, incoming_len); defer allocator.free(llvm_vals); const llvm_blocks = try allocator.alloc(*llvm.BasicBlock, incoming_len); defer allocator.free(llvm_blocks); for (llvm_vals, vals) |*llvm_val, incoming_val| llvm_val.* = incoming_val.toLlvm(wip); for (llvm_blocks, blocks) |*llvm_block, incoming_block| llvm_block.* = incoming_block.toLlvm(wip); self.instruction.toLlvm(wip) .addIncoming(llvm_vals.ptr, llvm_blocks.ptr, @intCast(incoming_len)); } } }; pub fn phi(self: *WipFunction, ty: Type, name: []const u8) Allocator.Error!WipPhi { return self.phiTag(.phi, ty, name); } pub fn phiFast(self: *WipFunction, ty: Type, name: []const u8) Allocator.Error!WipPhi { return self.phiTag(.@"phi fast", ty, name); } pub fn select( self: *WipFunction, cond: Value, lhs: Value, rhs: Value, name: []const u8, ) Allocator.Error!Value { return self.selectTag(.select, cond, lhs, rhs, name); } pub fn selectFast( self: *WipFunction, cond: Value, lhs: Value, rhs: Value, name: []const u8, ) Allocator.Error!Value { return self.selectTag(.@"select fast", cond, lhs, rhs, name); } 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, .{ .tag = .va_arg, .data = self.addExtraAssumeCapacity(Instruction.VaArg{ .list = list, .type = ty, }), }); if (self.builder.useLibLlvm()) self.llvm.instructions.appendAssumeCapacity( self.llvm.builder.buildVAArg( list.toLlvm(self), ty.toLlvm(self.builder), instruction.llvmName(self), ), ); return instruction.toValue(); } pub const WipUnimplemented = struct { instruction: Instruction.Index, pub fn finish(self: WipUnimplemented, val: *llvm.Value, wip: *WipFunction) Value { assert(wip.builder.useLibLlvm()); wip.llvm.instructions.items[@intFromEnum(self.instruction)] = val; return self.instruction.toValue(); } }; pub fn unimplemented( self: *WipFunction, ty: Type, name: []const u8, ) Allocator.Error!WipUnimplemented { try self.ensureUnusedExtraCapacity(1, NoExtra, 0); const instruction = try self.addInst(name, .{ .tag = .unimplemented, .data = @intFromEnum(ty), }); if (self.builder.useLibLlvm()) _ = self.llvm.instructions.addOneAssumeCapacity(); return .{ .instruction = instruction }; } pub fn finish(self: *WipFunction) Allocator.Error!void { const gpa = self.builder.gpa; const function = self.function.ptr(self.builder); const params_len = self.function.typeOf(self.builder).functionParameters(self.builder).len; const final_instructions_len = self.blocks.items.len + self.instructions.len; const blocks = try gpa.alloc(Function.Block, self.blocks.items.len); errdefer gpa.free(blocks); const instructions: struct { items: []Instruction.Index, fn map(instructions: @This(), val: Value) Value { if (val == .none) return .none; return switch (val.unwrap()) { .instruction => |instruction| instructions.items[ @intFromEnum(instruction) ].toValue(), .constant => |constant| constant.toValue(), }; } } = .{ .items = try gpa.alloc(Instruction.Index, self.instructions.len) }; defer gpa.free(instructions.items); const names = try gpa.alloc(String, final_instructions_len); errdefer gpa.free(names); const metadata = if (self.builder.strip) null else try gpa.alloc(Metadata, final_instructions_len); errdefer if (metadata) |new_metadata| gpa.free(new_metadata); var wip_extra: struct { index: Instruction.ExtraIndex = 0, items: []u32, fn addExtra(wip_extra: *@This(), extra: anytype) Instruction.ExtraIndex { const result = wip_extra.index; inline for (@typeInfo(@TypeOf(extra)).Struct.fields) |field| { 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), else => @compileError("bad field type: " ++ @typeName(field.type)), }; wip_extra.index += 1; } return result; } fn appendSlice(wip_extra: *@This(), slice: anytype) void { if (@typeInfo(@TypeOf(slice)).Pointer.child == Value) @compileError("use appendValues"); 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 { for (wip_extra.items[wip_extra.index..][0..vals.len], vals) |*extra, val| extra.* = @intFromEnum(ctx.map(val)); wip_extra.index += @intCast(vals.len); } fn finish(wip_extra: *const @This()) []const u32 { assert(wip_extra.index == wip_extra.items.len); return wip_extra.items; } } = .{ .items = try gpa.alloc(u32, self.extra.items.len) }; errdefer gpa.free(wip_extra.items); gpa.free(function.blocks); function.blocks = &.{}; gpa.free(function.names[0..function.instructions.len]); if (function.metadata) |old_metadata| gpa.free(old_metadata[0..function.instructions.len]); function.metadata = null; gpa.free(function.extra); function.extra = &.{}; function.instructions.shrinkRetainingCapacity(0); try function.instructions.setCapacity(gpa, final_instructions_len); errdefer function.instructions.shrinkRetainingCapacity(0); { var final_instruction_index: Instruction.Index = @enumFromInt(0); for (0..params_len) |param_index| { instructions.items[param_index] = final_instruction_index; final_instruction_index = @enumFromInt(@intFromEnum(final_instruction_index) + 1); } for (blocks, self.blocks.items) |*final_block, current_block| { assert(current_block.incoming == current_block.branches); final_block.instruction = final_instruction_index; final_instruction_index = @enumFromInt(@intFromEnum(final_instruction_index) + 1); for (current_block.instructions.items) |instruction| { instructions.items[@intFromEnum(instruction)] = final_instruction_index; final_instruction_index = @enumFromInt(@intFromEnum(final_instruction_index) + 1); } } } var wip_name: struct { next_name: String = @enumFromInt(0), fn map(wip_name: *@This(), old_name: String) String { if (old_name != .empty) return old_name; const new_name = wip_name.next_name; wip_name.next_name = @enumFromInt(@intFromEnum(new_name) + 1); return new_name; } } = .{}; for (0..params_len) |param_index| { const old_argument_index: Instruction.Index = @enumFromInt(param_index); const new_argument_index: Instruction.Index = @enumFromInt(function.instructions.len); const argument = self.instructions.get(@intFromEnum(old_argument_index)); assert(argument.tag == .arg); assert(argument.data == param_index); function.instructions.appendAssumeCapacity(argument); names[@intFromEnum(new_argument_index)] = wip_name.map( if (self.builder.strip) .empty else self.names.items[@intFromEnum(old_argument_index)], ); } for (self.blocks.items) |current_block| { const new_block_index: Instruction.Index = @enumFromInt(function.instructions.len); function.instructions.appendAssumeCapacity(.{ .tag = .block, .data = current_block.incoming, }); names[@intFromEnum(new_block_index)] = wip_name.map(current_block.name); for (current_block.instructions.items) |old_instruction_index| { const new_instruction_index: Instruction.Index = @enumFromInt(function.instructions.len); var instruction = self.instructions.get(@intFromEnum(old_instruction_index)); switch (instruction.tag) { .add, .@"add nsw", .@"add nuw", .@"add nuw nsw", .@"and", .ashr, .@"ashr exact", .fadd, .@"fadd fast", .@"fcmp false", .@"fcmp fast false", .@"fcmp fast oeq", .@"fcmp fast oge", .@"fcmp fast ogt", .@"fcmp fast ole", .@"fcmp fast olt", .@"fcmp fast one", .@"fcmp fast ord", .@"fcmp fast true", .@"fcmp fast ueq", .@"fcmp fast uge", .@"fcmp fast ugt", .@"fcmp fast ule", .@"fcmp fast ult", .@"fcmp fast une", .@"fcmp fast uno", .@"fcmp oeq", .@"fcmp oge", .@"fcmp ogt", .@"fcmp ole", .@"fcmp olt", .@"fcmp one", .@"fcmp ord", .@"fcmp true", .@"fcmp ueq", .@"fcmp uge", .@"fcmp ugt", .@"fcmp ule", .@"fcmp ult", .@"fcmp une", .@"fcmp uno", .fdiv, .@"fdiv fast", .fmul, .@"fmul fast", .frem, .@"frem fast", .fsub, .@"fsub fast", .@"icmp eq", .@"icmp ne", .@"icmp sge", .@"icmp sgt", .@"icmp sle", .@"icmp slt", .@"icmp uge", .@"icmp ugt", .@"icmp ule", .@"icmp ult", .@"llvm.maxnum.", .@"llvm.minnum.", .@"llvm.sadd.sat.", .@"llvm.smax.", .@"llvm.smin.", .@"llvm.smul.fix.sat.", .@"llvm.sshl.sat.", .@"llvm.ssub.sat.", .@"llvm.uadd.sat.", .@"llvm.umax.", .@"llvm.umin.", .@"llvm.umul.fix.sat.", .@"llvm.ushl.sat.", .@"llvm.usub.sat.", .lshr, .@"lshr exact", .mul, .@"mul nsw", .@"mul nuw", .@"mul nuw nsw", .@"or", .sdiv, .@"sdiv exact", .shl, .@"shl nsw", .@"shl nuw", .@"shl nuw nsw", .srem, .sub, .@"sub nsw", .@"sub nuw", .@"sub nuw nsw", .udiv, .@"udiv exact", .urem, .xor, => { const extra = self.extraData(Instruction.Binary, instruction.data); instruction.data = wip_extra.addExtra(Instruction.Binary{ .lhs = instructions.map(extra.lhs), .rhs = instructions.map(extra.rhs), }); }, .addrspacecast, .bitcast, .fpext, .fptosi, .fptoui, .fptrunc, .inttoptr, .ptrtoint, .sext, .sitofp, .trunc, .uitofp, .zext, => { const extra = self.extraData(Instruction.Cast, instruction.data); instruction.data = wip_extra.addExtra(Instruction.Cast{ .val = instructions.map(extra.val), .type = extra.type, }); }, .alloca, .@"alloca inalloca", => { const extra = self.extraData(Instruction.Alloca, instruction.data); instruction.data = wip_extra.addExtra(Instruction.Alloca{ .type = extra.type, .len = instructions.map(extra.len), .info = extra.info, }); }, .arg, .block, => unreachable, .br, .fence, .@"ret void", .unimplemented, .@"unreachable", => {}, .extractelement => { const extra = self.extraData(Instruction.ExtractElement, instruction.data); instruction.data = wip_extra.addExtra(Instruction.ExtractElement{ .val = instructions.map(extra.val), .index = instructions.map(extra.index), }); }, .br_cond => { const extra = self.extraData(Instruction.BrCond, instruction.data); instruction.data = wip_extra.addExtra(Instruction.BrCond{ .cond = instructions.map(extra.cond), .then = extra.then, .@"else" = extra.@"else", }); }, .extractvalue => { const extra = self.extraDataTrail(Instruction.ExtractValue, instruction.data); const indices: []const u32 = self.extra.items[extra.end..][0..extra.data.indices_len]; instruction.data = wip_extra.addExtra(Instruction.ExtractValue{ .val = instructions.map(extra.data.val), .indices_len = extra.data.indices_len, }); wip_extra.appendSlice(indices); }, .fneg, .@"fneg fast", .ret, => instruction.data = @intFromEnum(instructions.map(@enumFromInt(instruction.data))), .getelementptr, .@"getelementptr inbounds", => { const extra = self.extraDataTrail(Instruction.GetElementPtr, instruction.data); const indices: []const Value = @ptrCast(self.extra.items[extra.end..][0..extra.data.indices_len]); instruction.data = wip_extra.addExtra(Instruction.GetElementPtr{ .type = extra.data.type, .base = instructions.map(extra.data.base), .indices_len = extra.data.indices_len, }); wip_extra.appendValues(indices, instructions); }, .insertelement => { const extra = self.extraData(Instruction.InsertElement, instruction.data); instruction.data = wip_extra.addExtra(Instruction.InsertElement{ .val = instructions.map(extra.val), .elem = instructions.map(extra.elem), .index = instructions.map(extra.index), }); }, .insertvalue => { const extra = self.extraDataTrail(Instruction.InsertValue, instruction.data); const indices: []const u32 = self.extra.items[extra.end..][0..extra.data.indices_len]; instruction.data = wip_extra.addExtra(Instruction.InsertValue{ .val = instructions.map(extra.data.val), .elem = instructions.map(extra.data.elem), .indices_len = extra.data.indices_len, }); wip_extra.appendSlice(indices); }, .load, .@"load atomic", .@"load atomic volatile", .@"load volatile", => { const extra = self.extraData(Instruction.Load, instruction.data); instruction.data = wip_extra.addExtra(Instruction.Load{ .type = extra.type, .ptr = instructions.map(extra.ptr), .info = extra.info, }); }, .phi, .@"phi fast", => { const extra = self.extraDataTrail(Instruction.WipPhi, instruction.data); const incoming_len = current_block.incoming; const incoming_vals: []const Value = @ptrCast(self.extra.items[extra.end..][0..incoming_len]); const incoming_blocks: []const Block.Index = @ptrCast(self.extra.items[extra.end + incoming_len ..][0..incoming_len]); instruction.data = wip_extra.addExtra(Instruction.Phi{ .incoming_len = incoming_len, }); wip_extra.appendValues(incoming_vals, instructions); wip_extra.appendSlice(incoming_blocks); }, .select, .@"select fast", => { const extra = self.extraData(Instruction.Select, instruction.data); instruction.data = wip_extra.addExtra(Instruction.Select{ .cond = instructions.map(extra.cond), .lhs = instructions.map(extra.lhs), .rhs = instructions.map(extra.rhs), }); }, .shufflevector => { const extra = self.extraData(Instruction.ShuffleVector, instruction.data); instruction.data = wip_extra.addExtra(Instruction.ShuffleVector{ .lhs = instructions.map(extra.lhs), .rhs = instructions.map(extra.rhs), .mask = instructions.map(extra.mask), }); }, .store, .@"store atomic", .@"store atomic volatile", .@"store volatile", => { const extra = self.extraData(Instruction.Store, instruction.data); instruction.data = wip_extra.addExtra(Instruction.Store{ .val = instructions.map(extra.val), .ptr = instructions.map(extra.ptr), .info = extra.info, }); }, .@"switch" => { const extra = self.extraDataTrail(Instruction.Switch, instruction.data); const case_vals: []const Constant = @ptrCast(self.extra.items[extra.end..][0..extra.data.cases_len]); const case_blocks: []const Block.Index = @ptrCast(self.extra .items[extra.end + extra.data.cases_len ..][0..extra.data.cases_len]); instruction.data = wip_extra.addExtra(Instruction.Switch{ .val = instructions.map(extra.data.val), .default = extra.data.default, .cases_len = extra.data.cases_len, }); wip_extra.appendSlice(case_vals); wip_extra.appendSlice(case_blocks); }, .va_arg => { const extra = self.extraData(Instruction.VaArg, instruction.data); instruction.data = wip_extra.addExtra(Instruction.VaArg{ .list = instructions.map(extra.list), .type = extra.type, }); }, } function.instructions.appendAssumeCapacity(instruction); names[@intFromEnum(new_instruction_index)] = wip_name.map(if (self.builder.strip) if (old_instruction_index.hasResultWip(self)) .empty else .none else self.names.items[@intFromEnum(old_instruction_index)]); } } assert(function.instructions.len == final_instructions_len); function.extra = wip_extra.finish(); function.blocks = blocks; function.names = names.ptr; function.metadata = if (metadata) |new_metadata| new_metadata.ptr else null; } pub fn deinit(self: *WipFunction) void { self.extra.deinit(self.builder.gpa); self.instructions.deinit(self.builder.gpa); for (self.blocks.items) |*b| b.instructions.deinit(self.builder.gpa); self.blocks.deinit(self.builder.gpa); if (self.builder.useLibLlvm()) self.llvm.builder.dispose(); self.* = undefined; } fn cmpTag( self: *WipFunction, tag: Instruction.Tag, cond: u32, lhs: Value, rhs: Value, name: []const u8, ) Allocator.Error!Value { switch (tag) { .@"fcmp false", .@"fcmp fast false", .@"fcmp fast oeq", .@"fcmp fast oge", .@"fcmp fast ogt", .@"fcmp fast ole", .@"fcmp fast olt", .@"fcmp fast one", .@"fcmp fast ord", .@"fcmp fast true", .@"fcmp fast ueq", .@"fcmp fast uge", .@"fcmp fast ugt", .@"fcmp fast ule", .@"fcmp fast ult", .@"fcmp fast une", .@"fcmp fast uno", .@"fcmp oeq", .@"fcmp oge", .@"fcmp ogt", .@"fcmp ole", .@"fcmp olt", .@"fcmp one", .@"fcmp ord", .@"fcmp true", .@"fcmp ueq", .@"fcmp uge", .@"fcmp ugt", .@"fcmp ule", .@"fcmp ult", .@"fcmp une", .@"fcmp uno", .@"icmp eq", .@"icmp ne", .@"icmp sge", .@"icmp sgt", .@"icmp sle", .@"icmp slt", .@"icmp uge", .@"icmp ugt", .@"icmp ule", .@"icmp ult", => assert(lhs.typeOfWip(self) == rhs.typeOfWip(self)), else => unreachable, } _ = try lhs.typeOfWip(self).changeScalar(.i1, self.builder); try self.ensureUnusedExtraCapacity(1, Instruction.Binary, 0); const instruction = try self.addInst(name, .{ .tag = tag, .data = self.addExtraAssumeCapacity(Instruction.Binary{ .lhs = lhs, .rhs = rhs, }), }); if (self.builder.useLibLlvm()) { switch (tag) { .@"fcmp false", .@"fcmp oeq", .@"fcmp oge", .@"fcmp ogt", .@"fcmp ole", .@"fcmp olt", .@"fcmp one", .@"fcmp ord", .@"fcmp true", .@"fcmp ueq", .@"fcmp uge", .@"fcmp ugt", .@"fcmp ule", .@"fcmp ult", .@"fcmp une", .@"fcmp uno", => self.llvm.builder.setFastMath(false), .@"fcmp fast false", .@"fcmp fast oeq", .@"fcmp fast oge", .@"fcmp fast ogt", .@"fcmp fast ole", .@"fcmp fast olt", .@"fcmp fast one", .@"fcmp fast ord", .@"fcmp fast true", .@"fcmp fast ueq", .@"fcmp fast uge", .@"fcmp fast ugt", .@"fcmp fast ule", .@"fcmp fast ult", .@"fcmp fast une", .@"fcmp fast uno", => self.llvm.builder.setFastMath(true), .@"icmp eq", .@"icmp ne", .@"icmp sge", .@"icmp sgt", .@"icmp sle", .@"icmp slt", .@"icmp uge", .@"icmp ugt", .@"icmp ule", .@"icmp ult", => {}, else => unreachable, } self.llvm.instructions.appendAssumeCapacity(switch (tag) { .@"fcmp false", .@"fcmp fast false", .@"fcmp fast oeq", .@"fcmp fast oge", .@"fcmp fast ogt", .@"fcmp fast ole", .@"fcmp fast olt", .@"fcmp fast one", .@"fcmp fast ord", .@"fcmp fast true", .@"fcmp fast ueq", .@"fcmp fast uge", .@"fcmp fast ugt", .@"fcmp fast ule", .@"fcmp fast ult", .@"fcmp fast une", .@"fcmp fast uno", .@"fcmp oeq", .@"fcmp oge", .@"fcmp ogt", .@"fcmp ole", .@"fcmp olt", .@"fcmp one", .@"fcmp ord", .@"fcmp true", .@"fcmp ueq", .@"fcmp uge", .@"fcmp ugt", .@"fcmp ule", .@"fcmp ult", .@"fcmp une", .@"fcmp uno", => self.llvm.builder.buildFCmp( @enumFromInt(cond), lhs.toLlvm(self), rhs.toLlvm(self), instruction.llvmName(self), ), .@"icmp eq", .@"icmp ne", .@"icmp sge", .@"icmp sgt", .@"icmp sle", .@"icmp slt", .@"icmp uge", .@"icmp ugt", .@"icmp ule", .@"icmp ult", => self.llvm.builder.buildICmp( @enumFromInt(cond), lhs.toLlvm(self), rhs.toLlvm(self), instruction.llvmName(self), ), else => unreachable, }); } return instruction.toValue(); } fn phiTag( self: *WipFunction, tag: Instruction.Tag, ty: Type, name: []const u8, ) Allocator.Error!WipPhi { switch (tag) { .phi, .@"phi fast" => assert(try ty.isSized(self.builder)), else => unreachable, } const incoming = self.cursor.block.ptrConst(self).incoming; assert(incoming > 0); try self.ensureUnusedExtraCapacity(1, Instruction.WipPhi, incoming * 2); const instruction = try self.addInst(name, .{ .tag = tag, .data = self.addExtraAssumeCapacity(Instruction.WipPhi{ .type = ty }), }); _ = self.extra.addManyAsSliceAssumeCapacity(incoming * 2); if (self.builder.useLibLlvm()) { switch (tag) { .phi => self.llvm.builder.setFastMath(false), .@"phi fast" => self.llvm.builder.setFastMath(true), else => unreachable, } self.llvm.instructions.appendAssumeCapacity( self.llvm.builder.buildPhi(ty.toLlvm(self.builder), instruction.llvmName(self)), ); } return .{ .block = self.cursor.block, .instruction = instruction }; } fn selectTag( self: *WipFunction, tag: Instruction.Tag, cond: Value, lhs: Value, rhs: Value, name: []const u8, ) Allocator.Error!Value { switch (tag) { .select, .@"select fast" => { assert(cond.typeOfWip(self).scalarType(self.builder) == .i1); assert(lhs.typeOfWip(self) == rhs.typeOfWip(self)); }, else => unreachable, } try self.ensureUnusedExtraCapacity(1, Instruction.Select, 0); const instruction = try self.addInst(name, .{ .tag = tag, .data = self.addExtraAssumeCapacity(Instruction.Select{ .cond = cond, .lhs = lhs, .rhs = rhs, }), }); if (self.builder.useLibLlvm()) { switch (tag) { .select => self.llvm.builder.setFastMath(false), .@"select fast" => self.llvm.builder.setFastMath(true), else => unreachable, } self.llvm.instructions.appendAssumeCapacity(self.llvm.builder.buildSelect( cond.toLlvm(self), lhs.toLlvm(self), rhs.toLlvm(self), instruction.llvmName(self), )); } return instruction.toValue(); } fn ensureUnusedExtraCapacity( self: *WipFunction, count: usize, comptime Extra: type, trail_len: usize, ) Allocator.Error!void { try self.extra.ensureUnusedCapacity( self.builder.gpa, count * (@typeInfo(Extra).Struct.fields.len + trail_len), ); } fn addInst( self: *WipFunction, name: ?[]const u8, instruction: Instruction, ) Allocator.Error!Instruction.Index { const block_instructions = &self.cursor.block.ptr(self).instructions; try self.instructions.ensureUnusedCapacity(self.builder.gpa, 1); if (!self.builder.strip) try self.names.ensureUnusedCapacity(self.builder.gpa, 1); try block_instructions.ensureUnusedCapacity(self.builder.gpa, 1); if (self.builder.useLibLlvm()) try self.llvm.instructions.ensureUnusedCapacity(self.builder.gpa, 1); const final_name = if (name) |n| if (self.builder.strip) .empty else try self.builder.string(n) else .none; if (self.builder.useLibLlvm()) self.llvm.builder.positionBuilder( self.cursor.block.toLlvm(self), for (block_instructions.items[self.cursor.instruction..]) |instruction_index| { const llvm_instruction = self.llvm.instructions.items[@intFromEnum(instruction_index)]; // TODO: remove when constant propagation is implemented if (!llvm_instruction.isConstant().toBool()) break llvm_instruction; } else null, ); const index: Instruction.Index = @enumFromInt(self.instructions.len); self.instructions.appendAssumeCapacity(instruction); if (!self.builder.strip) self.names.appendAssumeCapacity(final_name); block_instructions.insertAssumeCapacity(self.cursor.instruction, index); self.cursor.instruction += 1; return index; } fn addExtraAssumeCapacity(self: *WipFunction, extra: anytype) Instruction.ExtraIndex { const result: Instruction.ExtraIndex = @intCast(self.extra.items.len); inline for (@typeInfo(@TypeOf(extra)).Struct.fields) |field| { 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), else => @compileError("bad field type: " ++ @typeName(field.type)), }); } return result; } fn extraDataTrail( self: *const WipFunction, comptime T: type, index: Instruction.ExtraIndex, ) struct { data: T, end: Instruction.ExtraIndex } { var result: T = undefined; const fields = @typeInfo(T).Struct.fields; 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), else => @compileError("bad field type: " ++ @typeName(field.type)), }; return .{ .data = result, .end = index + @as(Type.Item.ExtraIndex, @intCast(fields.len)) }; } fn extraData(self: *const WipFunction, comptime T: type, index: Instruction.ExtraIndex) T { return self.extraDataTrail(T, index).data; } }; pub const FloatCondition = enum(u4) { oeq = 1, ogt = 2, oge = 3, olt = 4, ole = 5, one = 6, ord = 7, uno = 8, ueq = 9, ugt = 10, uge = 11, ult = 12, ule = 13, une = 14, }; pub const IntegerCondition = enum(u6) { eq = 32, ne = 33, ugt = 34, uge = 35, ult = 36, ule = 37, sgt = 38, sge = 39, slt = 40, sle = 41, }; pub const MemoryAccessKind = enum(u1) { normal, @"volatile", }; pub const SyncScope = enum(u1) { singlethread, system, pub fn format( self: SyncScope, comptime prefix: []const u8, _: std.fmt.FormatOptions, writer: anytype, ) @TypeOf(writer).Error!void { if (self != .system) try writer.print( \\{s} syncscope("{s}") , .{ prefix, @tagName(self) }); } }; pub const AtomicOrdering = enum(u3) { none = 0, unordered = 1, monotonic = 2, acquire = 4, release = 5, acq_rel = 6, seq_cst = 7, pub fn format( self: AtomicOrdering, comptime prefix: []const u8, _: std.fmt.FormatOptions, writer: anytype, ) @TypeOf(writer).Error!void { if (self != .none) try writer.print("{s} {s}", .{ prefix, @tagName(self) }); } }; const MemoryAccessInfo = packed struct(u32) { scope: SyncScope, ordering: AtomicOrdering, alignment: Alignment, _: u22 = undefined, }; pub const FastMath = packed struct(u32) { nnan: bool = false, ninf: bool = false, nsz: bool = false, arcp: bool = false, contract: bool = false, afn: bool = false, reassoc: bool = false, pub const fast = FastMath{ .nnan = true, .ninf = true, .nsz = true, .arcp = true, .contract = true, .afn = true, .realloc = true, }; }; pub const Constant = enum(u32) { false, true, none, no_init = 1 << 31, _, const first_global: Constant = @enumFromInt(1 << 30); pub const Tag = enum(u6) { positive_integer, negative_integer, half, bfloat, float, double, fp128, x86_fp80, ppc_fp128, null, none, structure, packed_structure, array, string, string_null, vector, splat, zeroinitializer, undef, poison, blockaddress, dso_local_equivalent, no_cfi, trunc, zext, sext, fptrunc, fpext, fptoui, fptosi, uitofp, sitofp, ptrtoint, inttoptr, bitcast, addrspacecast, getelementptr, @"getelementptr inbounds", icmp, fcmp, extractelement, insertelement, shufflevector, add, @"add nsw", @"add nuw", sub, @"sub nsw", @"sub nuw", mul, @"mul nsw", @"mul nuw", shl, lshr, ashr, @"and", @"or", xor, }; pub const Item = struct { tag: Tag, data: ExtraIndex, const ExtraIndex = u32; }; pub const Integer = packed struct(u64) { type: Type, limbs_len: u32, pub const limbs = @divExact(@bitSizeOf(Integer), @bitSizeOf(std.math.big.Limb)); }; pub const Double = struct { lo: u32, hi: u32, }; pub const Fp80 = struct { lo_lo: u32, lo_hi: u32, hi: u32, }; pub const Fp128 = struct { lo_lo: u32, lo_hi: u32, hi_lo: u32, hi_hi: u32, }; pub const Aggregate = struct { type: Type, //fields: [type.aggregateLen(builder)]Constant, }; pub const Splat = extern struct { type: Type, value: Constant, }; pub const BlockAddress = extern struct { function: Function.Index, block: Function.Block.Index, }; pub const Cast = extern struct { val: Constant, type: Type, pub const Signedness = enum { unsigned, signed, unneeded }; }; pub const GetElementPtr = struct { type: Type, base: Constant, info: Info, //indices: [info.indices_len]Constant, pub const Kind = enum { normal, inbounds }; pub const InRangeIndex = enum(u16) { none = std.math.maxInt(u16), _ }; pub const Info = packed struct(u32) { indices_len: u16, inrange: InRangeIndex }; }; pub const Compare = extern struct { cond: u32, lhs: Constant, rhs: Constant, }; pub const ExtractElement = extern struct { val: Constant, index: Constant, }; pub const InsertElement = extern struct { val: Constant, elem: Constant, index: Constant, }; pub const ShuffleVector = extern struct { lhs: Constant, rhs: Constant, mask: Constant, }; pub const Binary = extern struct { lhs: Constant, rhs: Constant, }; pub fn unwrap(self: Constant) union(enum) { constant: u30, global: Global.Index, } { return if (@intFromEnum(self) < @intFromEnum(first_global)) .{ .constant = @intCast(@intFromEnum(self)) } else .{ .global = @enumFromInt(@intFromEnum(self) - @intFromEnum(first_global)) }; } pub fn toValue(self: Constant) Value { return @enumFromInt(@intFromEnum(Value.first_constant) + @intFromEnum(self)); } pub fn typeOf(self: Constant, builder: *Builder) Type { switch (self.unwrap()) { .constant => |constant| { const item = builder.constant_items.get(constant); return switch (item.tag) { .positive_integer, .negative_integer, => @as( *align(@alignOf(std.math.big.Limb)) Integer, @ptrCast(builder.constant_limbs.items[item.data..][0..Integer.limbs]), ).type, .half => .half, .bfloat => .bfloat, .float => .float, .double => .double, .fp128 => .fp128, .x86_fp80 => .x86_fp80, .ppc_fp128 => .ppc_fp128, .null, .none, .zeroinitializer, .undef, .poison, => @enumFromInt(item.data), .structure, .packed_structure, .array, .vector, => builder.constantExtraData(Aggregate, item.data).type, .splat => builder.constantExtraData(Splat, item.data).type, .string, .string_null, => builder.arrayTypeAssumeCapacity( @as(String, @enumFromInt(item.data)).toSlice(builder).?.len + @intFromBool(item.tag == .string_null), .i8, ), .blockaddress => builder.ptrTypeAssumeCapacity( builder.constantExtraData(BlockAddress, item.data) .function.ptrConst(builder).global.ptrConst(builder).addr_space, ), .dso_local_equivalent, .no_cfi, => builder.ptrTypeAssumeCapacity(@as(Function.Index, @enumFromInt(item.data)) .ptrConst(builder).global.ptrConst(builder).addr_space), .trunc, .zext, .sext, .fptrunc, .fpext, .fptoui, .fptosi, .uitofp, .sitofp, .ptrtoint, .inttoptr, .bitcast, .addrspacecast, => builder.constantExtraData(Cast, item.data).type, .getelementptr, .@"getelementptr inbounds", => { const extra = builder.constantExtraDataTrail(GetElementPtr, item.data); const indices: []const Constant = @ptrCast(builder.constant_extra .items[extra.end..][0..extra.data.info.indices_len]); const base_ty = extra.data.base.typeOf(builder); if (!base_ty.isVector(builder)) for (indices) |index| { const index_ty = index.typeOf(builder); if (!index_ty.isVector(builder)) continue; return index_ty.changeScalarAssumeCapacity(base_ty, builder); }; return base_ty; }, .icmp, .fcmp, => builder.constantExtraData(Compare, item.data).lhs.typeOf(builder) .changeScalarAssumeCapacity(.i1, builder), .extractelement => builder.constantExtraData(ExtractElement, item.data) .val.typeOf(builder).childType(builder), .insertelement => builder.constantExtraData(InsertElement, item.data) .val.typeOf(builder), .shufflevector => { const extra = builder.constantExtraData(ShuffleVector, item.data); return extra.lhs.typeOf(builder).changeLengthAssumeCapacity( extra.mask.typeOf(builder).vectorLen(builder), builder, ); }, .add, .@"add nsw", .@"add nuw", .sub, .@"sub nsw", .@"sub nuw", .mul, .@"mul nsw", .@"mul nuw", .shl, .lshr, .ashr, .@"and", .@"or", .xor, => builder.constantExtraData(Binary, item.data).lhs.typeOf(builder), }; }, .global => |global| return builder.ptrTypeAssumeCapacity( global.ptrConst(builder).addr_space, ), } } pub fn isZeroInit(self: Constant, builder: *const Builder) bool { switch (self.unwrap()) { .constant => |constant| { const item = builder.constant_items.get(constant); return switch (item.tag) { .positive_integer => { const extra: *align(@alignOf(std.math.big.Limb)) Integer = @ptrCast(builder.constant_limbs.items[item.data..][0..Integer.limbs]); const limbs = builder.constant_limbs .items[item.data + Integer.limbs ..][0..extra.limbs_len]; return std.mem.eql(std.math.big.Limb, limbs, &.{0}); }, .half, .bfloat, .float => item.data == 0, .double => { const extra = builder.constantExtraData(Constant.Double, item.data); return extra.lo == 0 and extra.hi == 0; }, .fp128, .ppc_fp128 => { const extra = builder.constantExtraData(Constant.Fp128, item.data); return extra.lo_lo == 0 and extra.lo_hi == 0 and extra.hi_lo == 0 and extra.hi_hi == 0; }, .x86_fp80 => { const extra = builder.constantExtraData(Constant.Fp80, item.data); return extra.lo_lo == 0 and extra.lo_hi == 0 and extra.hi == 0; }, .vector => { const extra = builder.constantExtraDataTrail(Aggregate, item.data); const len = extra.data.type.aggregateLen(builder); const vals: []const Constant = @ptrCast(builder.constant_extra.items[extra.end..][0..len]); for (vals) |val| if (!val.isZeroInit(builder)) return false; return true; }, .null, .zeroinitializer => true, else => false, }; }, .global => return false, } } pub fn getBase(self: Constant, builder: *const Builder) Global.Index { var cur = self; while (true) switch (cur.unwrap()) { .constant => |constant| { const item = builder.constant_items.get(constant); switch (item.tag) { .ptrtoint, .inttoptr, .bitcast, => cur = builder.constantExtraData(Cast, item.data).val, .getelementptr => cur = builder.constantExtraData(GetElementPtr, item.data).base, .add => { const extra = builder.constantExtraData(Binary, item.data); const lhs_base = extra.lhs.getBase(builder); const rhs_base = extra.rhs.getBase(builder); return if (lhs_base != .none and rhs_base != .none) .none else if (lhs_base != .none) lhs_base else rhs_base; }, .sub => { const extra = builder.constantExtraData(Binary, item.data); if (extra.rhs.getBase(builder) != .none) return .none; cur = extra.lhs; }, else => return .none, } }, .global => |global| switch (global.ptrConst(builder).kind) { .alias => |alias| cur = alias.ptrConst(builder).init, .variable, .function => return global, .replaced => unreachable, }, }; } const FormatData = struct { constant: Constant, builder: *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 ++ "'"); if (comptime std.mem.indexOfScalar(u8, fmt_str, ',') != null) { if (data.constant == .no_init) return; try writer.writeByte(','); } if (comptime std.mem.indexOfScalar(u8, fmt_str, ' ') != null) { if (data.constant == .no_init) return; try writer.writeByte(' '); } if (comptime std.mem.indexOfScalar(u8, fmt_str, '%') != null) try writer.print("{%} ", .{data.constant.typeOf(data.builder).fmt(data.builder)}); assert(data.constant != .no_init); if (std.enums.tagName(Constant, data.constant)) |name| return writer.writeAll(name); switch (data.constant.unwrap()) { .constant => |constant| { const item = data.builder.constant_items.get(constant); switch (item.tag) { .positive_integer, .negative_integer, => |tag| { const extra: *align(@alignOf(std.math.big.Limb)) Integer = @ptrCast(data.builder.constant_limbs.items[item.data..][0..Integer.limbs]); const limbs = data.builder.constant_limbs .items[item.data + Integer.limbs ..][0..extra.limbs_len]; const bigint = std.math.big.int.Const{ .limbs = limbs, .positive = tag == .positive_integer, }; const ExpectedContents = extern struct { string: [(64 * 8 / std.math.log2(10)) + 2]u8, limbs: [ std.math.big.int.calcToStringLimbsBufferLen( 64 / @sizeOf(std.math.big.Limb), 10, ) ]std.math.big.Limb, }; var stack align(@alignOf(ExpectedContents)) = std.heap.stackFallback(@sizeOf(ExpectedContents), data.builder.gpa); const allocator = stack.get(); const str = bigint.toStringAlloc(allocator, 10, undefined) catch return writer.writeAll("..."); defer allocator.free(str); try writer.writeAll(str); }, .half, .bfloat, => |tag| try writer.print("0x{c}{X:0>4}", .{ @as(u8, switch (tag) { .half => 'H', .bfloat => 'R', else => unreachable, }), item.data >> switch (tag) { .half => 0, .bfloat => 16, else => unreachable, } }), .float => try writer.print("0x{X:0>16}", .{ @as(u64, @bitCast(@as(f64, @as(f32, @bitCast(item.data))))), }), .double => { const extra = data.builder.constantExtraData(Double, item.data); try writer.print("0x{X:0>8}{X:0>8}", .{ extra.hi, extra.lo }); }, .fp128, .ppc_fp128, => |tag| { const extra = data.builder.constantExtraData(Fp128, item.data); try writer.print("0x{c}{X:0>8}{X:0>8}{X:0>8}{X:0>8}", .{ @as(u8, switch (tag) { .fp128 => 'L', .ppc_fp128 => 'M', else => unreachable, }), extra.lo_hi, extra.lo_lo, extra.hi_hi, extra.hi_lo, }); }, .x86_fp80 => { const extra = data.builder.constantExtraData(Fp80, item.data); try writer.print("0xK{X:0>4}{X:0>8}{X:0>8}", .{ extra.hi, extra.lo_hi, extra.lo_lo, }); }, .null, .none, .zeroinitializer, .undef, .poison, => |tag| try writer.writeAll(@tagName(tag)), .structure, .packed_structure, .array, .vector, => |tag| { const extra = data.builder.constantExtraDataTrail(Aggregate, item.data); const len = extra.data.type.aggregateLen(data.builder); const vals: []const Constant = @ptrCast(data.builder.constant_extra.items[extra.end..][0..len]); try writer.writeAll(switch (tag) { .structure => "{ ", .packed_structure => "<{ ", .array => "[", .vector => "<", else => unreachable, }); for (vals, 0..) |val, index| { if (index > 0) try writer.writeAll(", "); try writer.print("{%}", .{val.fmt(data.builder)}); } try writer.writeAll(switch (tag) { .structure => " }", .packed_structure => " }>", .array => "]", .vector => ">", else => unreachable, }); }, .splat => { const extra = data.builder.constantExtraData(Splat, item.data); const len = extra.type.vectorLen(data.builder); try writer.writeByte('<'); for (0..len) |index| { if (index > 0) try writer.writeAll(", "); try writer.print("{%}", .{extra.value.fmt(data.builder)}); } try writer.writeByte('>'); }, inline .string, .string_null, => |tag| try writer.print("c{\"" ++ switch (tag) { .string => "", .string_null => "@", else => unreachable, } ++ "}", .{@as(String, @enumFromInt(item.data)).fmt(data.builder)}), .blockaddress => |tag| { const extra = data.builder.constantExtraData(BlockAddress, item.data); const function = extra.function.ptrConst(data.builder); try writer.print("{s}({}, %{d})", .{ @tagName(tag), function.global.fmt(data.builder), @intFromEnum(extra.block), // TODO }); }, .dso_local_equivalent, .no_cfi, => |tag| { const function: Function.Index = @enumFromInt(item.data); try writer.print("{s} {}", .{ @tagName(tag), function.ptrConst(data.builder).global.fmt(data.builder), }); }, .trunc, .zext, .sext, .fptrunc, .fpext, .fptoui, .fptosi, .uitofp, .sitofp, .ptrtoint, .inttoptr, .bitcast, .addrspacecast, => |tag| { const extra = data.builder.constantExtraData(Cast, item.data); try writer.print("{s} ({%} to {%})", .{ @tagName(tag), extra.val.fmt(data.builder), extra.type.fmt(data.builder), }); }, .getelementptr, .@"getelementptr inbounds", => |tag| { const extra = data.builder.constantExtraDataTrail(GetElementPtr, item.data); const indices: []const Constant = @ptrCast(data.builder.constant_extra .items[extra.end..][0..extra.data.info.indices_len]); try writer.print("{s} ({%}, {%}", .{ @tagName(tag), extra.data.type.fmt(data.builder), extra.data.base.fmt(data.builder), }); for (indices) |index| try writer.print(", {%}", .{index.fmt(data.builder)}); try writer.writeByte(')'); }, inline .icmp, .fcmp, => |tag| { const extra = data.builder.constantExtraData(Compare, item.data); try writer.print("{s} {s} ({%}, {%})", .{ @tagName(tag), @tagName(@as(switch (tag) { .icmp => IntegerCondition, .fcmp => FloatCondition, else => unreachable, }, @enumFromInt(extra.cond))), extra.lhs.fmt(data.builder), extra.rhs.fmt(data.builder), }); }, .extractelement => |tag| { const extra = data.builder.constantExtraData(ExtractElement, item.data); try writer.print("{s} ({%}, {%})", .{ @tagName(tag), extra.val.fmt(data.builder), extra.index.fmt(data.builder), }); }, .insertelement => |tag| { const extra = data.builder.constantExtraData(InsertElement, item.data); try writer.print("{s} ({%}, {%}, {%})", .{ @tagName(tag), extra.val.fmt(data.builder), extra.elem.fmt(data.builder), extra.index.fmt(data.builder), }); }, .shufflevector => |tag| { const extra = data.builder.constantExtraData(ShuffleVector, item.data); try writer.print("{s} ({%}, {%}, {%})", .{ @tagName(tag), extra.lhs.fmt(data.builder), extra.rhs.fmt(data.builder), extra.mask.fmt(data.builder), }); }, .add, .@"add nsw", .@"add nuw", .sub, .@"sub nsw", .@"sub nuw", .mul, .@"mul nsw", .@"mul nuw", .shl, .lshr, .ashr, .@"and", .@"or", .xor, => |tag| { const extra = data.builder.constantExtraData(Binary, item.data); try writer.print("{s} ({%}, {%})", .{ @tagName(tag), extra.lhs.fmt(data.builder), extra.rhs.fmt(data.builder), }); }, } }, .global => |global| try writer.print("{}", .{global.fmt(data.builder)}), } } pub fn fmt(self: Constant, builder: *Builder) std.fmt.Formatter(format) { return .{ .data = .{ .constant = self, .builder = builder } }; } pub fn toLlvm(self: Constant, builder: *const Builder) *llvm.Value { assert(builder.useLibLlvm()); return switch (self.unwrap()) { .constant => |constant| builder.llvm.constants.items[constant], .global => |global| global.toLlvm(builder), }; } }; pub const Value = enum(u32) { none = std.math.maxInt(u31), _, const first_constant: Value = @enumFromInt(1 << 31); pub fn unwrap(self: Value) union(enum) { instruction: Function.Instruction.Index, constant: Constant, } { return if (@intFromEnum(self) < @intFromEnum(first_constant)) .{ .instruction = @enumFromInt(@intFromEnum(self)) } else .{ .constant = @enumFromInt(@intFromEnum(self) - @intFromEnum(first_constant)) }; } pub fn typeOfWip(self: Value, wip: *const WipFunction) Type { return switch (self.unwrap()) { .instruction => |instruction| instruction.typeOfWip(wip), .constant => |constant| constant.typeOf(wip.builder), }; } pub fn typeOf(self: Value, function: Function.Index, builder: *Builder) Type { return switch (self.unwrap()) { .instruction => |instruction| instruction.typeOf(function, builder), .constant => |constant| constant.typeOf(builder), }; } pub fn toConst(self: Value) ?Constant { return switch (self.unwrap()) { .instruction => null, .constant => |constant| constant, }; } const FormatData = struct { value: Value, function: Function.Index, builder: *Builder, }; fn format( data: FormatData, comptime fmt_str: []const u8, fmt_opts: std.fmt.FormatOptions, writer: anytype, ) @TypeOf(writer).Error!void { switch (data.value.unwrap()) { .instruction => |instruction| try Function.Instruction.Index.format(.{ .instruction = instruction, .function = data.function, .builder = data.builder, }, fmt_str, fmt_opts, writer), .constant => |constant| try Constant.format(.{ .constant = constant, .builder = data.builder, }, fmt_str, fmt_opts, writer), } } pub fn fmt(self: Value, function: Function.Index, builder: *Builder) std.fmt.Formatter(format) { return .{ .data = .{ .value = self, .function = function, .builder = builder } }; } pub fn toLlvm(self: Value, wip: *const WipFunction) *llvm.Value { return switch (self.unwrap()) { .instruction => |instruction| instruction.toLlvm(wip), .constant => |constant| constant.toLlvm(wip.builder), }; } }; pub const Metadata = enum(u32) { _ }; pub const InitError = error{ InvalidLlvmTriple, } || Allocator.Error; pub fn init(options: Options) InitError!Builder { var self = Builder{ .gpa = options.allocator, .use_lib_llvm = options.use_lib_llvm, .strip = options.strip, .llvm = undefined, .source_filename = .none, .data_layout = .none, .target_triple = .none, .string_map = .{}, .string_bytes = .{}, .string_indices = .{}, .types = .{}, .next_unnamed_type = @enumFromInt(0), .next_unique_type_id = .{}, .type_map = .{}, .type_items = .{}, .type_extra = .{}, .globals = .{}, .next_unnamed_global = @enumFromInt(0), .next_replaced_global = .none, .next_unique_global_id = .{}, .aliases = .{}, .variables = .{}, .functions = .{}, .constant_map = .{}, .constant_items = .{}, .constant_extra = .{}, .constant_limbs = .{}, }; if (self.useLibLlvm()) self.llvm = .{ .context = llvm.Context.create() }; errdefer self.deinit(); try self.string_indices.append(self.gpa, 0); assert(try self.string("") == .empty); 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.llvm.context, ); if (options.triple.len > 0) { self.target_triple = try self.string(options.triple); if (self.useLibLlvm()) { var error_message: [*:0]const u8 = undefined; var target: *llvm.Target = undefined; if (llvm.Target.getFromTriple( self.target_triple.toSlice(&self).?, &target, &error_message, ).toBool()) { defer llvm.disposeMessage(error_message); log.err("LLVM failed to parse '{s}': {s}", .{ self.target_triple.toSlice(&self).?, error_message, }); return InitError.InvalidLlvmTriple; } self.llvm.target = target; self.llvm.module.?.setTarget(self.target_triple.toSlice(&self).?); } } { const static_len = @typeInfo(Type).Enum.fields.len - 1; try self.type_map.ensureTotalCapacity(self.gpa, static_len); try self.type_items.ensureTotalCapacity(self.gpa, static_len); if (self.useLibLlvm()) try self.llvm.types.ensureTotalCapacity(self.gpa, static_len); inline for (@typeInfo(Type.Simple).Enum.fields) |simple_field| { const result = self.getOrPutTypeNoExtraAssumeCapacity( .{ .tag = .simple, .data = simple_field.value }, ); assert(result.new and result.type == @field(Type, simple_field.name)); if (self.useLibLlvm()) self.llvm.types.appendAssumeCapacity( @field(llvm.Context, simple_field.name ++ "Type")(self.llvm.context), ); } inline for (.{ 1, 8, 16, 29, 32, 64, 80, 128 }) |bits| assert(self.intTypeAssumeCapacity(bits) == @field(Type, std.fmt.comptimePrint("i{d}", .{bits}))); inline for (.{0}) |addr_space| assert(self.ptrTypeAssumeCapacity(@enumFromInt(addr_space)) == .ptr); } assert(try self.intConst(.i1, 0) == .false); assert(try self.intConst(.i1, 1) == .true); assert(try self.noneConst(.token) == .none); return self; } 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.types.deinit(self.gpa); self.next_unique_type_id.deinit(self.gpa); self.type_map.deinit(self.gpa); self.type_items.deinit(self.gpa); self.type_extra.deinit(self.gpa); self.globals.deinit(self.gpa); self.next_unique_global_id.deinit(self.gpa); self.aliases.deinit(self.gpa); self.variables.deinit(self.gpa); for (self.functions.items) |*function| function.deinit(self.gpa); self.functions.deinit(self.gpa); self.constant_map.deinit(self.gpa); self.constant_items.deinit(self.gpa); self.constant_extra.deinit(self.gpa); self.constant_limbs.deinit(self.gpa); if (self.useLibLlvm()) { self.llvm.constants.deinit(self.gpa); self.llvm.globals.deinit(self.gpa); self.llvm.types.deinit(self.gpa); if (self.llvm.di_builder) |di_builder| di_builder.dispose(); if (self.llvm.module) |module| module.dispose(); self.llvm.context.dispose(); } self.* = undefined; } pub fn initializeLLVMTarget(self: *const Builder, arch: std.Target.Cpu.Arch) void { if (!self.useLibLlvm()) return; switch (arch) { .aarch64, .aarch64_be, .aarch64_32 => { llvm.LLVMInitializeAArch64Target(); llvm.LLVMInitializeAArch64TargetInfo(); llvm.LLVMInitializeAArch64TargetMC(); llvm.LLVMInitializeAArch64AsmPrinter(); llvm.LLVMInitializeAArch64AsmParser(); }, .amdgcn => { llvm.LLVMInitializeAMDGPUTarget(); llvm.LLVMInitializeAMDGPUTargetInfo(); llvm.LLVMInitializeAMDGPUTargetMC(); llvm.LLVMInitializeAMDGPUAsmPrinter(); llvm.LLVMInitializeAMDGPUAsmParser(); }, .thumb, .thumbeb, .arm, .armeb => { llvm.LLVMInitializeARMTarget(); llvm.LLVMInitializeARMTargetInfo(); llvm.LLVMInitializeARMTargetMC(); llvm.LLVMInitializeARMAsmPrinter(); llvm.LLVMInitializeARMAsmParser(); }, .avr => { llvm.LLVMInitializeAVRTarget(); llvm.LLVMInitializeAVRTargetInfo(); llvm.LLVMInitializeAVRTargetMC(); llvm.LLVMInitializeAVRAsmPrinter(); llvm.LLVMInitializeAVRAsmParser(); }, .bpfel, .bpfeb => { llvm.LLVMInitializeBPFTarget(); llvm.LLVMInitializeBPFTargetInfo(); llvm.LLVMInitializeBPFTargetMC(); llvm.LLVMInitializeBPFAsmPrinter(); llvm.LLVMInitializeBPFAsmParser(); }, .hexagon => { llvm.LLVMInitializeHexagonTarget(); llvm.LLVMInitializeHexagonTargetInfo(); llvm.LLVMInitializeHexagonTargetMC(); llvm.LLVMInitializeHexagonAsmPrinter(); llvm.LLVMInitializeHexagonAsmParser(); }, .lanai => { llvm.LLVMInitializeLanaiTarget(); llvm.LLVMInitializeLanaiTargetInfo(); llvm.LLVMInitializeLanaiTargetMC(); llvm.LLVMInitializeLanaiAsmPrinter(); llvm.LLVMInitializeLanaiAsmParser(); }, .mips, .mipsel, .mips64, .mips64el => { llvm.LLVMInitializeMipsTarget(); llvm.LLVMInitializeMipsTargetInfo(); llvm.LLVMInitializeMipsTargetMC(); llvm.LLVMInitializeMipsAsmPrinter(); llvm.LLVMInitializeMipsAsmParser(); }, .msp430 => { llvm.LLVMInitializeMSP430Target(); llvm.LLVMInitializeMSP430TargetInfo(); llvm.LLVMInitializeMSP430TargetMC(); llvm.LLVMInitializeMSP430AsmPrinter(); llvm.LLVMInitializeMSP430AsmParser(); }, .nvptx, .nvptx64 => { llvm.LLVMInitializeNVPTXTarget(); llvm.LLVMInitializeNVPTXTargetInfo(); llvm.LLVMInitializeNVPTXTargetMC(); llvm.LLVMInitializeNVPTXAsmPrinter(); // There is no LLVMInitializeNVPTXAsmParser function available. }, .powerpc, .powerpcle, .powerpc64, .powerpc64le => { llvm.LLVMInitializePowerPCTarget(); llvm.LLVMInitializePowerPCTargetInfo(); llvm.LLVMInitializePowerPCTargetMC(); llvm.LLVMInitializePowerPCAsmPrinter(); llvm.LLVMInitializePowerPCAsmParser(); }, .riscv32, .riscv64 => { llvm.LLVMInitializeRISCVTarget(); llvm.LLVMInitializeRISCVTargetInfo(); llvm.LLVMInitializeRISCVTargetMC(); llvm.LLVMInitializeRISCVAsmPrinter(); llvm.LLVMInitializeRISCVAsmParser(); }, .sparc, .sparc64, .sparcel => { llvm.LLVMInitializeSparcTarget(); llvm.LLVMInitializeSparcTargetInfo(); llvm.LLVMInitializeSparcTargetMC(); llvm.LLVMInitializeSparcAsmPrinter(); llvm.LLVMInitializeSparcAsmParser(); }, .s390x => { llvm.LLVMInitializeSystemZTarget(); llvm.LLVMInitializeSystemZTargetInfo(); llvm.LLVMInitializeSystemZTargetMC(); llvm.LLVMInitializeSystemZAsmPrinter(); llvm.LLVMInitializeSystemZAsmParser(); }, .wasm32, .wasm64 => { llvm.LLVMInitializeWebAssemblyTarget(); llvm.LLVMInitializeWebAssemblyTargetInfo(); llvm.LLVMInitializeWebAssemblyTargetMC(); llvm.LLVMInitializeWebAssemblyAsmPrinter(); llvm.LLVMInitializeWebAssemblyAsmParser(); }, .x86, .x86_64 => { llvm.LLVMInitializeX86Target(); llvm.LLVMInitializeX86TargetInfo(); llvm.LLVMInitializeX86TargetMC(); llvm.LLVMInitializeX86AsmPrinter(); llvm.LLVMInitializeX86AsmParser(); }, .xtensa => { if (build_options.llvm_has_xtensa) { llvm.LLVMInitializeXtensaTarget(); llvm.LLVMInitializeXtensaTargetInfo(); llvm.LLVMInitializeXtensaTargetMC(); llvm.LLVMInitializeXtensaAsmPrinter(); llvm.LLVMInitializeXtensaAsmParser(); } }, .xcore => { llvm.LLVMInitializeXCoreTarget(); llvm.LLVMInitializeXCoreTargetInfo(); llvm.LLVMInitializeXCoreTargetMC(); llvm.LLVMInitializeXCoreAsmPrinter(); // There is no LLVMInitializeXCoreAsmParser function. }, .m68k => { if (build_options.llvm_has_m68k) { llvm.LLVMInitializeM68kTarget(); llvm.LLVMInitializeM68kTargetInfo(); llvm.LLVMInitializeM68kTargetMC(); llvm.LLVMInitializeM68kAsmPrinter(); llvm.LLVMInitializeM68kAsmParser(); } }, .csky => { if (build_options.llvm_has_csky) { llvm.LLVMInitializeCSKYTarget(); llvm.LLVMInitializeCSKYTargetInfo(); llvm.LLVMInitializeCSKYTargetMC(); // There is no LLVMInitializeCSKYAsmPrinter function. llvm.LLVMInitializeCSKYAsmParser(); } }, .ve => { llvm.LLVMInitializeVETarget(); llvm.LLVMInitializeVETargetInfo(); llvm.LLVMInitializeVETargetMC(); llvm.LLVMInitializeVEAsmPrinter(); llvm.LLVMInitializeVEAsmParser(); }, .arc => { if (build_options.llvm_has_arc) { llvm.LLVMInitializeARCTarget(); llvm.LLVMInitializeARCTargetInfo(); llvm.LLVMInitializeARCTargetMC(); llvm.LLVMInitializeARCAsmPrinter(); // There is no LLVMInitializeARCAsmParser function. } }, // LLVM backends that have no initialization functions. .tce, .tcele, .r600, .le32, .le64, .amdil, .amdil64, .hsail, .hsail64, .shave, .spir, .spir64, .kalimba, .renderscript32, .renderscript64, .dxil, .loongarch32, .loongarch64, => {}, .spu_2 => unreachable, // LLVM does not support this backend .spirv32 => unreachable, // LLVM does not support this backend .spirv64 => unreachable, // LLVM does not support this backend } } pub fn string(self: *Builder, bytes: []const u8) Allocator.Error!String { try self.string_bytes.ensureUnusedCapacity(self.gpa, bytes.len + 1); try self.string_indices.ensureUnusedCapacity(self.gpa, 1); try self.string_map.ensureUnusedCapacity(self.gpa, 1); const gop = self.string_map.getOrPutAssumeCapacityAdapted(bytes, String.Adapter{ .builder = self }); if (!gop.found_existing) { self.string_bytes.appendSliceAssumeCapacity(bytes); self.string_bytes.appendAssumeCapacity(0); self.string_indices.appendAssumeCapacity(@intCast(self.string_bytes.items.len)); } return String.fromIndex(gop.index); } pub fn stringIfExists(self: *const Builder, bytes: []const u8) ?String { return String.fromIndex( self.string_map.getIndexAdapted(bytes, String.Adapter{ .builder = self }) orelse return null, ); } pub fn fmt(self: *Builder, comptime fmt_str: []const u8, fmt_args: anytype) Allocator.Error!String { try self.string_map.ensureUnusedCapacity(self.gpa, 1); try self.string_bytes.ensureUnusedCapacity(self.gpa, std.fmt.count(fmt_str ++ .{0}, fmt_args)); try self.string_indices.ensureUnusedCapacity(self.gpa, 1); return self.fmtAssumeCapacity(fmt_str, fmt_args); } pub fn fmtAssumeCapacity(self: *Builder, comptime fmt_str: []const u8, fmt_args: anytype) String { const start = self.string_bytes.items.len; self.string_bytes.writer(self.gpa).print(fmt_str ++ .{0}, fmt_args) catch unreachable; const bytes: []const u8 = self.string_bytes.items[start .. self.string_bytes.items.len - 1]; const gop = self.string_map.getOrPutAssumeCapacityAdapted(bytes, String.Adapter{ .builder = self }); if (gop.found_existing) { self.string_bytes.shrinkRetainingCapacity(start); } else { self.string_indices.appendAssumeCapacity(@intCast(self.string_bytes.items.len)); } return String.fromIndex(gop.index); } pub fn fnType( self: *Builder, ret: Type, params: []const Type, kind: Type.Function.Kind, ) Allocator.Error!Type { try self.ensureUnusedTypeCapacity(1, Type.Function, params.len); return switch (kind) { inline else => |comptime_kind| self.fnTypeAssumeCapacity(ret, params, comptime_kind), }; } pub fn intType(self: *Builder, bits: u24) Allocator.Error!Type { try self.ensureUnusedTypeCapacity(1, NoExtra, 0); return self.intTypeAssumeCapacity(bits); } pub fn ptrType(self: *Builder, addr_space: AddrSpace) Allocator.Error!Type { try self.ensureUnusedTypeCapacity(1, NoExtra, 0); return self.ptrTypeAssumeCapacity(addr_space); } pub fn vectorType( self: *Builder, kind: Type.Vector.Kind, len: u32, child: Type, ) Allocator.Error!Type { try self.ensureUnusedTypeCapacity(1, Type.Vector, 0); return switch (kind) { inline else => |comptime_kind| self.vectorTypeAssumeCapacity(comptime_kind, len, child), }; } pub fn arrayType(self: *Builder, len: u64, child: Type) Allocator.Error!Type { comptime assert(@sizeOf(Type.Array) >= @sizeOf(Type.Vector)); try self.ensureUnusedTypeCapacity(1, Type.Array, 0); return self.arrayTypeAssumeCapacity(len, child); } pub fn structType( self: *Builder, kind: Type.Structure.Kind, fields: []const Type, ) Allocator.Error!Type { try self.ensureUnusedTypeCapacity(1, Type.Structure, fields.len); return switch (kind) { inline else => |comptime_kind| self.structTypeAssumeCapacity(comptime_kind, fields), }; } pub fn opaqueType(self: *Builder, name: String) Allocator.Error!Type { try self.string_map.ensureUnusedCapacity(self.gpa, 1); if (name.toSlice(self)) |id| try self.string_bytes.ensureUnusedCapacity(self.gpa, id.len + comptime std.fmt.count("{d}" ++ .{0}, .{std.math.maxInt(u32)})); try self.string_indices.ensureUnusedCapacity(self.gpa, 1); try self.types.ensureUnusedCapacity(self.gpa, 1); try self.next_unique_type_id.ensureUnusedCapacity(self.gpa, 1); try self.ensureUnusedTypeCapacity(1, Type.NamedStructure, 0); return self.opaqueTypeAssumeCapacity(name); } pub fn namedTypeSetBody( self: *Builder, named_type: Type, body_type: Type, ) if (build_options.have_llvm) Allocator.Error!void else void { const named_item = self.type_items.items[@intFromEnum(named_type)]; self.type_extra.items[named_item.data + std.meta.fieldIndex(Type.NamedStructure, "body").?] = @intFromEnum(body_type); if (self.useLibLlvm()) { const body_item = self.type_items.items[@intFromEnum(body_type)]; const body_extra = self.typeExtraDataTrail(Type.Structure, body_item.data); const body_fields: []const Type = @ptrCast(self.type_extra.items[body_extra.end..][0..body_extra.data.fields_len]); const llvm_fields = try self.gpa.alloc(*llvm.Type, body_fields.len); defer self.gpa.free(llvm_fields); for (llvm_fields, body_fields) |*llvm_field, body_field| llvm_field.* = body_field.toLlvm(self); self.llvm.types.items[@intFromEnum(named_type)].structSetBody( llvm_fields.ptr, @intCast(llvm_fields.len), switch (body_item.tag) { .structure => .False, .packed_structure => .True, else => unreachable, }, ); } } pub fn addGlobal(self: *Builder, name: String, global: Global) Allocator.Error!Global.Index { assert(!name.isAnon()); try self.ensureUnusedTypeCapacity(1, NoExtra, 0); try self.ensureUnusedGlobalCapacity(name); return self.addGlobalAssumeCapacity(name, global); } pub fn addGlobalAssumeCapacity(self: *Builder, name: String, global: Global) Global.Index { _ = self.ptrTypeAssumeCapacity(global.addr_space); var id = name; if (name == .empty) { id = self.next_unnamed_global; assert(id != self.next_replaced_global); self.next_unnamed_global = @enumFromInt(@intFromEnum(id) + 1); } while (true) { const global_gop = self.globals.getOrPutAssumeCapacity(id); if (!global_gop.found_existing) { global_gop.value_ptr.* = global; global_gop.value_ptr.updateAttributes(); const index: Global.Index = @enumFromInt(global_gop.index); index.updateName(self); return index; } 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.* }); unique_gop.value_ptr.* += 1; } } pub fn getGlobal(self: *const Builder, name: String) ?Global.Index { return @enumFromInt(self.globals.getIndex(name) orelse return null); } pub fn intConst(self: *Builder, ty: Type, value: anytype) Allocator.Error!Constant { var limbs: [ 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))), } ]std.math.big.Limb = undefined; return self.bigIntConst(ty, std.math.big.int.Mutable.init(&limbs, value).toConst()); } pub fn intValue(self: *Builder, ty: Type, value: anytype) Allocator.Error!Value { return (try self.intConst(ty, value)).toValue(); } pub fn bigIntConst(self: *Builder, ty: Type, value: std.math.big.int.Const) Allocator.Error!Constant { try self.constant_map.ensureUnusedCapacity(self.gpa, 1); try self.constant_items.ensureUnusedCapacity(self.gpa, 1); try self.constant_limbs.ensureUnusedCapacity(self.gpa, Constant.Integer.limbs + value.limbs.len); if (self.useLibLlvm()) try self.llvm.constants.ensureUnusedCapacity(self.gpa, 1); return self.bigIntConstAssumeCapacity(ty, value); } pub fn bigIntValue(self: *Builder, ty: Type, value: std.math.big.int.Const) Allocator.Error!Value { return (try self.bigIntConst(ty, value)).toValue(); } pub fn fpConst(self: *Builder, ty: Type, comptime val: comptime_float) Allocator.Error!Constant { return switch (ty) { .half => try self.halfConst(val), .bfloat => try self.bfloatConst(val), .float => try self.floatConst(val), .double => try self.doubleConst(val), .fp128 => try self.fp128Const(val), .x86_fp80 => try self.x86_fp80Const(val), .ppc_fp128 => try self.ppc_fp128Const(.{ val, -0.0 }), else => unreachable, }; } pub fn fpValue(self: *Builder, ty: Type, comptime value: comptime_float) Allocator.Error!Value { return (try self.fpConst(ty, value)).toValue(); } pub fn nanConst(self: *Builder, ty: Type) Allocator.Error!Constant { return switch (ty) { .half => try self.halfConst(std.math.nan(f16)), .bfloat => try self.bfloatConst(std.math.nan(f32)), .float => try self.floatConst(std.math.nan(f32)), .double => try self.doubleConst(std.math.nan(f64)), .fp128 => try self.fp128Const(std.math.nan(f128)), .x86_fp80 => try self.x86_fp80Const(std.math.nan(f80)), .ppc_fp128 => try self.ppc_fp128Const(.{std.math.nan(f64)} ** 2), else => unreachable, }; } pub fn nanValue(self: *Builder, ty: Type) Allocator.Error!Value { return (try self.nanConst(ty)).toValue(); } pub fn halfConst(self: *Builder, val: f16) Allocator.Error!Constant { try self.ensureUnusedConstantCapacity(1, NoExtra, 0); return self.halfConstAssumeCapacity(val); } pub fn halfValue(self: *Builder, ty: Type, value: f16) Allocator.Error!Value { return (try self.halfConst(ty, value)).toValue(); } pub fn bfloatConst(self: *Builder, val: f32) Allocator.Error!Constant { try self.ensureUnusedConstantCapacity(1, NoExtra, 0); return self.bfloatConstAssumeCapacity(val); } pub fn bfloatValue(self: *Builder, ty: Type, value: f32) Allocator.Error!Value { return (try self.bfloatConst(ty, value)).toValue(); } pub fn floatConst(self: *Builder, val: f32) Allocator.Error!Constant { try self.ensureUnusedConstantCapacity(1, NoExtra, 0); return self.floatConstAssumeCapacity(val); } pub fn floatValue(self: *Builder, ty: Type, value: f32) Allocator.Error!Value { return (try self.floatConst(ty, value)).toValue(); } pub fn doubleConst(self: *Builder, val: f64) Allocator.Error!Constant { try self.ensureUnusedConstantCapacity(1, Constant.Double, 0); return self.doubleConstAssumeCapacity(val); } pub fn doubleValue(self: *Builder, ty: Type, value: f64) Allocator.Error!Value { return (try self.doubleConst(ty, value)).toValue(); } pub fn fp128Const(self: *Builder, val: f128) Allocator.Error!Constant { try self.ensureUnusedConstantCapacity(1, Constant.Fp128, 0); return self.fp128ConstAssumeCapacity(val); } pub fn fp128Value(self: *Builder, ty: Type, value: f128) Allocator.Error!Value { return (try self.fp128Const(ty, value)).toValue(); } pub fn x86_fp80Const(self: *Builder, val: f80) Allocator.Error!Constant { try self.ensureUnusedConstantCapacity(1, Constant.Fp80, 0); return self.x86_fp80ConstAssumeCapacity(val); } pub fn x86_fp80Value(self: *Builder, ty: Type, value: f80) Allocator.Error!Value { return (try self.x86_fp80Const(ty, value)).toValue(); } pub fn ppc_fp128Const(self: *Builder, val: [2]f64) Allocator.Error!Constant { try self.ensureUnusedConstantCapacity(1, Constant.Fp128, 0); return self.ppc_fp128ConstAssumeCapacity(val); } pub fn ppc_fp128Value(self: *Builder, ty: Type, value: [2]f64) Allocator.Error!Value { return (try self.ppc_fp128Const(ty, value)).toValue(); } pub fn nullConst(self: *Builder, ty: Type) Allocator.Error!Constant { try self.ensureUnusedConstantCapacity(1, NoExtra, 0); return self.nullConstAssumeCapacity(ty); } pub fn nullValue(self: *Builder, ty: Type) Allocator.Error!Value { return (try self.nullConst(ty)).toValue(); } pub fn noneConst(self: *Builder, ty: Type) Allocator.Error!Constant { try self.ensureUnusedConstantCapacity(1, NoExtra, 0); return self.noneConstAssumeCapacity(ty); } pub fn noneValue(self: *Builder, ty: Type) Allocator.Error!Value { return (try self.noneConst(ty)).toValue(); } pub fn structConst(self: *Builder, ty: Type, vals: []const Constant) Allocator.Error!Constant { try self.ensureUnusedConstantCapacity(1, Constant.Aggregate, vals.len); return self.structConstAssumeCapacity(ty, vals); } pub fn structValue(self: *Builder, ty: Type, vals: []const Constant) Allocator.Error!Value { return (try self.structConst(ty, vals)).toValue(); } pub fn arrayConst(self: *Builder, ty: Type, vals: []const Constant) Allocator.Error!Constant { try self.ensureUnusedConstantCapacity(1, Constant.Aggregate, vals.len); return self.arrayConstAssumeCapacity(ty, vals); } pub fn arrayValue(self: *Builder, ty: Type, vals: []const Constant) Allocator.Error!Value { return (try self.arrayConst(ty, vals)).toValue(); } pub fn stringConst(self: *Builder, val: String) Allocator.Error!Constant { try self.ensureUnusedTypeCapacity(1, Type.Array, 0); try self.ensureUnusedConstantCapacity(1, NoExtra, 0); return self.stringConstAssumeCapacity(val); } pub fn stringValue(self: *Builder, val: String) Allocator.Error!Value { return (try self.stringConst(val)).toValue(); } pub fn stringNullConst(self: *Builder, val: String) Allocator.Error!Constant { try self.ensureUnusedTypeCapacity(1, Type.Array, 0); try self.ensureUnusedConstantCapacity(1, NoExtra, 0); return self.stringNullConstAssumeCapacity(val); } pub fn stringNullValue(self: *Builder, val: String) Allocator.Error!Value { return (try self.stringNullConst(val)).toValue(); } pub fn vectorConst(self: *Builder, ty: Type, vals: []const Constant) Allocator.Error!Constant { try self.ensureUnusedConstantCapacity(1, Constant.Aggregate, vals.len); return self.vectorConstAssumeCapacity(ty, vals); } pub fn vectorValue(self: *Builder, ty: Type, vals: []const Constant) Allocator.Error!Value { return (try self.vectorConst(ty, vals)).toValue(); } pub fn splatConst(self: *Builder, ty: Type, val: Constant) Allocator.Error!Constant { try self.ensureUnusedConstantCapacity(1, Constant.Splat, 0); return self.splatConstAssumeCapacity(ty, val); } pub fn splatValue(self: *Builder, ty: Type, val: Constant) Allocator.Error!Value { return (try self.splatConst(ty, val)).toValue(); } pub fn zeroInitConst(self: *Builder, ty: Type) Allocator.Error!Constant { try self.ensureUnusedConstantCapacity(1, Constant.Fp128, 0); try self.constant_limbs.ensureUnusedCapacity( self.gpa, Constant.Integer.limbs + comptime std.math.big.int.calcLimbLen(0), ); return self.zeroInitConstAssumeCapacity(ty); } pub fn zeroInitValue(self: *Builder, ty: Type) Allocator.Error!Value { return (try self.zeroInitConst(ty)).toValue(); } pub fn undefConst(self: *Builder, ty: Type) Allocator.Error!Constant { try self.ensureUnusedConstantCapacity(1, NoExtra, 0); return self.undefConstAssumeCapacity(ty); } pub fn undefValue(self: *Builder, ty: Type) Allocator.Error!Value { return (try self.undefConst(ty)).toValue(); } pub fn poisonConst(self: *Builder, ty: Type) Allocator.Error!Constant { try self.ensureUnusedConstantCapacity(1, NoExtra, 0); return self.poisonConstAssumeCapacity(ty); } pub fn poisonValue(self: *Builder, ty: Type) Allocator.Error!Value { return (try self.poisonConst(ty)).toValue(); } pub fn blockAddrConst( self: *Builder, function: Function.Index, block: Function.Block.Index, ) Allocator.Error!Constant { try self.ensureUnusedConstantCapacity(1, Constant.BlockAddress, 0); return self.blockAddrConstAssumeCapacity(function, block); } pub fn blockAddrValue( self: *Builder, function: Function.Index, block: Function.Block.Index, ) Allocator.Error!Value { return (try self.blockAddrConst(function, block)).toValue(); } pub fn dsoLocalEquivalentConst(self: *Builder, function: Function.Index) Allocator.Error!Constant { try self.ensureUnusedConstantCapacity(1, NoExtra, 0); return self.dsoLocalEquivalentConstAssumeCapacity(function); } pub fn dsoLocalEquivalentValue(self: *Builder, function: Function.Index) Allocator.Error!Value { return (try self.dsoLocalEquivalentConst(function)).toValue(); } pub fn noCfiConst(self: *Builder, function: Function.Index) Allocator.Error!Constant { try self.ensureUnusedConstantCapacity(1, NoExtra, 0); return self.noCfiConstAssumeCapacity(function); } pub fn noCfiValue(self: *Builder, function: Function.Index) Allocator.Error!Value { return (try self.noCfiConst(function)).toValue(); } pub fn convConst( self: *Builder, signedness: Constant.Cast.Signedness, val: Constant, ty: Type, ) Allocator.Error!Constant { try self.ensureUnusedConstantCapacity(1, Constant.Cast, 0); return self.convConstAssumeCapacity(signedness, val, ty); } pub fn convValue( self: *Builder, signedness: Constant.Cast.Signedness, val: Constant, ty: Type, ) Allocator.Error!Value { return (try self.convConst(signedness, val, ty)).toValue(); } pub fn castConst(self: *Builder, tag: Constant.Tag, val: Constant, ty: Type) Allocator.Error!Constant { try self.ensureUnusedConstantCapacity(1, Constant.Cast, 0); return self.castConstAssumeCapacity(tag, val, ty); } pub fn castValue(self: *Builder, tag: Constant.Tag, val: Constant, ty: Type) Allocator.Error!Value { return (try self.castConst(tag, val, ty)).toValue(); } pub fn gepConst( self: *Builder, comptime kind: Constant.GetElementPtr.Kind, ty: Type, base: Constant, inrange: ?u16, indices: []const Constant, ) Allocator.Error!Constant { try self.ensureUnusedTypeCapacity(1, Type.Vector, 0); try self.ensureUnusedConstantCapacity(1, Constant.GetElementPtr, indices.len); return self.gepConstAssumeCapacity(kind, ty, base, inrange, indices); } pub fn gepValue( self: *Builder, comptime kind: Constant.GetElementPtr.Kind, ty: Type, base: Constant, inrange: ?u16, indices: []const Constant, ) Allocator.Error!Value { return (try self.gepConst(kind, ty, base, inrange, indices)).toValue(); } pub fn icmpConst( self: *Builder, cond: IntegerCondition, lhs: Constant, rhs: Constant, ) Allocator.Error!Constant { try self.ensureUnusedConstantCapacity(1, Constant.Compare, 0); return self.icmpConstAssumeCapacity(cond, lhs, rhs); } pub fn icmpValue( self: *Builder, cond: IntegerCondition, lhs: Constant, rhs: Constant, ) Allocator.Error!Value { return (try self.icmpConst(cond, lhs, rhs)).toValue(); } pub fn fcmpConst( self: *Builder, cond: FloatCondition, lhs: Constant, rhs: Constant, ) Allocator.Error!Constant { try self.ensureUnusedConstantCapacity(1, Constant.Compare, 0); return self.icmpConstAssumeCapacity(cond, lhs, rhs); } pub fn fcmpValue( self: *Builder, cond: FloatCondition, lhs: Constant, rhs: Constant, ) Allocator.Error!Value { return (try self.fcmpConst(cond, lhs, rhs)).toValue(); } pub fn extractElementConst(self: *Builder, val: Constant, index: Constant) Allocator.Error!Constant { try self.ensureUnusedConstantCapacity(1, Constant.ExtractElement, 0); return self.extractElementConstAssumeCapacity(val, index); } pub fn extractElementValue(self: *Builder, val: Constant, index: Constant) Allocator.Error!Value { return (try self.extractElementConst(val, index)).toValue(); } pub fn insertElementConst( self: *Builder, val: Constant, elem: Constant, index: Constant, ) Allocator.Error!Constant { try self.ensureUnusedConstantCapacity(1, Constant.InsertElement, 0); return self.insertElementConstAssumeCapacity(val, elem, index); } pub fn insertElementValue( self: *Builder, val: Constant, elem: Constant, index: Constant, ) Allocator.Error!Value { return (try self.insertElementConst(val, elem, index)).toValue(); } pub fn shuffleVectorConst( self: *Builder, lhs: Constant, rhs: Constant, mask: Constant, ) Allocator.Error!Constant { try self.ensureUnusedTypeCapacity(1, Type.Array, 0); try self.ensureUnusedConstantCapacity(1, Constant.ShuffleVector, 0); return self.shuffleVectorConstAssumeCapacity(lhs, rhs, mask); } pub fn shuffleVectorValue( self: *Builder, lhs: Constant, rhs: Constant, mask: Constant, ) Allocator.Error!Value { return (try self.shuffleVectorConst(lhs, rhs, mask)).toValue(); } pub fn binConst( self: *Builder, tag: Constant.Tag, lhs: Constant, rhs: Constant, ) Allocator.Error!Constant { try self.ensureUnusedConstantCapacity(1, Constant.Binary, 0); return self.binConstAssumeCapacity(tag, lhs, rhs); } pub fn binValue(self: *Builder, tag: Constant.Tag, lhs: Constant, rhs: Constant) Allocator.Error!Value { return (try self.binConst(tag, lhs, rhs)).toValue(); } pub fn dump(self: *Builder, writer: anytype) (@TypeOf(writer).Error || Allocator.Error)!void { if (self.source_filename != .none) try writer.print( \\; ModuleID = '{s}' \\source_filename = {"} \\ , .{ self.source_filename.toSlice(self).?, self.source_filename.fmt(self) }); if (self.data_layout != .none) try writer.print( \\target datalayout = {"} \\ , .{self.data_layout.fmt(self)}); if (self.target_triple != .none) try writer.print( \\target triple = {"} \\ , .{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); try writer.print( \\{} ={}{}{}{}{}{}{}{} {s} {%}{ }{,} \\ , .{ variable.global.fmt(self), global.linkage, global.preemption, global.visibility, global.dll_storage_class, variable.thread_local, global.unnamed_addr, global.addr_space, global.externally_initialized, @tagName(variable.mutability), global.type.fmt(self), variable.init.fmt(self), variable.alignment, }); } try writer.writeByte('\n'); 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; try writer.print( \\{s}{}{}{}{} {} {}( , .{ if (function.instructions.len > 0) "define" else "declare", global.linkage, global.preemption, global.visibility, global.dll_storage_class, global.type.functionReturn(self).fmt(self), function.global.fmt(self), }); for (0..params_len) |arg| { if (arg > 0) try writer.writeAll(", "); try writer.print("{%}", .{function.arg(@intCast(arg)).fmt(function_index, self)}); } switch (global.type.functionKind(self)) { .normal => {}, .vararg => { if (params_len > 0) try writer.writeAll(", "); try writer.writeAll("..."); }, } try writer.print("){}{}", .{ global.unnamed_addr, function.alignment }); if (function.instructions.len > 0) { try writer.writeAll(" {\n"); for (params_len..function.instructions.len) |instruction_i| { const instruction_index: Function.Instruction.Index = @enumFromInt(instruction_i); const instruction = function.instructions.get(@intFromEnum(instruction_index)); switch (instruction.tag) { .add, .@"add nsw", .@"add nuw", .@"add nuw nsw", .@"and", .ashr, .@"ashr exact", .fadd, .@"fadd fast", .@"fcmp false", .@"fcmp fast false", .@"fcmp fast oeq", .@"fcmp fast oge", .@"fcmp fast ogt", .@"fcmp fast ole", .@"fcmp fast olt", .@"fcmp fast one", .@"fcmp fast ord", .@"fcmp fast true", .@"fcmp fast ueq", .@"fcmp fast uge", .@"fcmp fast ugt", .@"fcmp fast ule", .@"fcmp fast ult", .@"fcmp fast une", .@"fcmp fast uno", .@"fcmp oeq", .@"fcmp oge", .@"fcmp ogt", .@"fcmp ole", .@"fcmp olt", .@"fcmp one", .@"fcmp ord", .@"fcmp true", .@"fcmp ueq", .@"fcmp uge", .@"fcmp ugt", .@"fcmp ule", .@"fcmp ult", .@"fcmp une", .@"fcmp uno", .fdiv, .@"fdiv fast", .fmul, .@"fmul fast", .frem, .@"frem fast", .fsub, .@"fsub fast", .@"icmp eq", .@"icmp ne", .@"icmp sge", .@"icmp sgt", .@"icmp sle", .@"icmp slt", .@"icmp uge", .@"icmp ugt", .@"icmp ule", .@"icmp ult", .lshr, .@"lshr exact", .mul, .@"mul nsw", .@"mul nuw", .@"mul nuw nsw", .@"or", .sdiv, .@"sdiv exact", .srem, .shl, .@"shl nsw", .@"shl nuw", .@"shl nuw nsw", .sub, .@"sub nsw", .@"sub nuw", .@"sub nuw nsw", .udiv, .@"udiv exact", .urem, .xor, => |tag| { const extra = function.extraData(Function.Instruction.Binary, instruction.data); try writer.print(" %{} = {s} {%}, {}\n", .{ instruction_index.name(&function).fmt(self), @tagName(tag), extra.lhs.fmt(function_index, self), extra.rhs.fmt(function_index, self), }); }, .addrspacecast, .bitcast, .fpext, .fptosi, .fptoui, .fptrunc, .inttoptr, .ptrtoint, .sext, .sitofp, .trunc, .uitofp, .zext, => |tag| { const extra = function.extraData(Function.Instruction.Cast, instruction.data); try writer.print(" %{} = {s} {%} to {%}\n", .{ instruction_index.name(&function).fmt(self), @tagName(tag), extra.val.fmt(function_index, self), extra.type.fmt(self), }); }, .alloca, .@"alloca inalloca", => |tag| { const extra = function.extraData(Function.Instruction.Alloca, instruction.data); try writer.print(" %{} = {s} {%}{,%}{,}{,}\n", .{ instruction_index.name(&function).fmt(self), @tagName(tag), extra.type.fmt(self), extra.len.fmt(function_index, self), extra.info.alignment, extra.info.addr_space, }); }, .arg => unreachable, .block => { const name = instruction_index.name(&function); if (@intFromEnum(instruction_index) > params_len) try writer.writeByte('\n'); try writer.print("{}:\n", .{name.fmt(self)}); }, .br => |tag| { const target: Function.Block.Index = @enumFromInt(instruction.data); try writer.print(" {s} {%}\n", .{ @tagName(tag), target.toInst(&function).fmt(function_index, self), }); }, .br_cond => { const extra = function.extraData(Function.Instruction.BrCond, instruction.data); try writer.print(" br {%}, {%}, {%}\n", .{ extra.cond.fmt(function_index, self), extra.then.toInst(&function).fmt(function_index, self), extra.@"else".toInst(&function).fmt(function_index, self), }); }, .extractelement => |tag| { const extra = function.extraData(Function.Instruction.ExtractElement, instruction.data); try writer.print(" %{} = {s} {%}, {%}\n", .{ instruction_index.name(&function).fmt(self), @tagName(tag), extra.val.fmt(function_index, self), extra.index.fmt(function_index, self), }); }, .extractvalue => |tag| { const extra = function.extraDataTrail(Function.Instruction.ExtractValue, instruction.data); const indices: []const u32 = function.extra[extra.end..][0..extra.data.indices_len]; try writer.print(" %{} = {s} {%}", .{ instruction_index.name(&function).fmt(self), @tagName(tag), extra.data.val.fmt(function_index, self), }); for (indices) |index| try writer.print(", {d}", .{index}); try writer.writeByte('\n'); }, .fence => |tag| { const info: MemoryAccessInfo = @bitCast(instruction.data); try writer.print(" {s}{}{}", .{ @tagName(tag), info.scope, info.ordering }); }, .fneg, .@"fneg fast", .ret, => |tag| { const val: Value = @enumFromInt(instruction.data); try writer.print(" {s} {%}\n", .{ @tagName(tag), val.fmt(function_index, self), }); }, .getelementptr, .@"getelementptr inbounds", => |tag| { const extra = function.extraDataTrail( Function.Instruction.GetElementPtr, instruction.data, ); const indices: []const Value = @ptrCast(function.extra[extra.end..][0..extra.data.indices_len]); try writer.print(" %{} = {s} {%}, {%}", .{ instruction_index.name(&function).fmt(self), @tagName(tag), extra.data.type.fmt(self), extra.data.base.fmt(function_index, self), }); for (indices) |index| try writer.print(", {%}", .{ index.fmt(function_index, self), }); try writer.writeByte('\n'); }, .insertelement => |tag| { const extra = function.extraData(Function.Instruction.InsertElement, instruction.data); try writer.print(" %{} = {s} {%}, {%}, {%}\n", .{ instruction_index.name(&function).fmt(self), @tagName(tag), extra.val.fmt(function_index, self), extra.elem.fmt(function_index, self), extra.index.fmt(function_index, self), }); }, .insertvalue => |tag| { const extra = function.extraDataTrail(Function.Instruction.InsertValue, instruction.data); const indices: []const u32 = function.extra[extra.end..][0..extra.data.indices_len]; try writer.print(" %{} = {s} {%}, {%}", .{ instruction_index.name(&function).fmt(self), @tagName(tag), extra.data.val.fmt(function_index, self), extra.data.elem.fmt(function_index, self), }); for (indices) |index| try writer.print(", {d}", .{index}); try writer.writeByte('\n'); }, .@"llvm.maxnum.", .@"llvm.minnum.", .@"llvm.sadd.sat.", .@"llvm.smax.", .@"llvm.smin.", .@"llvm.smul.fix.sat.", .@"llvm.sshl.sat.", .@"llvm.ssub.sat.", .@"llvm.uadd.sat.", .@"llvm.umax.", .@"llvm.umin.", .@"llvm.umul.fix.sat.", .@"llvm.ushl.sat.", .@"llvm.usub.sat.", => |tag| { const extra = function.extraData(Function.Instruction.Binary, instruction.data); const ty = instruction_index.typeOf(function_index, self); try writer.print(" %{} = call {%} @{s}{m}({%}, {%})\n", .{ instruction_index.name(&function).fmt(self), ty.fmt(self), @tagName(tag), ty.fmt(self), extra.lhs.fmt(function_index, self), extra.rhs.fmt(function_index, self), }); }, .load, .@"load atomic", .@"load atomic volatile", .@"load volatile", => |tag| { const extra = function.extraData(Function.Instruction.Load, instruction.data); try writer.print(" %{} = {s} {%}, {%}{}{}{,}\n", .{ instruction_index.name(&function).fmt(self), @tagName(tag), extra.type.fmt(self), extra.ptr.fmt(function_index, self), extra.info.scope, extra.info.ordering, extra.info.alignment, }); }, .phi, .@"phi fast", => |tag| { const extra = function.extraDataTrail(Function.Instruction.Phi, instruction.data); const vals: []const Value = @ptrCast(function.extra[extra.end..][0..extra.data.incoming_len]); const blocks: []const Function.Block.Index = @ptrCast(function.extra[extra.end + extra.data.incoming_len ..][0..extra.data.incoming_len]); try writer.print(" %{} = {s} {%} ", .{ instruction_index.name(&function).fmt(self), @tagName(tag), vals[0].typeOf(function_index, self).fmt(self), }); for (0.., vals, blocks) |incoming_index, incoming_val, incoming_block| { if (incoming_index > 0) try writer.writeAll(", "); try writer.print("[ {}, {} ]", .{ incoming_val.fmt(function_index, self), incoming_block.toInst(&function).fmt(function_index, self), }); } try writer.writeByte('\n'); }, .@"ret void", .@"unreachable", => |tag| try writer.print(" {s}\n", .{@tagName(tag)}), .select, .@"select fast", => |tag| { const extra = function.extraData(Function.Instruction.Select, instruction.data); try writer.print(" %{} = {s} {%}, {%}, {%}\n", .{ instruction_index.name(&function).fmt(self), @tagName(tag), extra.cond.fmt(function_index, self), extra.lhs.fmt(function_index, self), extra.rhs.fmt(function_index, self), }); }, .shufflevector => |tag| { const extra = function.extraData(Function.Instruction.ShuffleVector, instruction.data); try writer.print(" %{} = {s} {%}, {%}, {%}\n", .{ instruction_index.name(&function).fmt(self), @tagName(tag), extra.lhs.fmt(function_index, self), extra.rhs.fmt(function_index, self), extra.mask.fmt(function_index, self), }); }, .store, .@"store atomic", .@"store atomic volatile", .@"store volatile", => |tag| { const extra = function.extraData(Function.Instruction.Store, instruction.data); try writer.print(" {s} {%}, {%}{}{}{,}\n", .{ @tagName(tag), extra.val.fmt(function_index, self), extra.ptr.fmt(function_index, self), extra.info.scope, extra.info.ordering, extra.info.alignment, }); }, .@"switch" => |tag| { const extra = function.extraDataTrail(Function.Instruction.Switch, instruction.data); const vals: []const Constant = @ptrCast(function.extra[extra.end..][0..extra.data.cases_len]); const blocks: []const Function.Block.Index = @ptrCast(function.extra[extra.end + extra.data.cases_len ..][0..extra.data.cases_len]); try writer.print(" {s} {%}, {%} [", .{ @tagName(tag), extra.data.val.fmt(function_index, self), extra.data.default.toInst(&function).fmt(function_index, self), }); for (vals, blocks) |case_val, case_block| try writer.print(" {%}, {%}\n", .{ case_val.fmt(self), case_block.toInst(&function).fmt(function_index, self), }); try writer.writeAll(" ]\n"); }, .unimplemented => |tag| { const ty: Type = @enumFromInt(instruction.data); try writer.writeAll(" "); switch (ty) { .none, .void => {}, else => try writer.print("%{} = ", .{ instruction_index.name(&function).fmt(self), }), } try writer.print("{s} {%}\n", .{ @tagName(tag), ty.fmt(self) }); }, .va_arg => |tag| { const extra = function.extraData(Function.Instruction.VaArg, instruction.data); try writer.print(" %{} = {s} {%}, {%}\n", .{ instruction_index.name(&function).fmt(self), @tagName(tag), extra.list.fmt(function_index, self), extra.type.fmt(self), }); }, } } try writer.writeByte('}'); } try writer.writeAll("\n\n"); } } pub inline fn useLibLlvm(self: *const Builder) bool { return build_options.have_llvm and self.use_lib_llvm; } const NoExtra = struct {}; fn isValidIdentifier(id: []const u8) bool { for (id, 0..) |character, index| switch (character) { '$', '-', '.', 'A'...'Z', '_', 'a'...'z' => {}, '0'...'9' => if (index == 0) return false, else => return false, }; return true; } 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| try self.string_bytes.ensureUnusedCapacity(self.gpa, id.len + comptime std.fmt.count("{d}" ++ .{0}, .{std.math.maxInt(u32)})); try self.string_indices.ensureUnusedCapacity(self.gpa, 1); try self.globals.ensureUnusedCapacity(self.gpa, 1); try self.next_unique_global_id.ensureUnusedCapacity(self.gpa, 1); } fn fnTypeAssumeCapacity( self: *Builder, ret: Type, params: []const Type, comptime kind: Type.Function.Kind, ) if (build_options.have_llvm) Allocator.Error!Type else Type { const tag: Type.Tag = switch (kind) { .normal => .function, .vararg => .vararg_function, }; const Key = struct { ret: Type, params: []const Type }; const Adapter = struct { builder: *const Builder, pub fn hash(_: @This(), key: Key) u32 { var hasher = std.hash.Wyhash.init(comptime std.hash.uint32(@intFromEnum(tag))); hasher.update(std.mem.asBytes(&key.ret)); hasher.update(std.mem.sliceAsBytes(key.params)); return @truncate(hasher.final()); } pub fn eql(ctx: @This(), lhs_key: Key, _: void, rhs_index: usize) bool { const rhs_data = ctx.builder.type_items.items[rhs_index]; const rhs_extra = ctx.builder.typeExtraDataTrail(Type.Function, rhs_data.data); const rhs_params: []const Type = @ptrCast(ctx.builder.type_extra.items[rhs_extra.end..][0..rhs_extra.data.params_len]); return rhs_data.tag == tag and lhs_key.ret == rhs_extra.data.ret and std.mem.eql(Type, lhs_key.params, rhs_params); } }; const gop = self.type_map.getOrPutAssumeCapacityAdapted( Key{ .ret = ret, .params = params }, Adapter{ .builder = self }, ); if (!gop.found_existing) { gop.key_ptr.* = {}; gop.value_ptr.* = {}; self.type_items.appendAssumeCapacity(.{ .tag = .function, .data = self.addTypeExtraAssumeCapacity(Type.Function{ .ret = ret, .params_len = @intCast(params.len), }), }); self.type_extra.appendSliceAssumeCapacity(@ptrCast(params)); if (self.useLibLlvm()) { const llvm_params = try self.gpa.alloc(*llvm.Type, params.len); defer self.gpa.free(llvm_params); for (llvm_params, params) |*llvm_param, param| llvm_param.* = param.toLlvm(self); self.llvm.types.appendAssumeCapacity(llvm.functionType( ret.toLlvm(self), llvm_params.ptr, @intCast(llvm_params.len), switch (kind) { .normal => .False, .vararg => .True, }, )); } } return @enumFromInt(gop.index); } fn intTypeAssumeCapacity(self: *Builder, bits: u24) Type { assert(bits > 0); const result = self.getOrPutTypeNoExtraAssumeCapacity(.{ .tag = .integer, .data = bits }); if (self.useLibLlvm() and result.new) self.llvm.types.appendAssumeCapacity(self.llvm.context.intType(bits)); return result.type; } fn ptrTypeAssumeCapacity(self: *Builder, addr_space: AddrSpace) Type { const result = self.getOrPutTypeNoExtraAssumeCapacity( .{ .tag = .pointer, .data = @intFromEnum(addr_space) }, ); if (self.useLibLlvm() and result.new) self.llvm.types.appendAssumeCapacity(self.llvm.context.pointerType(@intFromEnum(addr_space))); return result.type; } fn vectorTypeAssumeCapacity( self: *Builder, comptime kind: Type.Vector.Kind, len: u32, child: Type, ) Type { assert(child.isFloatingPoint() or child.isInteger(self) or child.isPointer(self)); const tag: Type.Tag = switch (kind) { .normal => .vector, .scalable => .scalable_vector, }; const Adapter = struct { builder: *const Builder, pub fn hash(_: @This(), key: Type.Vector) u32 { return @truncate(std.hash.Wyhash.hash( comptime std.hash.uint32(@intFromEnum(tag)), std.mem.asBytes(&key), )); } pub fn eql(ctx: @This(), lhs_key: Type.Vector, _: void, rhs_index: usize) bool { const rhs_data = ctx.builder.type_items.items[rhs_index]; return rhs_data.tag == tag and std.meta.eql(lhs_key, ctx.builder.typeExtraData(Type.Vector, rhs_data.data)); } }; const data = Type.Vector{ .len = len, .child = child }; const gop = self.type_map.getOrPutAssumeCapacityAdapted(data, Adapter{ .builder = self }); if (!gop.found_existing) { gop.key_ptr.* = {}; gop.value_ptr.* = {}; self.type_items.appendAssumeCapacity(.{ .tag = tag, .data = self.addTypeExtraAssumeCapacity(data), }); if (self.useLibLlvm()) self.llvm.types.appendAssumeCapacity(switch (kind) { .normal => llvm.Type.vectorType, .scalable => llvm.Type.scalableVectorType, }(child.toLlvm(self), @intCast(len))); } return @enumFromInt(gop.index); } fn arrayTypeAssumeCapacity(self: *Builder, len: u64, child: Type) Type { if (std.math.cast(u32, len)) |small_len| { const Adapter = struct { builder: *const Builder, pub fn hash(_: @This(), key: Type.Vector) u32 { return @truncate(std.hash.Wyhash.hash( comptime std.hash.uint32(@intFromEnum(Type.Tag.small_array)), std.mem.asBytes(&key), )); } pub fn eql(ctx: @This(), lhs_key: Type.Vector, _: void, rhs_index: usize) bool { const rhs_data = ctx.builder.type_items.items[rhs_index]; return rhs_data.tag == .small_array and std.meta.eql(lhs_key, ctx.builder.typeExtraData(Type.Vector, rhs_data.data)); } }; const data = Type.Vector{ .len = small_len, .child = child }; const gop = self.type_map.getOrPutAssumeCapacityAdapted(data, Adapter{ .builder = self }); if (!gop.found_existing) { gop.key_ptr.* = {}; gop.value_ptr.* = {}; self.type_items.appendAssumeCapacity(.{ .tag = .small_array, .data = self.addTypeExtraAssumeCapacity(data), }); if (self.useLibLlvm()) self.llvm.types.appendAssumeCapacity( child.toLlvm(self).arrayType(@intCast(len)), ); } return @enumFromInt(gop.index); } else { const Adapter = struct { builder: *const Builder, pub fn hash(_: @This(), key: Type.Array) u32 { return @truncate(std.hash.Wyhash.hash( comptime std.hash.uint32(@intFromEnum(Type.Tag.array)), std.mem.asBytes(&key), )); } pub fn eql(ctx: @This(), lhs_key: Type.Array, _: void, rhs_index: usize) bool { const rhs_data = ctx.builder.type_items.items[rhs_index]; return rhs_data.tag == .array and std.meta.eql(lhs_key, ctx.builder.typeExtraData(Type.Array, rhs_data.data)); } }; const data = Type.Array{ .len_lo = @truncate(len), .len_hi = @intCast(len >> 32), .child = child, }; const gop = self.type_map.getOrPutAssumeCapacityAdapted(data, Adapter{ .builder = self }); if (!gop.found_existing) { gop.key_ptr.* = {}; gop.value_ptr.* = {}; self.type_items.appendAssumeCapacity(.{ .tag = .array, .data = self.addTypeExtraAssumeCapacity(data), }); if (self.useLibLlvm()) self.llvm.types.appendAssumeCapacity( child.toLlvm(self).arrayType(@intCast(len)), ); } return @enumFromInt(gop.index); } } fn structTypeAssumeCapacity( self: *Builder, comptime kind: Type.Structure.Kind, fields: []const Type, ) if (build_options.have_llvm) Allocator.Error!Type else Type { const tag: Type.Tag = switch (kind) { .normal => .structure, .@"packed" => .packed_structure, }; const Adapter = struct { builder: *const Builder, pub fn hash(_: @This(), key: []const Type) u32 { return @truncate(std.hash.Wyhash.hash( comptime std.hash.uint32(@intFromEnum(tag)), std.mem.sliceAsBytes(key), )); } pub fn eql(ctx: @This(), lhs_key: []const Type, _: void, rhs_index: usize) bool { const rhs_data = ctx.builder.type_items.items[rhs_index]; const rhs_extra = ctx.builder.typeExtraDataTrail(Type.Structure, rhs_data.data); const rhs_fields: []const Type = @ptrCast(ctx.builder.type_extra.items[rhs_extra.end..][0..rhs_extra.data.fields_len]); return rhs_data.tag == tag and std.mem.eql(Type, lhs_key, rhs_fields); } }; const gop = self.type_map.getOrPutAssumeCapacityAdapted(fields, Adapter{ .builder = self }); if (!gop.found_existing) { gop.key_ptr.* = {}; gop.value_ptr.* = {}; self.type_items.appendAssumeCapacity(.{ .tag = tag, .data = self.addTypeExtraAssumeCapacity(Type.Structure{ .fields_len = @intCast(fields.len), }), }); self.type_extra.appendSliceAssumeCapacity(@ptrCast(fields)); if (self.useLibLlvm()) { const ExpectedContents = [expected_fields_len]*llvm.Type; var stack align(@alignOf(ExpectedContents)) = std.heap.stackFallback(@sizeOf(ExpectedContents), self.gpa); const allocator = stack.get(); const llvm_fields = try allocator.alloc(*llvm.Type, fields.len); defer allocator.free(llvm_fields); for (llvm_fields, fields) |*llvm_field, field| llvm_field.* = field.toLlvm(self); self.llvm.types.appendAssumeCapacity(self.llvm.context.structType( llvm_fields.ptr, @intCast(llvm_fields.len), switch (kind) { .normal => .False, .@"packed" => .True, }, )); } } return @enumFromInt(gop.index); } fn opaqueTypeAssumeCapacity(self: *Builder, name: String) Type { const Adapter = struct { builder: *const Builder, pub fn hash(_: @This(), key: String) u32 { return @truncate(std.hash.Wyhash.hash( comptime std.hash.uint32(@intFromEnum(Type.Tag.named_structure)), std.mem.asBytes(&key), )); } pub fn eql(ctx: @This(), lhs_key: String, _: void, rhs_index: usize) bool { const rhs_data = ctx.builder.type_items.items[rhs_index]; return rhs_data.tag == .named_structure and lhs_key == ctx.builder.typeExtraData(Type.NamedStructure, rhs_data.data).id; } }; var id = name; if (name == .empty) { id = self.next_unnamed_type; assert(id != .none); self.next_unnamed_type = @enumFromInt(@intFromEnum(id) + 1); } else assert(!name.isAnon()); while (true) { const type_gop = self.types.getOrPutAssumeCapacity(id); if (!type_gop.found_existing) { const gop = self.type_map.getOrPutAssumeCapacityAdapted(id, Adapter{ .builder = self }); assert(!gop.found_existing); gop.key_ptr.* = {}; gop.value_ptr.* = {}; self.type_items.appendAssumeCapacity(.{ .tag = .named_structure, .data = self.addTypeExtraAssumeCapacity(Type.NamedStructure{ .id = id, .body = .none, }), }); 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 ""), ); 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.* }); unique_gop.value_ptr.* += 1; } } fn ensureUnusedTypeCapacity( self: *Builder, count: usize, comptime Extra: type, trail_len: usize, ) Allocator.Error!void { try self.type_map.ensureUnusedCapacity(self.gpa, count); try self.type_items.ensureUnusedCapacity(self.gpa, count); try self.type_extra.ensureUnusedCapacity( self.gpa, count * (@typeInfo(Extra).Struct.fields.len + trail_len), ); if (self.useLibLlvm()) try self.llvm.types.ensureUnusedCapacity(self.gpa, count); } fn getOrPutTypeNoExtraAssumeCapacity(self: *Builder, item: Type.Item) struct { new: bool, type: Type } { const Adapter = struct { builder: *const Builder, pub fn hash(_: @This(), key: Type.Item) u32 { return @truncate(std.hash.Wyhash.hash( comptime std.hash.uint32(@intFromEnum(Type.Tag.simple)), std.mem.asBytes(&key), )); } pub fn eql(ctx: @This(), lhs_key: Type.Item, _: void, rhs_index: usize) bool { const lhs_bits: u32 = @bitCast(lhs_key); const rhs_bits: u32 = @bitCast(ctx.builder.type_items.items[rhs_index]); return lhs_bits == rhs_bits; } }; const gop = self.type_map.getOrPutAssumeCapacityAdapted(item, Adapter{ .builder = self }); if (!gop.found_existing) { gop.key_ptr.* = {}; gop.value_ptr.* = {}; self.type_items.appendAssumeCapacity(item); } return .{ .new = !gop.found_existing, .type = @enumFromInt(gop.index) }; } fn addTypeExtraAssumeCapacity(self: *Builder, extra: anytype) Type.Item.ExtraIndex { const result: Type.Item.ExtraIndex = @intCast(self.type_extra.items.len); inline for (@typeInfo(@TypeOf(extra)).Struct.fields) |field| { const value = @field(extra, field.name); self.type_extra.appendAssumeCapacity(switch (field.type) { u32 => value, String, Type => @intFromEnum(value), else => @compileError("bad field type: " ++ @typeName(field.type)), }); } return result; } fn typeExtraDataTrail( self: *const Builder, comptime T: type, index: Type.Item.ExtraIndex, ) struct { data: T, end: Type.Item.ExtraIndex } { var result: T = undefined; const fields = @typeInfo(T).Struct.fields; inline for (fields, self.type_extra.items[index..][0..fields.len]) |field, value| @field(result, field.name) = switch (field.type) { u32 => value, String, Type => @enumFromInt(value), else => @compileError("bad field type: " ++ @typeName(field.type)), }; return .{ .data = result, .end = index + @as(Type.Item.ExtraIndex, @intCast(fields.len)) }; } fn typeExtraData(self: *const Builder, comptime T: type, index: Type.Item.ExtraIndex) T { return self.typeExtraDataTrail(T, index).data; } fn bigIntConstAssumeCapacity( self: *Builder, ty: Type, value: std.math.big.int.Const, ) if (build_options.have_llvm) Allocator.Error!Constant else Constant { const type_item = self.type_items.items[@intFromEnum(ty)]; assert(type_item.tag == .integer); const bits = type_item.data; const ExpectedContents = extern struct { limbs: [64 / @sizeOf(std.math.big.Limb)]std.math.big.Limb, llvm_limbs: if (build_options.have_llvm) [64 / @sizeOf(u64)]u64 else void, }; var stack align(@alignOf(ExpectedContents)) = std.heap.stackFallback(@sizeOf(ExpectedContents), self.gpa); const allocator = stack.get(); var limbs: []std.math.big.Limb = &.{}; defer allocator.free(limbs); const canonical_value = if (value.fitsInTwosComp(.signed, bits)) value else canon: { assert(value.fitsInTwosComp(.unsigned, bits)); limbs = try allocator.alloc(std.math.big.Limb, std.math.big.int.calcTwosCompLimbCount(bits)); var temp_value = std.math.big.int.Mutable.init(limbs, 0); temp_value.truncate(value, .signed, bits); break :canon temp_value.toConst(); }; assert(canonical_value.fitsInTwosComp(.signed, bits)); const ExtraPtr = *align(@alignOf(std.math.big.Limb)) Constant.Integer; const Key = struct { tag: Constant.Tag, type: Type, limbs: []const std.math.big.Limb }; const tag: Constant.Tag = switch (canonical_value.positive) { true => .positive_integer, false => .negative_integer, }; const Adapter = struct { builder: *const Builder, pub fn hash(_: @This(), key: Key) u32 { var hasher = std.hash.Wyhash.init(std.hash.uint32(@intFromEnum(key.tag))); hasher.update(std.mem.asBytes(&key.type)); hasher.update(std.mem.sliceAsBytes(key.limbs)); return @truncate(hasher.final()); } pub fn eql(ctx: @This(), lhs_key: Key, _: void, rhs_index: usize) bool { if (lhs_key.tag != ctx.builder.constant_items.items(.tag)[rhs_index]) return false; const rhs_data = ctx.builder.constant_items.items(.data)[rhs_index]; const rhs_extra: ExtraPtr = @ptrCast(ctx.builder.constant_limbs.items[rhs_data..][0..Constant.Integer.limbs]); const rhs_limbs = ctx.builder.constant_limbs .items[rhs_data + Constant.Integer.limbs ..][0..rhs_extra.limbs_len]; return lhs_key.type == rhs_extra.type and std.mem.eql(std.math.big.Limb, lhs_key.limbs, rhs_limbs); } }; const gop = self.constant_map.getOrPutAssumeCapacityAdapted( Key{ .tag = tag, .type = ty, .limbs = canonical_value.limbs }, Adapter{ .builder = self }, ); if (!gop.found_existing) { gop.key_ptr.* = {}; gop.value_ptr.* = {}; self.constant_items.appendAssumeCapacity(.{ .tag = tag, .data = @intCast(self.constant_limbs.items.len), }); const extra: ExtraPtr = @ptrCast(self.constant_limbs.addManyAsArrayAssumeCapacity(Constant.Integer.limbs)); extra.* = .{ .type = ty, .limbs_len = @intCast(canonical_value.limbs.len) }; self.constant_limbs.appendSliceAssumeCapacity(canonical_value.limbs); if (self.useLibLlvm()) { const llvm_type = ty.toLlvm(self); if (canonical_value.to(c_longlong)) |small| { self.llvm.constants.appendAssumeCapacity(llvm_type.constInt(@bitCast(small), .True)); } else |_| if (canonical_value.to(c_ulonglong)) |small| { self.llvm.constants.appendAssumeCapacity(llvm_type.constInt(small, .False)); } else |_| { const llvm_limbs = try allocator.alloc(u64, std.math.divCeil( usize, if (canonical_value.positive) canonical_value.bitCountAbs() else bits, @bitSizeOf(u64), ) catch unreachable); defer allocator.free(llvm_limbs); var limb_index: usize = 0; var borrow: std.math.big.Limb = 0; for (llvm_limbs) |*result_limb| { var llvm_limb: u64 = 0; inline for (0..Constant.Integer.limbs) |shift| { const limb = if (limb_index < canonical_value.limbs.len) canonical_value.limbs[limb_index] else 0; limb_index += 1; llvm_limb |= @as(u64, limb) << shift * @bitSizeOf(std.math.big.Limb); } if (!canonical_value.positive) { const overflow = @subWithOverflow(borrow, llvm_limb); llvm_limb = overflow[0]; borrow -%= overflow[1]; assert(borrow == 0 or borrow == std.math.maxInt(u64)); } result_limb.* = llvm_limb; } self.llvm.constants.appendAssumeCapacity( llvm_type.constIntOfArbitraryPrecision(@intCast(llvm_limbs.len), llvm_limbs.ptr), ); } } } return @enumFromInt(gop.index); } fn halfConstAssumeCapacity(self: *Builder, val: f16) Constant { const result = self.getOrPutConstantNoExtraAssumeCapacity( .{ .tag = .half, .data = @as(u16, @bitCast(val)) }, ); if (self.useLibLlvm() and result.new) self.llvm.constants.appendAssumeCapacity( if (std.math.isSignalNan(val)) Type.i16.toLlvm(self).constInt(@as(u16, @bitCast(val)), .False) .constBitCast(Type.half.toLlvm(self)) else Type.half.toLlvm(self).constReal(val), ); return result.constant; } fn bfloatConstAssumeCapacity(self: *Builder, val: f32) Constant { assert(@as(u16, @truncate(@as(u32, @bitCast(val)))) == 0); const result = self.getOrPutConstantNoExtraAssumeCapacity( .{ .tag = .bfloat, .data = @bitCast(val) }, ); if (self.useLibLlvm() and result.new) self.llvm.constants.appendAssumeCapacity( if (std.math.isSignalNan(val)) Type.i16.toLlvm(self).constInt(@as(u32, @bitCast(val)) >> 16, .False) .constBitCast(Type.bfloat.toLlvm(self)) else Type.bfloat.toLlvm(self).constReal(val), ); if (self.useLibLlvm() and result.new) self.llvm.constants.appendAssumeCapacity(Type.bfloat.toLlvm(self).constReal(val)); return result.constant; } fn floatConstAssumeCapacity(self: *Builder, val: f32) Constant { const result = self.getOrPutConstantNoExtraAssumeCapacity( .{ .tag = .float, .data = @bitCast(val) }, ); if (self.useLibLlvm() and result.new) self.llvm.constants.appendAssumeCapacity( if (std.math.isSignalNan(val)) Type.i32.toLlvm(self).constInt(@as(u32, @bitCast(val)), .False) .constBitCast(Type.float.toLlvm(self)) else Type.float.toLlvm(self).constReal(val), ); return result.constant; } fn doubleConstAssumeCapacity(self: *Builder, val: f64) Constant { const Adapter = struct { builder: *const Builder, pub fn hash(_: @This(), key: f64) u32 { return @truncate(std.hash.Wyhash.hash( comptime std.hash.uint32(@intFromEnum(Constant.Tag.double)), std.mem.asBytes(&key), )); } pub fn eql(ctx: @This(), lhs_key: f64, _: void, rhs_index: usize) bool { if (ctx.builder.constant_items.items(.tag)[rhs_index] != .double) return false; const rhs_data = ctx.builder.constant_items.items(.data)[rhs_index]; const rhs_extra = ctx.builder.constantExtraData(Constant.Double, rhs_data); return @as(u64, @bitCast(lhs_key)) == @as(u64, rhs_extra.hi) << 32 | rhs_extra.lo; } }; const gop = self.constant_map.getOrPutAssumeCapacityAdapted(val, Adapter{ .builder = self }); if (!gop.found_existing) { gop.key_ptr.* = {}; gop.value_ptr.* = {}; self.constant_items.appendAssumeCapacity(.{ .tag = .double, .data = self.addConstantExtraAssumeCapacity(Constant.Double{ .lo = @truncate(@as(u64, @bitCast(val))), .hi = @intCast(@as(u64, @bitCast(val)) >> 32), }), }); if (self.useLibLlvm()) self.llvm.constants.appendAssumeCapacity( if (std.math.isSignalNan(val)) Type.i64.toLlvm(self).constInt(@as(u64, @bitCast(val)), .False) .constBitCast(Type.double.toLlvm(self)) else Type.double.toLlvm(self).constReal(val), ); } return @enumFromInt(gop.index); } fn fp128ConstAssumeCapacity(self: *Builder, val: f128) Constant { const Adapter = struct { builder: *const Builder, pub fn hash(_: @This(), key: f128) u32 { return @truncate(std.hash.Wyhash.hash( comptime std.hash.uint32(@intFromEnum(Constant.Tag.fp128)), std.mem.asBytes(&key), )); } pub fn eql(ctx: @This(), lhs_key: f128, _: void, rhs_index: usize) bool { if (ctx.builder.constant_items.items(.tag)[rhs_index] != .fp128) return false; const rhs_data = ctx.builder.constant_items.items(.data)[rhs_index]; const rhs_extra = ctx.builder.constantExtraData(Constant.Fp128, rhs_data); return @as(u128, @bitCast(lhs_key)) == @as(u128, rhs_extra.hi_hi) << 96 | @as(u128, rhs_extra.hi_lo) << 64 | @as(u128, rhs_extra.lo_hi) << 32 | rhs_extra.lo_lo; } }; const gop = self.constant_map.getOrPutAssumeCapacityAdapted(val, Adapter{ .builder = self }); if (!gop.found_existing) { gop.key_ptr.* = {}; gop.value_ptr.* = {}; self.constant_items.appendAssumeCapacity(.{ .tag = .fp128, .data = self.addConstantExtraAssumeCapacity(Constant.Fp128{ .lo_lo = @truncate(@as(u128, @bitCast(val))), .lo_hi = @truncate(@as(u128, @bitCast(val)) >> 32), .hi_lo = @truncate(@as(u128, @bitCast(val)) >> 64), .hi_hi = @intCast(@as(u128, @bitCast(val)) >> 96), }), }); if (self.useLibLlvm()) { const llvm_limbs = [_]u64{ @truncate(@as(u128, @bitCast(val))), @intCast(@as(u128, @bitCast(val)) >> 64), }; self.llvm.constants.appendAssumeCapacity( Type.i128.toLlvm(self) .constIntOfArbitraryPrecision(@intCast(llvm_limbs.len), &llvm_limbs) .constBitCast(Type.fp128.toLlvm(self)), ); } } return @enumFromInt(gop.index); } fn x86_fp80ConstAssumeCapacity(self: *Builder, val: f80) Constant { const Adapter = struct { builder: *const Builder, pub fn hash(_: @This(), key: f80) u32 { return @truncate(std.hash.Wyhash.hash( comptime std.hash.uint32(@intFromEnum(Constant.Tag.x86_fp80)), std.mem.asBytes(&key)[0..10], )); } pub fn eql(ctx: @This(), lhs_key: f80, _: void, rhs_index: usize) bool { if (ctx.builder.constant_items.items(.tag)[rhs_index] != .x86_fp80) return false; const rhs_data = ctx.builder.constant_items.items(.data)[rhs_index]; const rhs_extra = ctx.builder.constantExtraData(Constant.Fp80, rhs_data); return @as(u80, @bitCast(lhs_key)) == @as(u80, rhs_extra.hi) << 64 | @as(u80, rhs_extra.lo_hi) << 32 | rhs_extra.lo_lo; } }; const gop = self.constant_map.getOrPutAssumeCapacityAdapted(val, Adapter{ .builder = self }); if (!gop.found_existing) { gop.key_ptr.* = {}; gop.value_ptr.* = {}; self.constant_items.appendAssumeCapacity(.{ .tag = .x86_fp80, .data = self.addConstantExtraAssumeCapacity(Constant.Fp80{ .lo_lo = @truncate(@as(u80, @bitCast(val))), .lo_hi = @truncate(@as(u80, @bitCast(val)) >> 32), .hi = @intCast(@as(u80, @bitCast(val)) >> 64), }), }); if (self.useLibLlvm()) { const llvm_limbs = [_]u64{ @truncate(@as(u80, @bitCast(val))), @intCast(@as(u80, @bitCast(val)) >> 64), }; self.llvm.constants.appendAssumeCapacity( Type.i80.toLlvm(self) .constIntOfArbitraryPrecision(@intCast(llvm_limbs.len), &llvm_limbs) .constBitCast(Type.x86_fp80.toLlvm(self)), ); } } return @enumFromInt(gop.index); } fn ppc_fp128ConstAssumeCapacity(self: *Builder, val: [2]f64) Constant { const Adapter = struct { builder: *const Builder, pub fn hash(_: @This(), key: [2]f64) u32 { return @truncate(std.hash.Wyhash.hash( comptime std.hash.uint32(@intFromEnum(Constant.Tag.ppc_fp128)), std.mem.asBytes(&key), )); } pub fn eql(ctx: @This(), lhs_key: [2]f64, _: void, rhs_index: usize) bool { if (ctx.builder.constant_items.items(.tag)[rhs_index] != .ppc_fp128) return false; const rhs_data = ctx.builder.constant_items.items(.data)[rhs_index]; const rhs_extra = ctx.builder.constantExtraData(Constant.Fp128, rhs_data); return @as(u64, @bitCast(lhs_key[0])) == @as(u64, rhs_extra.lo_hi) << 32 | rhs_extra.lo_lo and @as(u64, @bitCast(lhs_key[1])) == @as(u64, rhs_extra.hi_hi) << 32 | rhs_extra.hi_lo; } }; const gop = self.constant_map.getOrPutAssumeCapacityAdapted(val, Adapter{ .builder = self }); if (!gop.found_existing) { gop.key_ptr.* = {}; gop.value_ptr.* = {}; self.constant_items.appendAssumeCapacity(.{ .tag = .ppc_fp128, .data = self.addConstantExtraAssumeCapacity(Constant.Fp128{ .lo_lo = @truncate(@as(u64, @bitCast(val[0]))), .lo_hi = @intCast(@as(u64, @bitCast(val[0])) >> 32), .hi_lo = @truncate(@as(u64, @bitCast(val[1]))), .hi_hi = @intCast(@as(u64, @bitCast(val[1])) >> 32), }), }); if (self.useLibLlvm()) { const llvm_limbs: *const [2]u64 = @ptrCast(&val); self.llvm.constants.appendAssumeCapacity( Type.i128.toLlvm(self) .constIntOfArbitraryPrecision(@intCast(llvm_limbs.len), llvm_limbs) .constBitCast(Type.ppc_fp128.toLlvm(self)), ); } } return @enumFromInt(gop.index); } fn nullConstAssumeCapacity(self: *Builder, ty: Type) Constant { assert(self.type_items.items[@intFromEnum(ty)].tag == .pointer); const result = self.getOrPutConstantNoExtraAssumeCapacity( .{ .tag = .null, .data = @intFromEnum(ty) }, ); if (self.useLibLlvm() and result.new) self.llvm.constants.appendAssumeCapacity(ty.toLlvm(self).constNull()); return result.constant; } fn noneConstAssumeCapacity(self: *Builder, ty: Type) Constant { assert(ty == .token); const result = self.getOrPutConstantNoExtraAssumeCapacity( .{ .tag = .none, .data = @intFromEnum(ty) }, ); if (self.useLibLlvm() and result.new) self.llvm.constants.appendAssumeCapacity(ty.toLlvm(self).constNull()); return result.constant; } fn structConstAssumeCapacity( self: *Builder, ty: Type, vals: []const Constant, ) if (build_options.have_llvm) Allocator.Error!Constant else Constant { const type_item = self.type_items.items[@intFromEnum(ty)]; const extra = self.typeExtraDataTrail(Type.Structure, switch (type_item.tag) { .structure, .packed_structure => type_item.data, .named_structure => data: { const body_ty = self.typeExtraData(Type.NamedStructure, type_item.data).body; const body_item = self.type_items.items[@intFromEnum(body_ty)]; switch (body_item.tag) { .structure, .packed_structure => break :data body_item.data, else => unreachable, } }, else => unreachable, }); const fields: []const Type = @ptrCast(self.type_extra.items[extra.end..][0..extra.data.fields_len]); for (fields, vals) |field, val| assert(field == val.typeOf(self)); for (vals) |val| { if (!val.isZeroInit(self)) break; } else return self.zeroInitConstAssumeCapacity(ty); const tag: Constant.Tag = switch (ty.unnamedTag(self)) { .structure => .structure, .packed_structure => .packed_structure, else => unreachable, }; const result = self.getOrPutConstantAggregateAssumeCapacity(tag, ty, vals); if (self.useLibLlvm() and result.new) { const ExpectedContents = [expected_fields_len]*llvm.Value; var stack align(@alignOf(ExpectedContents)) = std.heap.stackFallback(@sizeOf(ExpectedContents), self.gpa); const allocator = stack.get(); const llvm_vals = try allocator.alloc(*llvm.Value, vals.len); defer allocator.free(llvm_vals); for (llvm_vals, vals) |*llvm_val, val| llvm_val.* = val.toLlvm(self); self.llvm.constants.appendAssumeCapacity( ty.toLlvm(self).constNamedStruct(llvm_vals.ptr, @intCast(llvm_vals.len)), ); } return result.constant; } fn arrayConstAssumeCapacity( self: *Builder, ty: Type, vals: []const Constant, ) if (build_options.have_llvm) Allocator.Error!Constant else Constant { const type_item = self.type_items.items[@intFromEnum(ty)]; const type_extra: struct { len: u64, child: Type } = switch (type_item.tag) { inline .small_array, .array => |kind| extra: { const extra = self.typeExtraData(switch (kind) { .small_array => Type.Vector, .array => Type.Array, else => unreachable, }, type_item.data); break :extra .{ .len = extra.length(), .child = extra.child }; }, else => unreachable, }; assert(type_extra.len == vals.len); for (vals) |val| assert(type_extra.child == val.typeOf(self)); for (vals) |val| { if (!val.isZeroInit(self)) break; } else return self.zeroInitConstAssumeCapacity(ty); const result = self.getOrPutConstantAggregateAssumeCapacity(.array, ty, vals); if (self.useLibLlvm() and result.new) { const ExpectedContents = [expected_fields_len]*llvm.Value; var stack align(@alignOf(ExpectedContents)) = std.heap.stackFallback(@sizeOf(ExpectedContents), self.gpa); const allocator = stack.get(); const llvm_vals = try allocator.alloc(*llvm.Value, vals.len); defer allocator.free(llvm_vals); for (llvm_vals, vals) |*llvm_val, val| llvm_val.* = val.toLlvm(self); self.llvm.constants.appendAssumeCapacity( type_extra.child.toLlvm(self).constArray(llvm_vals.ptr, @intCast(llvm_vals.len)), ); } return result.constant; } fn stringConstAssumeCapacity(self: *Builder, val: String) Constant { const slice = val.toSlice(self).?; const ty = self.arrayTypeAssumeCapacity(slice.len, .i8); if (std.mem.allEqual(u8, slice, 0)) return self.zeroInitConstAssumeCapacity(ty); const result = self.getOrPutConstantNoExtraAssumeCapacity( .{ .tag = .string, .data = @intFromEnum(val) }, ); if (self.useLibLlvm() and result.new) self.llvm.constants.appendAssumeCapacity( self.llvm.context.constString(slice.ptr, @intCast(slice.len), .True), ); return result.constant; } fn stringNullConstAssumeCapacity(self: *Builder, val: String) Constant { const slice = val.toSlice(self).?; const ty = self.arrayTypeAssumeCapacity(slice.len + 1, .i8); if (std.mem.allEqual(u8, slice, 0)) return self.zeroInitConstAssumeCapacity(ty); const result = self.getOrPutConstantNoExtraAssumeCapacity( .{ .tag = .string_null, .data = @intFromEnum(val) }, ); if (self.useLibLlvm() and result.new) self.llvm.constants.appendAssumeCapacity( self.llvm.context.constString(slice.ptr, @intCast(slice.len + 1), .True), ); return result.constant; } fn vectorConstAssumeCapacity( self: *Builder, ty: Type, vals: []const Constant, ) if (build_options.have_llvm) Allocator.Error!Constant else Constant { assert(ty.isVector(self)); assert(ty.vectorLen(self) == vals.len); for (vals) |val| assert(ty.childType(self) == val.typeOf(self)); for (vals[1..]) |val| { if (vals[0] != val) break; } else return self.splatConstAssumeCapacity(ty, vals[0]); for (vals) |val| { if (!val.isZeroInit(self)) break; } else return self.zeroInitConstAssumeCapacity(ty); const result = self.getOrPutConstantAggregateAssumeCapacity(.vector, ty, vals); if (self.useLibLlvm() and result.new) { const ExpectedContents = [expected_fields_len]*llvm.Value; var stack align(@alignOf(ExpectedContents)) = std.heap.stackFallback(@sizeOf(ExpectedContents), self.gpa); const allocator = stack.get(); const llvm_vals = try allocator.alloc(*llvm.Value, vals.len); defer allocator.free(llvm_vals); for (llvm_vals, vals) |*llvm_val, val| llvm_val.* = val.toLlvm(self); self.llvm.constants.appendAssumeCapacity( llvm.constVector(llvm_vals.ptr, @intCast(llvm_vals.len)), ); } return result.constant; } fn splatConstAssumeCapacity( self: *Builder, ty: Type, val: Constant, ) if (build_options.have_llvm) Allocator.Error!Constant else Constant { assert(ty.scalarType(self) == val.typeOf(self)); if (!ty.isVector(self)) return val; if (val.isZeroInit(self)) return self.zeroInitConstAssumeCapacity(ty); const Adapter = struct { builder: *const Builder, pub fn hash(_: @This(), key: Constant.Splat) u32 { return @truncate(std.hash.Wyhash.hash( comptime std.hash.uint32(@intFromEnum(Constant.Tag.splat)), std.mem.asBytes(&key), )); } pub fn eql(ctx: @This(), lhs_key: Constant.Splat, _: void, rhs_index: usize) bool { if (ctx.builder.constant_items.items(.tag)[rhs_index] != .splat) return false; const rhs_data = ctx.builder.constant_items.items(.data)[rhs_index]; const rhs_extra = ctx.builder.constantExtraData(Constant.Splat, rhs_data); return std.meta.eql(lhs_key, rhs_extra); } }; const data = Constant.Splat{ .type = ty, .value = val }; const gop = self.constant_map.getOrPutAssumeCapacityAdapted(data, Adapter{ .builder = self }); if (!gop.found_existing) { gop.key_ptr.* = {}; gop.value_ptr.* = {}; self.constant_items.appendAssumeCapacity(.{ .tag = .splat, .data = self.addConstantExtraAssumeCapacity(data), }); if (self.useLibLlvm()) { const ExpectedContents = [expected_fields_len]*llvm.Value; var stack align(@alignOf(ExpectedContents)) = std.heap.stackFallback(@sizeOf(ExpectedContents), self.gpa); const allocator = stack.get(); const llvm_vals = try allocator.alloc(*llvm.Value, ty.vectorLen(self)); defer allocator.free(llvm_vals); @memset(llvm_vals, val.toLlvm(self)); self.llvm.constants.appendAssumeCapacity( llvm.constVector(llvm_vals.ptr, @intCast(llvm_vals.len)), ); } } return @enumFromInt(gop.index); } fn zeroInitConstAssumeCapacity(self: *Builder, ty: Type) Constant { switch (ty) { inline .half, .bfloat, .float, .double, .fp128, .x86_fp80, => |tag| return @field(Builder, @tagName(tag) ++ "ConstAssumeCapacity")(self, 0.0), .ppc_fp128 => return self.ppc_fp128ConstAssumeCapacity(.{ 0.0, 0.0 }), .token => return .none, .i1 => return .false, else => switch (self.type_items.items[@intFromEnum(ty)].tag) { .simple, .function, .vararg_function, => unreachable, .integer => { var limbs: [std.math.big.int.calcLimbLen(0)]std.math.big.Limb = undefined; const bigint = std.math.big.int.Mutable.init(&limbs, 0); return self.bigIntConstAssumeCapacity(ty, bigint.toConst()) catch unreachable; }, .pointer => return self.nullConstAssumeCapacity(ty), .target, .vector, .scalable_vector, .small_array, .array, .structure, .packed_structure, .named_structure, => {}, }, } const result = self.getOrPutConstantNoExtraAssumeCapacity( .{ .tag = .zeroinitializer, .data = @intFromEnum(ty) }, ); if (self.useLibLlvm() and result.new) self.llvm.constants.appendAssumeCapacity(ty.toLlvm(self).constNull()); return result.constant; } fn undefConstAssumeCapacity(self: *Builder, ty: Type) Constant { switch (self.type_items.items[@intFromEnum(ty)].tag) { .simple => switch (ty) { .void, .label => unreachable, else => {}, }, .function, .vararg_function => unreachable, else => {}, } const result = self.getOrPutConstantNoExtraAssumeCapacity( .{ .tag = .undef, .data = @intFromEnum(ty) }, ); if (self.useLibLlvm() and result.new) self.llvm.constants.appendAssumeCapacity(ty.toLlvm(self).getUndef()); return result.constant; } fn poisonConstAssumeCapacity(self: *Builder, ty: Type) Constant { switch (self.type_items.items[@intFromEnum(ty)].tag) { .simple => switch (ty) { .void, .label => unreachable, else => {}, }, .function, .vararg_function => unreachable, else => {}, } const result = self.getOrPutConstantNoExtraAssumeCapacity( .{ .tag = .poison, .data = @intFromEnum(ty) }, ); if (self.useLibLlvm() and result.new) self.llvm.constants.appendAssumeCapacity(ty.toLlvm(self).getPoison()); return result.constant; } fn blockAddrConstAssumeCapacity( self: *Builder, function: Function.Index, block: Function.Block.Index, ) Constant { const Adapter = struct { builder: *const Builder, pub fn hash(_: @This(), key: Constant.BlockAddress) u32 { return @truncate(std.hash.Wyhash.hash( comptime std.hash.uint32(@intFromEnum(Constant.Tag.blockaddress)), std.mem.asBytes(&key), )); } pub fn eql(ctx: @This(), lhs_key: Constant.BlockAddress, _: void, rhs_index: usize) bool { if (ctx.builder.constant_items.items(.tag)[rhs_index] != .blockaddress) return false; const rhs_data = ctx.builder.constant_items.items(.data)[rhs_index]; const rhs_extra = ctx.builder.constantExtraData(Constant.BlockAddress, rhs_data); return std.meta.eql(lhs_key, rhs_extra); } }; const data = Constant.BlockAddress{ .function = function, .block = block }; const gop = self.constant_map.getOrPutAssumeCapacityAdapted(data, Adapter{ .builder = self }); if (!gop.found_existing) { gop.key_ptr.* = {}; gop.value_ptr.* = {}; self.constant_items.appendAssumeCapacity(.{ .tag = .blockaddress, .data = self.addConstantExtraAssumeCapacity(data), }); if (self.useLibLlvm()) self.llvm.constants.appendAssumeCapacity( function.toLlvm(self).blockAddress(block.toValue(self, function).toLlvm(self, function)), ); } return @enumFromInt(gop.index); } fn dsoLocalEquivalentConstAssumeCapacity(self: *Builder, function: Function.Index) Constant { const result = self.getOrPutConstantNoExtraAssumeCapacity( .{ .tag = .dso_local_equivalent, .data = @intFromEnum(function) }, ); if (self.useLibLlvm() and result.new) self.llvm.constants.appendAssumeCapacity(undefined); return result.constant; } fn noCfiConstAssumeCapacity(self: *Builder, function: Function.Index) Constant { const result = self.getOrPutConstantNoExtraAssumeCapacity( .{ .tag = .no_cfi, .data = @intFromEnum(function) }, ); if (self.useLibLlvm() and result.new) self.llvm.constants.appendAssumeCapacity(undefined); return result.constant; } fn convTag( self: *Builder, comptime Tag: type, signedness: Constant.Cast.Signedness, val_ty: Type, ty: Type, ) Tag { assert(val_ty != ty); return switch (val_ty.scalarTag(self)) { .simple => switch (ty.scalarTag(self)) { .simple => switch (std.math.order(val_ty.scalarBits(self), ty.scalarBits(self))) { .lt => .fpext, .eq => unreachable, .gt => .fptrunc, }, .integer => switch (signedness) { .unsigned => .fptoui, .signed => .fptosi, .unneeded => unreachable, }, else => unreachable, }, .integer => switch (ty.scalarTag(self)) { .simple => switch (signedness) { .unsigned => .uitofp, .signed => .sitofp, .unneeded => unreachable, }, .integer => switch (std.math.order(val_ty.scalarBits(self), ty.scalarBits(self))) { .lt => switch (signedness) { .unsigned => .zext, .signed => .sext, .unneeded => unreachable, }, .eq => unreachable, .gt => .trunc, }, .pointer => .inttoptr, else => unreachable, }, .pointer => switch (ty.scalarTag(self)) { .integer => .ptrtoint, .pointer => .addrspacecast, else => unreachable, }, else => unreachable, }; } fn convConstAssumeCapacity( self: *Builder, signedness: Constant.Cast.Signedness, val: Constant, ty: Type, ) Constant { const val_ty = val.typeOf(self); if (val_ty == ty) return val; return self.castConstAssumeCapacity(self.convTag(Constant.Tag, signedness, val_ty, ty), val, ty); } fn castConstAssumeCapacity(self: *Builder, tag: Constant.Tag, val: Constant, ty: Type) Constant { const Key = struct { tag: Constant.Tag, cast: Constant.Cast }; const Adapter = struct { builder: *const Builder, pub fn hash(_: @This(), key: Key) u32 { return @truncate(std.hash.Wyhash.hash( std.hash.uint32(@intFromEnum(key.tag)), std.mem.asBytes(&key.cast), )); } pub fn eql(ctx: @This(), lhs_key: Key, _: void, rhs_index: usize) bool { if (lhs_key.tag != ctx.builder.constant_items.items(.tag)[rhs_index]) return false; const rhs_data = ctx.builder.constant_items.items(.data)[rhs_index]; const rhs_extra = ctx.builder.constantExtraData(Constant.Cast, rhs_data); return std.meta.eql(lhs_key.cast, rhs_extra); } }; const data = Key{ .tag = tag, .cast = .{ .val = val, .type = ty } }; const gop = self.constant_map.getOrPutAssumeCapacityAdapted(data, Adapter{ .builder = self }); if (!gop.found_existing) { gop.key_ptr.* = {}; gop.value_ptr.* = {}; self.constant_items.appendAssumeCapacity(.{ .tag = tag, .data = self.addConstantExtraAssumeCapacity(data.cast), }); if (self.useLibLlvm()) self.llvm.constants.appendAssumeCapacity(switch (tag) { .trunc => &llvm.Value.constTrunc, .zext => &llvm.Value.constZExt, .sext => &llvm.Value.constSExt, .fptrunc => &llvm.Value.constFPTrunc, .fpext => &llvm.Value.constFPExt, .fptoui => &llvm.Value.constFPToUI, .fptosi => &llvm.Value.constFPToSI, .uitofp => &llvm.Value.constUIToFP, .sitofp => &llvm.Value.constSIToFP, .ptrtoint => &llvm.Value.constPtrToInt, .inttoptr => &llvm.Value.constIntToPtr, .bitcast => &llvm.Value.constBitCast, else => unreachable, }(val.toLlvm(self), ty.toLlvm(self))); } return @enumFromInt(gop.index); } fn gepConstAssumeCapacity( self: *Builder, comptime kind: Constant.GetElementPtr.Kind, ty: Type, base: Constant, inrange: ?u16, indices: []const Constant, ) if (build_options.have_llvm) Allocator.Error!Constant else Constant { const tag: Constant.Tag = switch (kind) { .normal => .getelementptr, .inbounds => .@"getelementptr inbounds", }; const base_ty = base.typeOf(self); const base_is_vector = base_ty.isVector(self); const VectorInfo = struct { kind: Type.Vector.Kind, len: u32, fn init(vector_ty: Type, builder: *const Builder) @This() { return .{ .kind = vector_ty.vectorKind(builder), .len = vector_ty.vectorLen(builder) }; } }; var vector_info: ?VectorInfo = if (base_is_vector) VectorInfo.init(base_ty, self) else null; for (indices) |index| { const index_ty = index.typeOf(self); switch (index_ty.tag(self)) { .integer => {}, .vector, .scalable_vector => { const index_info = VectorInfo.init(index_ty, self); if (vector_info) |info| assert(std.meta.eql(info, index_info)) else vector_info = index_info; }, else => unreachable, } } if (!base_is_vector) if (vector_info) |info| switch (info.kind) { inline else => |vector_kind| _ = self.vectorTypeAssumeCapacity(vector_kind, info.len, base_ty), }; const Key = struct { type: Type, base: Constant, inrange: Constant.GetElementPtr.InRangeIndex, indices: []const Constant, }; const Adapter = struct { builder: *const Builder, pub fn hash(_: @This(), key: Key) u32 { var hasher = std.hash.Wyhash.init(comptime std.hash.uint32(@intFromEnum(tag))); hasher.update(std.mem.asBytes(&key.type)); hasher.update(std.mem.asBytes(&key.base)); hasher.update(std.mem.asBytes(&key.inrange)); hasher.update(std.mem.sliceAsBytes(key.indices)); return @truncate(hasher.final()); } pub fn eql(ctx: @This(), lhs_key: Key, _: void, rhs_index: usize) bool { if (ctx.builder.constant_items.items(.tag)[rhs_index] != tag) return false; const rhs_data = ctx.builder.constant_items.items(.data)[rhs_index]; const rhs_extra = ctx.builder.constantExtraDataTrail(Constant.GetElementPtr, rhs_data); const rhs_indices: []const Constant = @ptrCast(ctx.builder.constant_extra .items[rhs_extra.end..][0..rhs_extra.data.info.indices_len]); return lhs_key.type == rhs_extra.data.type and lhs_key.base == rhs_extra.data.base and lhs_key.inrange == rhs_extra.data.info.inrange and std.mem.eql(Constant, lhs_key.indices, rhs_indices); } }; const data = Key{ .type = ty, .base = base, .inrange = if (inrange) |index| @enumFromInt(index) else .none, .indices = indices, }; const gop = self.constant_map.getOrPutAssumeCapacityAdapted(data, Adapter{ .builder = self }); if (!gop.found_existing) { gop.key_ptr.* = {}; gop.value_ptr.* = {}; self.constant_items.appendAssumeCapacity(.{ .tag = tag, .data = self.addConstantExtraAssumeCapacity(Constant.GetElementPtr{ .type = ty, .base = base, .info = .{ .indices_len = @intCast(indices.len), .inrange = data.inrange }, }), }); self.constant_extra.appendSliceAssumeCapacity(@ptrCast(indices)); if (self.useLibLlvm()) { const ExpectedContents = [expected_gep_indices_len]*llvm.Value; var stack align(@alignOf(ExpectedContents)) = std.heap.stackFallback(@sizeOf(ExpectedContents), self.gpa); const allocator = stack.get(); const llvm_indices = try allocator.alloc(*llvm.Value, indices.len); defer allocator.free(llvm_indices); for (llvm_indices, indices) |*llvm_index, index| llvm_index.* = index.toLlvm(self); self.llvm.constants.appendAssumeCapacity(switch (kind) { .normal => llvm.Type.constGEP, .inbounds => llvm.Type.constInBoundsGEP, }(ty.toLlvm(self), base.toLlvm(self), llvm_indices.ptr, @intCast(llvm_indices.len))); } } return @enumFromInt(gop.index); } fn icmpConstAssumeCapacity( self: *Builder, cond: IntegerCondition, lhs: Constant, rhs: Constant, ) Constant { const Adapter = struct { builder: *const Builder, pub fn hash(_: @This(), key: Constant.Compare) u32 { return @truncate(std.hash.Wyhash.hash( std.hash.uint32(@intFromEnum(Constant.tag.icmp)), std.mem.asBytes(&key), )); } pub fn eql(ctx: @This(), lhs_key: Constant.Compare, _: void, rhs_index: usize) bool { if (ctx.builder.constant_items.items(.tag)[rhs_index] != .icmp) return false; const rhs_data = ctx.builder.constant_items.items(.data)[rhs_index]; const rhs_extra = ctx.builder.constantExtraData(Constant.Compare, rhs_data); return std.meta.eql(lhs_key, rhs_extra); } }; const data = Constant.Compare{ .cond = @intFromEnum(cond), .lhs = lhs, .rhs = rhs }; const gop = self.constant_map.getOrPutAssumeCapacityAdapted(data, Adapter{ .builder = self }); if (!gop.found_existing) { gop.key_ptr.* = {}; gop.value_ptr.* = {}; self.constant_items.appendAssumeCapacity(.{ .tag = .icmp, .data = self.addConstantExtraAssumeCapacity(data), }); if (self.useLibLlvm()) self.llvm.constants.appendAssumeCapacity( llvm.constICmp(@enumFromInt(@intFromEnum(cond)), lhs.toLlvm(self), rhs.toLlvm(self)), ); } return @enumFromInt(gop.index); } fn fcmpConstAssumeCapacity( self: *Builder, cond: FloatCondition, lhs: Constant, rhs: Constant, ) Constant { const Adapter = struct { builder: *const Builder, pub fn hash(_: @This(), key: Constant.Compare) u32 { return @truncate(std.hash.Wyhash.hash( std.hash.uint32(@intFromEnum(Constant.tag.fcmp)), std.mem.asBytes(&key), )); } pub fn eql(ctx: @This(), lhs_key: Constant.Compare, _: void, rhs_index: usize) bool { if (ctx.builder.constant_items.items(.tag)[rhs_index] != .fcmp) return false; const rhs_data = ctx.builder.constant_items.items(.data)[rhs_index]; const rhs_extra = ctx.builder.constantExtraData(Constant.Compare, rhs_data); return std.meta.eql(lhs_key, rhs_extra); } }; const data = Constant.Compare{ .cond = @intFromEnum(cond), .lhs = lhs, .rhs = rhs }; const gop = self.constant_map.getOrPutAssumeCapacityAdapted(data, Adapter{ .builder = self }); if (!gop.found_existing) { gop.key_ptr.* = {}; gop.value_ptr.* = {}; self.constant_items.appendAssumeCapacity(.{ .tag = .fcmp, .data = self.addConstantExtraAssumeCapacity(data), }); if (self.useLibLlvm()) self.llvm.constants.appendAssumeCapacity( llvm.constFCmp(@enumFromInt(@intFromEnum(cond)), lhs.toLlvm(self), rhs.toLlvm(self)), ); } return @enumFromInt(gop.index); } fn extractElementConstAssumeCapacity( self: *Builder, val: Constant, index: Constant, ) Constant { const Adapter = struct { builder: *const Builder, pub fn hash(_: @This(), key: Constant.ExtractElement) u32 { return @truncate(std.hash.Wyhash.hash( comptime std.hash.uint32(@intFromEnum(Constant.Tag.extractelement)), std.mem.asBytes(&key), )); } pub fn eql(ctx: @This(), lhs_key: Constant.ExtractElement, _: void, rhs_index: usize) bool { if (ctx.builder.constant_items.items(.tag)[rhs_index] != .extractelement) return false; const rhs_data = ctx.builder.constant_items.items(.data)[rhs_index]; const rhs_extra = ctx.builder.constantExtraData(Constant.ExtractElement, rhs_data); return std.meta.eql(lhs_key, rhs_extra); } }; const data = Constant.ExtractElement{ .val = val, .index = index }; const gop = self.constant_map.getOrPutAssumeCapacityAdapted(data, Adapter{ .builder = self }); if (!gop.found_existing) { gop.key_ptr.* = {}; gop.value_ptr.* = {}; self.constant_items.appendAssumeCapacity(.{ .tag = .extractelement, .data = self.addConstantExtraAssumeCapacity(data), }); if (self.useLibLlvm()) self.llvm.constants.appendAssumeCapacity( val.toLlvm(self).constExtractElement(index.toLlvm(self)), ); } return @enumFromInt(gop.index); } fn insertElementConstAssumeCapacity( self: *Builder, val: Constant, elem: Constant, index: Constant, ) Constant { const Adapter = struct { builder: *const Builder, pub fn hash(_: @This(), key: Constant.InsertElement) u32 { return @truncate(std.hash.Wyhash.hash( comptime std.hash.uint32(@intFromEnum(Constant.Tag.insertelement)), std.mem.asBytes(&key), )); } pub fn eql(ctx: @This(), lhs_key: Constant.InsertElement, _: void, rhs_index: usize) bool { if (ctx.builder.constant_items.items(.tag)[rhs_index] != .insertelement) return false; const rhs_data = ctx.builder.constant_items.items(.data)[rhs_index]; const rhs_extra = ctx.builder.constantExtraData(Constant.InsertElement, rhs_data); return std.meta.eql(lhs_key, rhs_extra); } }; const data = Constant.InsertElement{ .val = val, .elem = elem, .index = index }; const gop = self.constant_map.getOrPutAssumeCapacityAdapted(data, Adapter{ .builder = self }); if (!gop.found_existing) { gop.key_ptr.* = {}; gop.value_ptr.* = {}; self.constant_items.appendAssumeCapacity(.{ .tag = .insertelement, .data = self.addConstantExtraAssumeCapacity(data), }); if (self.useLibLlvm()) self.llvm.constants.appendAssumeCapacity( val.toLlvm(self).constInsertElement(elem.toLlvm(self), index.toLlvm(self)), ); } return @enumFromInt(gop.index); } fn shuffleVectorConstAssumeCapacity( self: *Builder, lhs: Constant, rhs: Constant, mask: Constant, ) Constant { assert(lhs.typeOf(self).isVector(self.builder)); assert(lhs.typeOf(self) == rhs.typeOf(self)); assert(mask.typeOf(self).scalarType(self).isInteger(self)); _ = lhs.typeOf(self).changeLengthAssumeCapacity(mask.typeOf(self).vectorLen(self), self); const Adapter = struct { builder: *const Builder, pub fn hash(_: @This(), key: Constant.ShuffleVector) u32 { return @truncate(std.hash.Wyhash.hash( comptime std.hash.uint32(@intFromEnum(Constant.Tag.shufflevector)), std.mem.asBytes(&key), )); } pub fn eql(ctx: @This(), lhs_key: Constant.ShuffleVector, _: void, rhs_index: usize) bool { if (ctx.builder.constant_items.items(.tag)[rhs_index] != .shufflevector) return false; const rhs_data = ctx.builder.constant_items.items(.data)[rhs_index]; const rhs_extra = ctx.builder.constantExtraData(Constant.ShuffleVector, rhs_data); return std.meta.eql(lhs_key, rhs_extra); } }; const data = Constant.ShuffleVector{ .lhs = lhs, .rhs = rhs, .mask = mask }; const gop = self.constant_map.getOrPutAssumeCapacityAdapted(data, Adapter{ .builder = self }); if (!gop.found_existing) { gop.key_ptr.* = {}; gop.value_ptr.* = {}; self.constant_items.appendAssumeCapacity(.{ .tag = .shufflevector, .data = self.addConstantExtraAssumeCapacity(data), }); if (self.useLibLlvm()) self.llvm.constants.appendAssumeCapacity( lhs.toLlvm(self).constShuffleVector(rhs.toLlvm(self), mask.toLlvm(self)), ); } return @enumFromInt(gop.index); } fn binConstAssumeCapacity( self: *Builder, tag: Constant.Tag, lhs: Constant, rhs: Constant, ) Constant { switch (tag) { .add, .@"add nsw", .@"add nuw", .sub, .@"sub nsw", .@"sub nuw", .mul, .@"mul nsw", .@"mul nuw", .shl, .lshr, .ashr, .@"and", .@"or", .xor, => {}, else => unreachable, } const Key = struct { tag: Constant.Tag, bin: Constant.Binary }; const Adapter = struct { builder: *const Builder, pub fn hash(_: @This(), key: Key) u32 { return @truncate(std.hash.Wyhash.hash( std.hash.uint32(@intFromEnum(key.tag)), std.mem.asBytes(&key.bin), )); } pub fn eql(ctx: @This(), lhs_key: Key, _: void, rhs_index: usize) bool { if (lhs_key.tag != ctx.builder.constant_items.items(.tag)[rhs_index]) return false; const rhs_data = ctx.builder.constant_items.items(.data)[rhs_index]; const rhs_extra = ctx.builder.constantExtraData(Constant.Binary, rhs_data); return std.meta.eql(lhs_key.bin, rhs_extra); } }; const data = Key{ .tag = tag, .bin = .{ .lhs = lhs, .rhs = rhs } }; const gop = self.constant_map.getOrPutAssumeCapacityAdapted(data, Adapter{ .builder = self }); if (!gop.found_existing) { gop.key_ptr.* = {}; gop.value_ptr.* = {}; self.constant_items.appendAssumeCapacity(.{ .tag = tag, .data = self.addConstantExtraAssumeCapacity(data.bin), }); if (self.useLibLlvm()) self.llvm.constants.appendAssumeCapacity(switch (tag) { .add => &llvm.Value.constAdd, .sub => &llvm.Value.constSub, .mul => &llvm.Value.constMul, .shl => &llvm.Value.constShl, .lshr => &llvm.Value.constLShr, .ashr => &llvm.Value.constAShr, .@"and" => &llvm.Value.constAnd, .@"or" => &llvm.Value.constOr, .xor => &llvm.Value.constXor, else => unreachable, }(lhs.toLlvm(self), rhs.toLlvm(self))); } return @enumFromInt(gop.index); } fn ensureUnusedConstantCapacity( self: *Builder, count: usize, comptime Extra: type, trail_len: usize, ) Allocator.Error!void { try self.constant_map.ensureUnusedCapacity(self.gpa, count); try self.constant_items.ensureUnusedCapacity(self.gpa, count); try self.constant_extra.ensureUnusedCapacity( self.gpa, count * (@typeInfo(Extra).Struct.fields.len + trail_len), ); if (self.useLibLlvm()) try self.llvm.constants.ensureUnusedCapacity(self.gpa, count); } fn getOrPutConstantNoExtraAssumeCapacity( self: *Builder, item: Constant.Item, ) struct { new: bool, constant: Constant } { const Adapter = struct { builder: *const Builder, pub fn hash(_: @This(), key: Constant.Item) u32 { return @truncate(std.hash.Wyhash.hash( std.hash.uint32(@intFromEnum(key.tag)), std.mem.asBytes(&key.data), )); } pub fn eql(ctx: @This(), lhs_key: Constant.Item, _: void, rhs_index: usize) bool { return std.meta.eql(lhs_key, ctx.builder.constant_items.get(rhs_index)); } }; const gop = self.constant_map.getOrPutAssumeCapacityAdapted(item, Adapter{ .builder = self }); if (!gop.found_existing) { gop.key_ptr.* = {}; gop.value_ptr.* = {}; self.constant_items.appendAssumeCapacity(item); } return .{ .new = !gop.found_existing, .constant = @enumFromInt(gop.index) }; } fn getOrPutConstantAggregateAssumeCapacity( self: *Builder, tag: Constant.Tag, ty: Type, vals: []const Constant, ) struct { new: bool, constant: Constant } { switch (tag) { .structure, .packed_structure, .array, .vector => {}, else => unreachable, } const Key = struct { tag: Constant.Tag, type: Type, vals: []const Constant }; const Adapter = struct { builder: *const Builder, pub fn hash(_: @This(), key: Key) u32 { var hasher = std.hash.Wyhash.init(std.hash.uint32(@intFromEnum(key.tag))); hasher.update(std.mem.asBytes(&key.type)); hasher.update(std.mem.sliceAsBytes(key.vals)); return @truncate(hasher.final()); } pub fn eql(ctx: @This(), lhs_key: Key, _: void, rhs_index: usize) bool { if (lhs_key.tag != ctx.builder.constant_items.items(.tag)[rhs_index]) return false; const rhs_data = ctx.builder.constant_items.items(.data)[rhs_index]; const rhs_extra = ctx.builder.constantExtraDataTrail(Constant.Aggregate, rhs_data); if (lhs_key.type != rhs_extra.data.type) return false; const rhs_vals: []const Constant = @ptrCast(ctx.builder.constant_extra.items[rhs_extra.end..][0..lhs_key.vals.len]); return std.mem.eql(Constant, lhs_key.vals, rhs_vals); } }; const gop = self.constant_map.getOrPutAssumeCapacityAdapted( Key{ .tag = tag, .type = ty, .vals = vals }, Adapter{ .builder = self }, ); if (!gop.found_existing) { gop.key_ptr.* = {}; gop.value_ptr.* = {}; self.constant_items.appendAssumeCapacity(.{ .tag = tag, .data = self.addConstantExtraAssumeCapacity(Constant.Aggregate{ .type = ty }), }); self.constant_extra.appendSliceAssumeCapacity(@ptrCast(vals)); } return .{ .new = !gop.found_existing, .constant = @enumFromInt(gop.index) }; } fn addConstantExtraAssumeCapacity(self: *Builder, extra: anytype) Constant.Item.ExtraIndex { const result: Constant.Item.ExtraIndex = @intCast(self.constant_extra.items.len); inline for (@typeInfo(@TypeOf(extra)).Struct.fields) |field| { const value = @field(extra, field.name); self.constant_extra.appendAssumeCapacity(switch (field.type) { u32 => value, Type, Constant, Function.Index, Function.Block.Index => @intFromEnum(value), Constant.GetElementPtr.Info => @bitCast(value), else => @compileError("bad field type: " ++ @typeName(field.type)), }); } return result; } fn constantExtraDataTrail( self: *const Builder, comptime T: type, index: Constant.Item.ExtraIndex, ) struct { data: T, end: Constant.Item.ExtraIndex } { var result: T = undefined; const fields = @typeInfo(T).Struct.fields; inline for (fields, self.constant_extra.items[index..][0..fields.len]) |field, value| @field(result, field.name) = switch (field.type) { u32 => value, Type, Constant, Function.Index, Function.Block.Index => @enumFromInt(value), Constant.GetElementPtr.Info => @bitCast(value), else => @compileError("bad field type: " ++ @typeName(field.type)), }; return .{ .data = result, .end = index + @as(Constant.Item.ExtraIndex, @intCast(fields.len)) }; } fn constantExtraData(self: *const Builder, comptime T: type, index: Constant.Item.ExtraIndex) T { return self.constantExtraDataTrail(T, index).data; } const assert = std.debug.assert; const build_options = @import("build_options"); const builtin = @import("builtin"); const llvm = if (build_options.have_llvm) @import("bindings.zig") else @compileError("LLVM unavailable"); const log = std.log.scoped(.llvm); const std = @import("std"); const Allocator = std.mem.Allocator; const Builder = @This();