const std = @import("std"); const cstr = std.cstr; const mem = std.mem; const Allocator = mem.Allocator; const assert = std.debug.assert; const autoHash = std.hash.autoHash; const Target = std.Target; const Module = @import("../../Module.zig"); const Type = @import("../../type.zig").Type; pub const CType = extern union { /// If the tag value is less than Tag.no_payload_count, then no pointer /// dereference is needed. tag_if_small_enough: Tag, ptr_otherwise: *const Payload, pub fn initTag(small_tag: Tag) CType { assert(!small_tag.hasPayload()); return .{ .tag_if_small_enough = small_tag }; } pub fn initPayload(pl: anytype) CType { const T = @typeInfo(@TypeOf(pl)).Pointer.child; return switch (pl.base.tag) { inline else => |t| if (comptime t.hasPayload() and t.Type() == T) .{ .ptr_otherwise = &pl.base, } else unreachable, }; } pub fn hasPayload(self: CType) bool { return self.tag_if_small_enough.hasPayload(); } pub fn tag(self: CType) Tag { return if (self.hasPayload()) self.ptr_otherwise.tag else self.tag_if_small_enough; } pub fn cast(self: CType, comptime T: type) ?*const T { if (!self.hasPayload()) return null; const pl = self.ptr_otherwise; return switch (pl.tag) { inline else => |t| if (comptime t.hasPayload() and t.Type() == T) @fieldParentPtr(T, "base", pl) else null, }; } pub fn castTag(self: CType, comptime t: Tag) ?*const t.Type() { return if (self.tag() == t) @fieldParentPtr(t.Type(), "base", self.ptr_otherwise) else null; } pub const Tag = enum(usize) { // The first section of this enum are tags that require no payload. void, // C basic types char, @"signed char", short, int, long, @"long long", _Bool, @"unsigned char", @"unsigned short", @"unsigned int", @"unsigned long", @"unsigned long long", float, double, @"long double", // C header types // - stdbool.h bool, // - stddef.h size_t, ptrdiff_t, // - stdint.h uint8_t, int8_t, uint16_t, int16_t, uint32_t, int32_t, uint64_t, int64_t, uintptr_t, intptr_t, // zig.h types zig_u128, zig_i128, zig_f16, zig_f32, zig_f64, zig_f80, zig_f128, zig_c_longdouble, // Keep last_no_payload_tag updated! // After this, the tag requires a payload. pointer, pointer_const, pointer_volatile, pointer_const_volatile, array, vector, fwd_anon_struct, fwd_anon_union, fwd_struct, fwd_union, unnamed_struct, unnamed_union, packed_unnamed_struct, packed_unnamed_union, anon_struct, anon_union, @"struct", @"union", packed_struct, packed_union, function, varargs_function, pub const last_no_payload_tag = Tag.zig_c_longdouble; pub const no_payload_count = @enumToInt(last_no_payload_tag) + 1; pub fn hasPayload(self: Tag) bool { return @enumToInt(self) >= no_payload_count; } pub fn toIndex(self: Tag) Index { assert(!self.hasPayload()); return @intCast(Index, @enumToInt(self)); } pub fn Type(comptime self: Tag) type { return switch (self) { .void, .char, .@"signed char", .short, .int, .long, .@"long long", ._Bool, .@"unsigned char", .@"unsigned short", .@"unsigned int", .@"unsigned long", .@"unsigned long long", .float, .double, .@"long double", .bool, .size_t, .ptrdiff_t, .uint8_t, .int8_t, .uint16_t, .int16_t, .uint32_t, .int32_t, .uint64_t, .int64_t, .uintptr_t, .intptr_t, .zig_u128, .zig_i128, .zig_f16, .zig_f32, .zig_f64, .zig_f80, .zig_f128, .zig_c_longdouble, => @compileError("Type Tag " ++ @tagName(self) ++ " has no payload"), .pointer, .pointer_const, .pointer_volatile, .pointer_const_volatile, => Payload.Child, .array, .vector, => Payload.Sequence, .fwd_anon_struct, .fwd_anon_union, => Payload.Fields, .fwd_struct, .fwd_union, => Payload.FwdDecl, .unnamed_struct, .unnamed_union, .packed_unnamed_struct, .packed_unnamed_union, => Payload.Unnamed, .anon_struct, .anon_union, .@"struct", .@"union", .packed_struct, .packed_union, => Payload.Aggregate, .function, .varargs_function, => Payload.Function, }; } }; pub const Payload = struct { tag: Tag, pub const Child = struct { base: Payload, data: Index, }; pub const Sequence = struct { base: Payload, data: struct { len: u64, elem_type: Index, }, }; pub const FwdDecl = struct { base: Payload, data: Module.Decl.Index, }; pub const Fields = struct { base: Payload, data: Data, pub const Data = []const Field; pub const Field = struct { name: [*:0]const u8, type: Index, alignas: AlignAs, }; }; pub const Unnamed = struct { base: Payload, data: struct { fields: Fields.Data, owner_decl: Module.Decl.Index, id: u32, }, }; pub const Aggregate = struct { base: Payload, data: struct { fields: Fields.Data, fwd_decl: Index, }, }; pub const Function = struct { base: Payload, data: struct { return_type: Index, param_types: []const Index, }, }; }; pub const AlignAs = struct { @"align": std.math.Log2Int(u32), abi: std.math.Log2Int(u32), pub fn init(alignment: u32, abi_alignment: u32) AlignAs { const actual_align = if (alignment != 0) alignment else abi_alignment; assert(std.math.isPowerOfTwo(actual_align)); assert(std.math.isPowerOfTwo(abi_alignment)); return .{ .@"align" = std.math.log2_int(u32, actual_align), .abi = std.math.log2_int(u32, abi_alignment), }; } pub fn abiAlign(ty: Type, target: Target) AlignAs { const abi_align = ty.abiAlignment(target); return init(abi_align, abi_align); } pub fn fieldAlign(struct_ty: Type, field_i: usize, target: Target) AlignAs { return init( struct_ty.structFieldAlign(field_i, target), struct_ty.structFieldType(field_i).abiAlignment(target), ); } pub fn unionPayloadAlign(union_ty: Type, target: Target) AlignAs { const union_obj = union_ty.cast(Type.Payload.Union).?.data; const union_payload_align = union_obj.abiAlignment(target, false); return init(union_payload_align, union_payload_align); } pub fn getAlign(self: AlignAs) u32 { return @as(u32, 1) << self.@"align"; } }; pub const Index = u32; pub const Store = struct { arena: std.heap.ArenaAllocator.State = .{}, set: Set = .{}, pub const Set = struct { pub const Map = std.ArrayHashMapUnmanaged(CType, void, HashContext, true); const HashContext = struct { store: *const Set, pub fn hash(self: @This(), cty: CType) Map.Hash { return @truncate(Map.Hash, cty.hash(self.store.*)); } pub fn eql(_: @This(), lhs: CType, rhs: CType, _: usize) bool { return lhs.eql(rhs); } }; map: Map = .{}, pub fn indexToCType(self: Set, index: Index) CType { if (index < Tag.no_payload_count) return initTag(@intToEnum(Tag, index)); return self.map.keys()[index - Tag.no_payload_count]; } pub fn indexToHash(self: Set, index: Index) Map.Hash { if (index < Tag.no_payload_count) return (HashContext{ .store = &self }).hash(self.indexToCType(index)); return self.map.entries.items(.hash)[index - Tag.no_payload_count]; } pub fn typeToIndex(self: Set, ty: Type, target: Target, kind: Kind) ?Index { const lookup = Convert.Lookup{ .imm = .{ .set = &self, .target = target } }; var convert: Convert = undefined; convert.initType(ty, kind, lookup) catch unreachable; const t = convert.tag(); if (!t.hasPayload()) return t.toIndex(); return if (self.map.getIndexAdapted( ty, TypeAdapter32{ .kind = kind, .lookup = lookup, .convert = &convert }, )) |idx| @intCast(Index, Tag.no_payload_count + idx) else null; } }; pub const Promoted = struct { arena: std.heap.ArenaAllocator, set: Set, pub fn gpa(self: *Promoted) Allocator { return self.arena.child_allocator; } pub fn cTypeToIndex(self: *Promoted, cty: CType) Allocator.Error!Index { const t = cty.tag(); if (@enumToInt(t) < Tag.no_payload_count) return @intCast(Index, @enumToInt(t)); const gop = try self.set.map.getOrPutContext(self.gpa(), cty, .{ .store = &self.set }); if (!gop.found_existing) gop.key_ptr.* = cty; if (std.debug.runtime_safety) { const key = &self.set.map.entries.items(.key)[gop.index]; assert(key == gop.key_ptr); assert(cty.eql(key.*)); assert(cty.hash(self.set) == key.hash(self.set)); } return @intCast(Index, Tag.no_payload_count + gop.index); } pub fn typeToIndex( self: *Promoted, ty: Type, mod: *Module, kind: Kind, ) Allocator.Error!Index { const lookup = Convert.Lookup{ .mut = .{ .promoted = self, .mod = mod } }; var convert: Convert = undefined; try convert.initType(ty, kind, lookup); const t = convert.tag(); if (!t.hasPayload()) return t.toIndex(); const gop = try self.set.map.getOrPutContextAdapted( self.gpa(), ty, TypeAdapter32{ .kind = kind, .lookup = lookup.freeze(), .convert = &convert }, .{ .store = &self.set }, ); if (!gop.found_existing) { errdefer _ = self.set.map.pop(); gop.key_ptr.* = try createFromConvert(self, ty, lookup.getTarget(), kind, convert); } if (std.debug.runtime_safety) { const adapter = TypeAdapter64{ .kind = kind, .lookup = lookup.freeze(), .convert = &convert, }; const cty = &self.set.map.entries.items(.key)[gop.index]; assert(cty == gop.key_ptr); assert(adapter.eql(ty, cty.*)); assert(adapter.hash(ty) == cty.hash(self.set)); } return @intCast(Index, Tag.no_payload_count + gop.index); } }; pub fn promote(self: Store, gpa: Allocator) Promoted { return .{ .arena = self.arena.promote(gpa), .set = self.set }; } pub fn demote(self: *Store, promoted: Promoted) void { self.arena = promoted.arena.state; self.set = promoted.set; } pub fn indexToCType(self: Store, index: Index) CType { return self.set.indexToCType(index); } pub fn indexToHash(self: Store, index: Index) Set.Map.Hash { return self.set.indexToHash(index); } pub fn cTypeToIndex(self: *Store, gpa: Allocator, cty: CType) !Index { var promoted = self.promote(gpa); defer self.demote(promoted); return promoted.cTypeToIndex(cty); } pub fn typeToCType(self: *Store, gpa: Allocator, ty: Type, mod: *Module, kind: Kind) !CType { const idx = try self.typeToIndex(gpa, ty, mod, kind); return self.indexToCType(idx); } pub fn typeToIndex(self: *Store, gpa: Allocator, ty: Type, mod: *Module, kind: Kind) !Index { var promoted = self.promote(gpa); defer self.demote(promoted); return promoted.typeToIndex(ty, mod, kind); } pub fn clearRetainingCapacity(self: *Store, gpa: Allocator) void { var promoted = self.promote(gpa); defer self.demote(promoted); promoted.set.map.clearRetainingCapacity(); _ = promoted.arena.reset(.retain_capacity); } pub fn clearAndFree(self: *Store, gpa: Allocator) void { var promoted = self.promote(gpa); defer self.demote(promoted); promoted.set.map.clearAndFree(gpa); _ = promoted.arena.reset(.free_all); } pub fn shrinkRetainingCapacity(self: *Store, gpa: Allocator, new_len: usize) void { self.set.map.shrinkRetainingCapacity(gpa, new_len); } pub fn shrinkAndFree(self: *Store, gpa: Allocator, new_len: usize) void { self.set.map.shrinkAndFree(gpa, new_len); } pub fn count(self: Store) usize { return self.set.map.count(); } pub fn move(self: *Store) Store { const moved = self.*; self.* = .{}; return moved; } pub fn deinit(self: *Store, gpa: Allocator) void { var promoted = self.promote(gpa); promoted.set.map.deinit(gpa); _ = promoted.arena.deinit(); self.* = undefined; } }; pub fn isBool(self: CType) bool { return switch (self.tag()) { ._Bool, .bool, => true, else => false, }; } pub fn isInteger(self: CType) bool { return switch (self.tag()) { .char, .@"signed char", .short, .int, .long, .@"long long", .@"unsigned char", .@"unsigned short", .@"unsigned int", .@"unsigned long", .@"unsigned long long", .size_t, .ptrdiff_t, .uint8_t, .int8_t, .uint16_t, .int16_t, .uint32_t, .int32_t, .uint64_t, .int64_t, .uintptr_t, .intptr_t, .zig_u128, .zig_i128, => true, else => false, }; } pub fn signedness(self: CType) ?std.builtin.Signedness { return switch (self.tag()) { .char => null, // unknown signedness .@"signed char", .short, .int, .long, .@"long long", .ptrdiff_t, .int8_t, .int16_t, .int32_t, .int64_t, .intptr_t, .zig_i128, => .signed, .@"unsigned char", .@"unsigned short", .@"unsigned int", .@"unsigned long", .@"unsigned long long", .size_t, .uint8_t, .uint16_t, .uint32_t, .uint64_t, .uintptr_t, .zig_u128, => .unsigned, else => unreachable, }; } pub fn isFloat(self: CType) bool { return switch (self.tag()) { .float, .double, .@"long double", .zig_f16, .zig_f32, .zig_f64, .zig_f80, .zig_f128, .zig_c_longdouble, => true, else => false, }; } pub fn isPointer(self: CType) bool { return switch (self.tag()) { .pointer, .pointer_const, .pointer_volatile, .pointer_const_volatile, => true, else => false, }; } pub fn isFunction(self: CType) bool { return switch (self.tag()) { .function, .varargs_function, => true, else => false, }; } pub fn toSigned(self: CType) CType { return CType.initTag(switch (self.tag()) { .char, .@"signed char", .@"unsigned char" => .@"signed char", .short, .@"unsigned short" => .short, .int, .@"unsigned int" => .int, .long, .@"unsigned long" => .long, .@"long long", .@"unsigned long long" => .@"long long", .size_t, .ptrdiff_t => .ptrdiff_t, .uint8_t, .int8_t => .int8_t, .uint16_t, .int16_t => .int16_t, .uint32_t, .int32_t => .int32_t, .uint64_t, .int64_t => .int64_t, .uintptr_t, .intptr_t => .intptr_t, .zig_u128, .zig_i128 => .zig_i128, .float, .double, .@"long double", .zig_f16, .zig_f32, .zig_f80, .zig_f128, .zig_c_longdouble, => |t| t, else => unreachable, }); } pub fn toUnsigned(self: CType) CType { return CType.initTag(switch (self.tag()) { .char, .@"signed char", .@"unsigned char" => .@"unsigned char", .short, .@"unsigned short" => .@"unsigned short", .int, .@"unsigned int" => .@"unsigned int", .long, .@"unsigned long" => .@"unsigned long", .@"long long", .@"unsigned long long" => .@"unsigned long long", .size_t, .ptrdiff_t => .size_t, .uint8_t, .int8_t => .uint8_t, .uint16_t, .int16_t => .uint16_t, .uint32_t, .int32_t => .uint32_t, .uint64_t, .int64_t => .uint64_t, .uintptr_t, .intptr_t => .uintptr_t, .zig_u128, .zig_i128 => .zig_u128, else => unreachable, }); } pub fn toSignedness(self: CType, s: std.builtin.Signedness) CType { return switch (s) { .unsigned => self.toUnsigned(), .signed => self.toSigned(), }; } pub fn getStandardDefineAbbrev(self: CType) ?[]const u8 { return switch (self.tag()) { .char => "CHAR", .@"signed char" => "SCHAR", .short => "SHRT", .int => "INT", .long => "LONG", .@"long long" => "LLONG", .@"unsigned char" => "UCHAR", .@"unsigned short" => "USHRT", .@"unsigned int" => "UINT", .@"unsigned long" => "ULONG", .@"unsigned long long" => "ULLONG", .float => "FLT", .double => "DBL", .@"long double" => "LDBL", .size_t => "SIZE", .ptrdiff_t => "PTRDIFF", .uint8_t => "UINT8", .int8_t => "INT8", .uint16_t => "UINT16", .int16_t => "INT16", .uint32_t => "UINT32", .int32_t => "INT32", .uint64_t => "UINT64", .int64_t => "INT64", .uintptr_t => "UINTPTR", .intptr_t => "INTPTR", else => null, }; } pub fn renderLiteralPrefix(self: CType, writer: anytype, kind: Kind) @TypeOf(writer).Error!void { switch (self.tag()) { .void => unreachable, ._Bool, .char, .@"signed char", .short, .@"unsigned short", .bool, .size_t, .ptrdiff_t, .uintptr_t, .intptr_t, => |t| switch (kind) { else => try writer.print("({s})", .{@tagName(t)}), .global => {}, }, .int, .long, .@"long long", .@"unsigned char", .@"unsigned int", .@"unsigned long", .@"unsigned long long", .float, .double, .@"long double", => {}, .uint8_t, .int8_t, .uint16_t, .int16_t, .uint32_t, .int32_t, .uint64_t, .int64_t, => try writer.print("{s}_C(", .{self.getStandardDefineAbbrev().?}), .zig_u128, .zig_i128, .zig_f16, .zig_f32, .zig_f64, .zig_f80, .zig_f128, .zig_c_longdouble, => |t| try writer.print("zig_{s}_{s}(", .{ switch (kind) { else => "make", .global => "init", }, @tagName(t)["zig_".len..], }), .pointer, .pointer_const, .pointer_volatile, .pointer_const_volatile, => unreachable, .array, .vector, => try writer.writeByte('{'), .fwd_anon_struct, .fwd_anon_union, .fwd_struct, .fwd_union, .unnamed_struct, .unnamed_union, .packed_unnamed_struct, .packed_unnamed_union, .anon_struct, .anon_union, .@"struct", .@"union", .packed_struct, .packed_union, .function, .varargs_function, => unreachable, } } pub fn renderLiteralSuffix(self: CType, writer: anytype) @TypeOf(writer).Error!void { switch (self.tag()) { .void => unreachable, ._Bool => {}, .char, .@"signed char", .short, .int, => {}, .long => try writer.writeByte('l'), .@"long long" => try writer.writeAll("ll"), .@"unsigned char", .@"unsigned short", .@"unsigned int", => try writer.writeByte('u'), .@"unsigned long", .size_t, .uintptr_t, => try writer.writeAll("ul"), .@"unsigned long long" => try writer.writeAll("ull"), .float => try writer.writeByte('f'), .double => {}, .@"long double" => try writer.writeByte('l'), .bool, .ptrdiff_t, .intptr_t, => {}, .uint8_t, .int8_t, .uint16_t, .int16_t, .uint32_t, .int32_t, .uint64_t, .int64_t, .zig_u128, .zig_i128, .zig_f16, .zig_f32, .zig_f64, .zig_f80, .zig_f128, .zig_c_longdouble, => try writer.writeByte(')'), .pointer, .pointer_const, .pointer_volatile, .pointer_const_volatile, => unreachable, .array, .vector, => try writer.writeByte('}'), .fwd_anon_struct, .fwd_anon_union, .fwd_struct, .fwd_union, .unnamed_struct, .unnamed_union, .packed_unnamed_struct, .packed_unnamed_union, .anon_struct, .anon_union, .@"struct", .@"union", .packed_struct, .packed_union, .function, .varargs_function, => unreachable, } } pub fn floatActiveBits(self: CType, target: Target) u16 { return switch (self.tag()) { .float => target.c_type_bit_size(.float), .double => target.c_type_bit_size(.double), .@"long double", .zig_c_longdouble => target.c_type_bit_size(.longdouble), .zig_f16 => 16, .zig_f32 => 32, .zig_f64 => 64, .zig_f80 => 80, .zig_f128 => 128, else => unreachable, }; } pub fn byteSize(self: CType, store: Store.Set, target: Target) u64 { return switch (self.tag()) { .void => 0, .char, .@"signed char", ._Bool, .@"unsigned char", .bool, .uint8_t, .int8_t => 1, .short => target.c_type_byte_size(.short), .int => target.c_type_byte_size(.int), .long => target.c_type_byte_size(.long), .@"long long" => target.c_type_byte_size(.longlong), .@"unsigned short" => target.c_type_byte_size(.ushort), .@"unsigned int" => target.c_type_byte_size(.uint), .@"unsigned long" => target.c_type_byte_size(.ulong), .@"unsigned long long" => target.c_type_byte_size(.ulonglong), .float => target.c_type_byte_size(.float), .double => target.c_type_byte_size(.double), .@"long double" => target.c_type_byte_size(.longdouble), .size_t, .ptrdiff_t, .uintptr_t, .intptr_t, .pointer, .pointer_const, .pointer_volatile, .pointer_const_volatile, => @divExact(target.cpu.arch.ptrBitWidth(), 8), .uint16_t, .int16_t, .zig_f16 => 2, .uint32_t, .int32_t, .zig_f32 => 4, .uint64_t, .int64_t, .zig_f64 => 8, .zig_u128, .zig_i128, .zig_f128 => 16, .zig_f80 => if (target.c_type_bit_size(.longdouble) == 80) target.c_type_byte_size(.longdouble) else 16, .zig_c_longdouble => target.c_type_byte_size(.longdouble), .array, .vector, => { const data = self.cast(Payload.Sequence).?.data; return data.len * store.indexToCType(data.elem_type).byteSize(store, target); }, .fwd_anon_struct, .fwd_anon_union, .fwd_struct, .fwd_union, .unnamed_struct, .unnamed_union, .packed_unnamed_struct, .packed_unnamed_union, .anon_struct, .anon_union, .@"struct", .@"union", .packed_struct, .packed_union, .function, .varargs_function, => unreachable, }; } pub fn isPacked(self: CType) bool { return switch (self.tag()) { else => false, .packed_unnamed_struct, .packed_unnamed_union, .packed_struct, .packed_union, => true, }; } pub fn fields(self: CType) Payload.Fields.Data { return if (self.cast(Payload.Aggregate)) |pl| pl.data.fields else if (self.cast(Payload.Unnamed)) |pl| pl.data.fields else if (self.cast(Payload.Fields)) |pl| pl.data else unreachable; } pub fn eql(lhs: CType, rhs: CType) bool { return lhs.eqlContext(rhs, struct { pub fn eqlIndex(_: @This(), lhs_idx: Index, rhs_idx: Index) bool { return lhs_idx == rhs_idx; } }{}); } pub fn eqlContext(lhs: CType, rhs: CType, ctx: anytype) bool { // As a shortcut, if the small tags / addresses match, we're done. if (lhs.tag_if_small_enough == rhs.tag_if_small_enough) return true; const lhs_tag = lhs.tag(); const rhs_tag = rhs.tag(); if (lhs_tag != rhs_tag) return false; return switch (lhs_tag) { .void, .char, .@"signed char", .short, .int, .long, .@"long long", ._Bool, .@"unsigned char", .@"unsigned short", .@"unsigned int", .@"unsigned long", .@"unsigned long long", .float, .double, .@"long double", .bool, .size_t, .ptrdiff_t, .uint8_t, .int8_t, .uint16_t, .int16_t, .uint32_t, .int32_t, .uint64_t, .int64_t, .uintptr_t, .intptr_t, .zig_u128, .zig_i128, .zig_f16, .zig_f32, .zig_f64, .zig_f80, .zig_f128, .zig_c_longdouble, => false, .pointer, .pointer_const, .pointer_volatile, .pointer_const_volatile, => ctx.eqlIndex(lhs.cast(Payload.Child).?.data, rhs.cast(Payload.Child).?.data), .array, .vector, => { const lhs_data = lhs.cast(Payload.Sequence).?.data; const rhs_data = rhs.cast(Payload.Sequence).?.data; return lhs_data.len == rhs_data.len and ctx.eqlIndex(lhs_data.elem_type, rhs_data.elem_type); }, .fwd_anon_struct, .fwd_anon_union, => { const lhs_data = lhs.cast(Payload.Fields).?.data; const rhs_data = rhs.cast(Payload.Fields).?.data; if (lhs_data.len != rhs_data.len) return false; for (lhs_data, rhs_data) |lhs_field, rhs_field| { if (!ctx.eqlIndex(lhs_field.type, rhs_field.type)) return false; if (lhs_field.alignas.@"align" != rhs_field.alignas.@"align") return false; if (cstr.cmp(lhs_field.name, rhs_field.name) != 0) return false; } return true; }, .fwd_struct, .fwd_union, => lhs.cast(Payload.FwdDecl).?.data == rhs.cast(Payload.FwdDecl).?.data, .unnamed_struct, .unnamed_union, .packed_unnamed_struct, .packed_unnamed_union, => { const lhs_data = lhs.cast(Payload.Unnamed).?.data; const rhs_data = rhs.cast(Payload.Unnamed).?.data; return lhs_data.owner_decl == rhs_data.owner_decl and lhs_data.id == rhs_data.id; }, .anon_struct, .anon_union, .@"struct", .@"union", .packed_struct, .packed_union, => ctx.eqlIndex( lhs.cast(Payload.Aggregate).?.data.fwd_decl, rhs.cast(Payload.Aggregate).?.data.fwd_decl, ), .function, .varargs_function, => { const lhs_data = lhs.cast(Payload.Function).?.data; const rhs_data = rhs.cast(Payload.Function).?.data; if (lhs_data.param_types.len != rhs_data.param_types.len) return false; if (!ctx.eqlIndex(lhs_data.return_type, rhs_data.return_type)) return false; for (lhs_data.param_types, rhs_data.param_types) |lhs_param_idx, rhs_param_idx| { if (!ctx.eqlIndex(lhs_param_idx, rhs_param_idx)) return false; } return true; }, }; } pub fn hash(self: CType, store: Store.Set) u64 { var hasher = std.hash.Wyhash.init(0); self.updateHasher(&hasher, store); return hasher.final(); } pub fn updateHasher(self: CType, hasher: anytype, store: Store.Set) void { const t = self.tag(); autoHash(hasher, t); switch (t) { .void, .char, .@"signed char", .short, .int, .long, .@"long long", ._Bool, .@"unsigned char", .@"unsigned short", .@"unsigned int", .@"unsigned long", .@"unsigned long long", .float, .double, .@"long double", .bool, .size_t, .ptrdiff_t, .uint8_t, .int8_t, .uint16_t, .int16_t, .uint32_t, .int32_t, .uint64_t, .int64_t, .uintptr_t, .intptr_t, .zig_u128, .zig_i128, .zig_f16, .zig_f32, .zig_f64, .zig_f80, .zig_f128, .zig_c_longdouble, => {}, .pointer, .pointer_const, .pointer_volatile, .pointer_const_volatile, => store.indexToCType(self.cast(Payload.Child).?.data).updateHasher(hasher, store), .array, .vector, => { const data = self.cast(Payload.Sequence).?.data; autoHash(hasher, data.len); store.indexToCType(data.elem_type).updateHasher(hasher, store); }, .fwd_anon_struct, .fwd_anon_union, => for (self.cast(Payload.Fields).?.data) |field| { store.indexToCType(field.type).updateHasher(hasher, store); hasher.update(mem.span(field.name)); autoHash(hasher, field.alignas.@"align"); }, .fwd_struct, .fwd_union, => autoHash(hasher, self.cast(Payload.FwdDecl).?.data), .unnamed_struct, .unnamed_union, .packed_unnamed_struct, .packed_unnamed_union, => { const data = self.cast(Payload.Unnamed).?.data; autoHash(hasher, data.owner_decl); autoHash(hasher, data.id); }, .anon_struct, .anon_union, .@"struct", .@"union", .packed_struct, .packed_union, => store.indexToCType(self.cast(Payload.Aggregate).?.data.fwd_decl) .updateHasher(hasher, store), .function, .varargs_function, => { const data = self.cast(Payload.Function).?.data; store.indexToCType(data.return_type).updateHasher(hasher, store); for (data.param_types) |param_ty| { store.indexToCType(param_ty).updateHasher(hasher, store); } }, } } pub const Kind = enum { forward, forward_parameter, complete, global, parameter, payload }; const Convert = struct { storage: union { none: void, child: Payload.Child, seq: Payload.Sequence, fwd: Payload.FwdDecl, anon: struct { fields: [2]Payload.Fields.Field, pl: union { forward: Payload.Fields, complete: Payload.Aggregate, }, }, }, value: union(enum) { tag: Tag, cty: CType, }, pub fn init(self: *@This(), t: Tag) void { self.* = if (t.hasPayload()) .{ .storage = .{ .none = {} }, .value = .{ .tag = t }, } else .{ .storage = .{ .none = {} }, .value = .{ .cty = initTag(t) }, }; } pub fn tag(self: @This()) Tag { return switch (self.value) { .tag => |t| t, .cty => |c| c.tag(), }; } fn tagFromIntInfo(int_info: std.builtin.Type.Int) Tag { return switch (int_info.bits) { 0 => .void, 1...8 => switch (int_info.signedness) { .unsigned => .uint8_t, .signed => .int8_t, }, 9...16 => switch (int_info.signedness) { .unsigned => .uint16_t, .signed => .int16_t, }, 17...32 => switch (int_info.signedness) { .unsigned => .uint32_t, .signed => .int32_t, }, 33...64 => switch (int_info.signedness) { .unsigned => .uint64_t, .signed => .int64_t, }, 65...128 => switch (int_info.signedness) { .unsigned => .zig_u128, .signed => .zig_i128, }, else => .array, }; } pub const Lookup = union(enum) { fail: Target, imm: struct { set: *const Store.Set, target: Target, }, mut: struct { promoted: *Store.Promoted, mod: *Module, }, pub fn isMutable(self: @This()) bool { return switch (self) { .fail, .imm => false, .mut => true, }; } pub fn getTarget(self: @This()) Target { return switch (self) { .fail => |target| target, .imm => |imm| imm.target, .mut => |mut| mut.mod.getTarget(), }; } pub fn getSet(self: @This()) ?*const Store.Set { return switch (self) { .fail => null, .imm => |imm| imm.set, .mut => |mut| &mut.promoted.set, }; } pub fn typeToIndex(self: @This(), ty: Type, kind: Kind) !?Index { return switch (self) { .fail => null, .imm => |imm| imm.set.typeToIndex(ty, imm.target, kind), .mut => |mut| try mut.promoted.typeToIndex(ty, mut.mod, kind), }; } pub fn indexToCType(self: @This(), index: Index) ?CType { return if (self.getSet()) |set| set.indexToCType(index) else null; } pub fn freeze(self: @This()) @This() { return switch (self) { .fail, .imm => self, .mut => |mut| .{ .imm = .{ .set = &mut.promoted.set, .target = self.getTarget() } }, }; } }; fn sortFields(self: *@This(), fields_len: usize) []Payload.Fields.Field { const Field = Payload.Fields.Field; const slice = self.storage.anon.fields[0..fields_len]; std.sort.sort(Field, slice, {}, struct { fn before(_: void, lhs: Field, rhs: Field) bool { return lhs.alignas.@"align" > rhs.alignas.@"align"; } }.before); return slice; } fn initAnon(self: *@This(), kind: Kind, fwd_idx: Index, fields_len: usize) void { switch (kind) { .forward, .forward_parameter => { self.storage.anon.pl = .{ .forward = .{ .base = .{ .tag = .fwd_anon_struct }, .data = self.sortFields(fields_len), } }; self.value = .{ .cty = initPayload(&self.storage.anon.pl.forward) }; }, .complete, .parameter, .global => { self.storage.anon.pl = .{ .complete = .{ .base = .{ .tag = .anon_struct }, .data = .{ .fields = self.sortFields(fields_len), .fwd_decl = fwd_idx, }, } }; self.value = .{ .cty = initPayload(&self.storage.anon.pl.complete) }; }, .payload => unreachable, } } fn initArrayParameter(self: *@This(), ty: Type, kind: Kind, lookup: Lookup) !void { if (switch (kind) { .forward_parameter => @as(Index, undefined), .parameter => try lookup.typeToIndex(ty, .forward_parameter), .forward, .complete, .global, .payload => unreachable, }) |fwd_idx| { if (try lookup.typeToIndex(ty, switch (kind) { .forward_parameter => .forward, .parameter => .complete, .forward, .complete, .global, .payload => unreachable, })) |array_idx| { self.storage = .{ .anon = undefined }; self.storage.anon.fields[0] = .{ .name = "array", .type = array_idx, .alignas = AlignAs.abiAlign(ty, lookup.getTarget()), }; self.initAnon(kind, fwd_idx, 1); } else self.init(switch (kind) { .forward_parameter => .fwd_anon_struct, .parameter => .anon_struct, .forward, .complete, .global, .payload => unreachable, }); } else self.init(.anon_struct); } pub fn initType(self: *@This(), ty: Type, kind: Kind, lookup: Lookup) !void { const target = lookup.getTarget(); self.* = undefined; if (!ty.isFnOrHasRuntimeBitsIgnoreComptime()) self.init(.void) else if (ty.isAbiInt()) switch (ty.tag()) { .usize => self.init(.uintptr_t), .isize => self.init(.intptr_t), .c_short => self.init(.short), .c_ushort => self.init(.@"unsigned short"), .c_int => self.init(.int), .c_uint => self.init(.@"unsigned int"), .c_long => self.init(.long), .c_ulong => self.init(.@"unsigned long"), .c_longlong => self.init(.@"long long"), .c_ulonglong => self.init(.@"unsigned long long"), else => switch (tagFromIntInfo(ty.intInfo(target))) { .void => unreachable, else => |t| self.init(t), .array => switch (kind) { .forward, .complete, .global => { const abi_size = ty.abiSize(target); const abi_align = ty.abiAlignment(target); self.storage = .{ .seq = .{ .base = .{ .tag = .array }, .data = .{ .len = @divExact(abi_size, abi_align), .elem_type = tagFromIntInfo(.{ .signedness = .unsigned, .bits = @intCast(u16, abi_align * 8), }).toIndex(), } } }; self.value = .{ .cty = initPayload(&self.storage.seq) }; }, .forward_parameter, .parameter, => try self.initArrayParameter(ty, kind, lookup), .payload => unreachable, }, }, } else switch (ty.zigTypeTag()) { .Frame => unreachable, .AnyFrame => unreachable, .Int, .Enum, .ErrorSet, .Type, .Void, .NoReturn, .ComptimeFloat, .ComptimeInt, .Undefined, .Null, .EnumLiteral, => unreachable, .Bool => self.init(.bool), .Float => self.init(switch (ty.tag()) { .f16 => .zig_f16, .f32 => .zig_f32, .f64 => .zig_f64, .f80 => .zig_f80, .f128 => .zig_f128, .c_longdouble => .zig_c_longdouble, else => unreachable, }), .Pointer => { const info = ty.ptrInfo().data; switch (info.size) { .Slice => { if (switch (kind) { .forward, .forward_parameter => @as(Index, undefined), .complete, .parameter, .global => try lookup.typeToIndex(ty, .forward), .payload => unreachable, }) |fwd_idx| { var buf: Type.SlicePtrFieldTypeBuffer = undefined; const ptr_ty = ty.slicePtrFieldType(&buf); if (try lookup.typeToIndex(ptr_ty, kind)) |ptr_idx| { self.storage = .{ .anon = undefined }; self.storage.anon.fields[0] = .{ .name = "ptr", .type = ptr_idx, .alignas = AlignAs.abiAlign(ptr_ty, target), }; self.storage.anon.fields[1] = .{ .name = "len", .type = Tag.uintptr_t.toIndex(), .alignas = AlignAs.abiAlign(Type.usize, target), }; self.initAnon(kind, fwd_idx, 2); } else self.init(switch (kind) { .forward, .forward_parameter => .fwd_anon_struct, .complete, .parameter, .global => .anon_struct, .payload => unreachable, }); } else self.init(.anon_struct); }, .One, .Many, .C => { const t: Tag = switch (info.@"volatile") { false => switch (info.mutable) { true => .pointer, false => .pointer_const, }, true => switch (info.mutable) { true => .pointer_volatile, false => .pointer_const_volatile, }, }; var host_int_pl = Type.Payload.Bits{ .base = .{ .tag = .int_unsigned }, .data = info.host_size * 8, }; const pointee_ty = if (info.host_size > 0 and info.vector_index == .none) Type.initPayload(&host_int_pl.base) else info.pointee_type; if (if (info.size == .C and pointee_ty.tag() == .u8) Tag.char.toIndex() else try lookup.typeToIndex(pointee_ty, .forward)) |child_idx| { self.storage = .{ .child = .{ .base = .{ .tag = t }, .data = child_idx, } }; self.value = .{ .cty = initPayload(&self.storage.child) }; } else self.init(t); }, } }, .Struct, .Union => |zig_ty_tag| if (ty.containerLayout() == .Packed) { if (ty.castTag(.@"struct")) |struct_obj| { try self.initType(struct_obj.data.backing_int_ty, kind, lookup); } else { var buf: Type.Payload.Bits = .{ .base = .{ .tag = .int_unsigned }, .data = @intCast(u16, ty.bitSize(target)), }; try self.initType(Type.initPayload(&buf.base), kind, lookup); } } else if (ty.isTupleOrAnonStruct()) { if (lookup.isMutable()) { for (0..switch (zig_ty_tag) { .Struct => ty.structFieldCount(), .Union => ty.unionFields().count(), else => unreachable, }) |field_i| { const field_ty = ty.structFieldType(field_i); if ((zig_ty_tag == .Struct and ty.structFieldIsComptime(field_i)) or !field_ty.hasRuntimeBitsIgnoreComptime()) continue; _ = try lookup.typeToIndex(field_ty, switch (kind) { .forward, .forward_parameter => .forward, .complete, .parameter => .complete, .global => .global, .payload => unreachable, }); } switch (kind) { .forward, .forward_parameter => {}, .complete, .parameter, .global => _ = try lookup.typeToIndex(ty, .forward), .payload => unreachable, } } self.init(switch (kind) { .forward, .forward_parameter => switch (zig_ty_tag) { .Struct => .fwd_anon_struct, .Union => .fwd_anon_union, else => unreachable, }, .complete, .parameter, .global => switch (zig_ty_tag) { .Struct => .anon_struct, .Union => .anon_union, else => unreachable, }, .payload => unreachable, }); } else { const tag_ty = ty.unionTagTypeSafety(); const is_tagged_union_wrapper = kind != .payload and tag_ty != null; const is_struct = zig_ty_tag == .Struct or is_tagged_union_wrapper; switch (kind) { .forward, .forward_parameter => { self.storage = .{ .fwd = .{ .base = .{ .tag = if (is_struct) .fwd_struct else .fwd_union }, .data = ty.getOwnerDecl(), } }; self.value = .{ .cty = initPayload(&self.storage.fwd) }; }, .complete, .parameter, .global, .payload => if (is_tagged_union_wrapper) { const fwd_idx = try lookup.typeToIndex(ty, .forward); const payload_idx = try lookup.typeToIndex(ty, .payload); const tag_idx = try lookup.typeToIndex(tag_ty.?, kind); if (fwd_idx != null and payload_idx != null and tag_idx != null) { self.storage = .{ .anon = undefined }; var field_count: usize = 0; if (payload_idx != Tag.void.toIndex()) { self.storage.anon.fields[field_count] = .{ .name = "payload", .type = payload_idx.?, .alignas = AlignAs.unionPayloadAlign(ty, target), }; field_count += 1; } if (tag_idx != Tag.void.toIndex()) { self.storage.anon.fields[field_count] = .{ .name = "tag", .type = tag_idx.?, .alignas = AlignAs.abiAlign(tag_ty.?, target), }; field_count += 1; } self.storage.anon.pl = .{ .complete = .{ .base = .{ .tag = .@"struct" }, .data = .{ .fields = self.sortFields(field_count), .fwd_decl = fwd_idx.?, }, } }; self.value = .{ .cty = initPayload(&self.storage.anon.pl.complete) }; } else self.init(.@"struct"); } else if (kind == .payload and ty.unionHasAllZeroBitFieldTypes()) { self.init(.void); } else { var is_packed = false; for (0..switch (zig_ty_tag) { .Struct => ty.structFieldCount(), .Union => ty.unionFields().count(), else => unreachable, }) |field_i| { const field_ty = ty.structFieldType(field_i); if (!field_ty.hasRuntimeBitsIgnoreComptime()) continue; const field_align = AlignAs.fieldAlign(ty, field_i, target); if (field_align.@"align" < field_align.abi) { is_packed = true; if (!lookup.isMutable()) break; } if (lookup.isMutable()) { _ = try lookup.typeToIndex(field_ty, switch (kind) { .forward, .forward_parameter => unreachable, .complete, .parameter, .payload => .complete, .global => .global, }); } } switch (kind) { .forward, .forward_parameter => unreachable, .complete, .parameter, .global => { _ = try lookup.typeToIndex(ty, .forward); self.init(if (is_struct) if (is_packed) .packed_struct else .@"struct" else if (is_packed) .packed_union else .@"union"); }, .payload => self.init(if (is_packed) .packed_unnamed_union else .unnamed_union), } }, } }, .Array, .Vector => |zig_ty_tag| { switch (kind) { .forward, .complete, .global => { const t: Tag = switch (zig_ty_tag) { .Array => .array, .Vector => .vector, else => unreachable, }; if (try lookup.typeToIndex(ty.childType(), kind)) |child_idx| { self.storage = .{ .seq = .{ .base = .{ .tag = t }, .data = .{ .len = ty.arrayLenIncludingSentinel(), .elem_type = child_idx, } } }; self.value = .{ .cty = initPayload(&self.storage.seq) }; } else self.init(t); }, .forward_parameter, .parameter => try self.initArrayParameter(ty, kind, lookup), .payload => unreachable, } }, .Optional => { var buf: Type.Payload.ElemType = undefined; const payload_ty = ty.optionalChild(&buf); if (payload_ty.hasRuntimeBitsIgnoreComptime()) { if (ty.optionalReprIsPayload()) { try self.initType(payload_ty, kind, lookup); } else if (switch (kind) { .forward, .forward_parameter => @as(Index, undefined), .complete, .parameter, .global => try lookup.typeToIndex(ty, .forward), .payload => unreachable, }) |fwd_idx| { if (try lookup.typeToIndex(payload_ty, switch (kind) { .forward, .forward_parameter => .forward, .complete, .parameter => .complete, .global => .global, .payload => unreachable, })) |payload_idx| { self.storage = .{ .anon = undefined }; self.storage.anon.fields[0] = .{ .name = "payload", .type = payload_idx, .alignas = AlignAs.abiAlign(payload_ty, target), }; self.storage.anon.fields[1] = .{ .name = "is_null", .type = Tag.bool.toIndex(), .alignas = AlignAs.abiAlign(Type.bool, target), }; self.initAnon(kind, fwd_idx, 2); } else self.init(switch (kind) { .forward, .forward_parameter => .fwd_anon_struct, .complete, .parameter, .global => .anon_struct, .payload => unreachable, }); } else self.init(.anon_struct); } else self.init(.bool); }, .ErrorUnion => { if (switch (kind) { .forward, .forward_parameter => @as(Index, undefined), .complete, .parameter, .global => try lookup.typeToIndex(ty, .forward), .payload => unreachable, }) |fwd_idx| { const payload_ty = ty.errorUnionPayload(); if (try lookup.typeToIndex(payload_ty, switch (kind) { .forward, .forward_parameter => .forward, .complete, .parameter => .complete, .global => .global, .payload => unreachable, })) |payload_idx| { const error_ty = ty.errorUnionSet(); if (payload_idx == Tag.void.toIndex()) { try self.initType(error_ty, kind, lookup); } else if (try lookup.typeToIndex(error_ty, kind)) |error_idx| { self.storage = .{ .anon = undefined }; self.storage.anon.fields[0] = .{ .name = "payload", .type = payload_idx, .alignas = AlignAs.abiAlign(payload_ty, target), }; self.storage.anon.fields[1] = .{ .name = "error", .type = error_idx, .alignas = AlignAs.abiAlign(error_ty, target), }; self.initAnon(kind, fwd_idx, 2); } else self.init(switch (kind) { .forward, .forward_parameter => .fwd_anon_struct, .complete, .parameter, .global => .anon_struct, .payload => unreachable, }); } else self.init(switch (kind) { .forward, .forward_parameter => .fwd_anon_struct, .complete, .parameter, .global => .anon_struct, .payload => unreachable, }); } else self.init(.anon_struct); }, .Opaque => switch (ty.tag()) { .anyopaque => self.init(.void), .@"opaque" => { self.storage = .{ .fwd = .{ .base = .{ .tag = .fwd_struct }, .data = ty.getOwnerDecl(), } }; self.value = .{ .cty = initPayload(&self.storage.fwd) }; }, else => unreachable, }, .Fn => { const info = ty.fnInfo(); if (!info.is_generic) { if (lookup.isMutable()) { const param_kind: Kind = switch (kind) { .forward, .forward_parameter => .forward_parameter, .complete, .parameter, .global => .parameter, .payload => unreachable, }; _ = try lookup.typeToIndex(info.return_type, param_kind); for (info.param_types) |param_type| { if (!param_type.hasRuntimeBitsIgnoreComptime()) continue; _ = try lookup.typeToIndex(param_type, param_kind); } } self.init(if (info.is_var_args) .varargs_function else .function); } else self.init(.void); }, } } }; pub fn copy(self: CType, arena: Allocator) !CType { return self.copyContext(struct { arena: Allocator, pub fn copyIndex(_: @This(), idx: Index) Index { return idx; } }{ .arena = arena }); } fn copyFields(ctx: anytype, old_fields: Payload.Fields.Data) !Payload.Fields.Data { const new_fields = try ctx.arena.alloc(Payload.Fields.Field, old_fields.len); for (new_fields, old_fields) |*new_field, old_field| { new_field.name = try ctx.arena.dupeZ(u8, mem.span(old_field.name)); new_field.type = ctx.copyIndex(old_field.type); new_field.alignas = old_field.alignas; } return new_fields; } fn copyParams(ctx: anytype, old_param_types: []const Index) ![]const Index { const new_param_types = try ctx.arena.alloc(Index, old_param_types.len); for (new_param_types, old_param_types) |*new_param_type, old_param_type| new_param_type.* = ctx.copyIndex(old_param_type); return new_param_types; } pub fn copyContext(self: CType, ctx: anytype) !CType { switch (self.tag()) { .void, .char, .@"signed char", .short, .int, .long, .@"long long", ._Bool, .@"unsigned char", .@"unsigned short", .@"unsigned int", .@"unsigned long", .@"unsigned long long", .float, .double, .@"long double", .bool, .size_t, .ptrdiff_t, .uint8_t, .int8_t, .uint16_t, .int16_t, .uint32_t, .int32_t, .uint64_t, .int64_t, .uintptr_t, .intptr_t, .zig_u128, .zig_i128, .zig_f16, .zig_f32, .zig_f64, .zig_f80, .zig_f128, .zig_c_longdouble, => return self, .pointer, .pointer_const, .pointer_volatile, .pointer_const_volatile, => { const pl = self.cast(Payload.Child).?; const new_pl = try ctx.arena.create(Payload.Child); new_pl.* = .{ .base = .{ .tag = pl.base.tag }, .data = ctx.copyIndex(pl.data) }; return initPayload(new_pl); }, .array, .vector, => { const pl = self.cast(Payload.Sequence).?; const new_pl = try ctx.arena.create(Payload.Sequence); new_pl.* = .{ .base = .{ .tag = pl.base.tag }, .data = .{ .len = pl.data.len, .elem_type = ctx.copyIndex(pl.data.elem_type) }, }; return initPayload(new_pl); }, .fwd_anon_struct, .fwd_anon_union, => { const pl = self.cast(Payload.Fields).?; const new_pl = try ctx.arena.create(Payload.Fields); new_pl.* = .{ .base = .{ .tag = pl.base.tag }, .data = try copyFields(ctx, pl.data), }; return initPayload(new_pl); }, .fwd_struct, .fwd_union, => { const pl = self.cast(Payload.FwdDecl).?; const new_pl = try ctx.arena.create(Payload.FwdDecl); new_pl.* = .{ .base = .{ .tag = pl.base.tag }, .data = pl.data }; return initPayload(new_pl); }, .unnamed_struct, .unnamed_union, .packed_unnamed_struct, .packed_unnamed_union, => { const pl = self.cast(Payload.Unnamed).?; const new_pl = try ctx.arena.create(Payload.Unnamed); new_pl.* = .{ .base = .{ .tag = pl.base.tag }, .data = .{ .fields = try copyFields(ctx, pl.data.fields), .owner_decl = pl.data.owner_decl, .id = pl.data.id, } }; return initPayload(new_pl); }, .anon_struct, .anon_union, .@"struct", .@"union", .packed_struct, .packed_union, => { const pl = self.cast(Payload.Aggregate).?; const new_pl = try ctx.arena.create(Payload.Aggregate); new_pl.* = .{ .base = .{ .tag = pl.base.tag }, .data = .{ .fields = try copyFields(ctx, pl.data.fields), .fwd_decl = ctx.copyIndex(pl.data.fwd_decl), } }; return initPayload(new_pl); }, .function, .varargs_function, => { const pl = self.cast(Payload.Function).?; const new_pl = try ctx.arena.create(Payload.Function); new_pl.* = .{ .base = .{ .tag = pl.base.tag }, .data = .{ .return_type = ctx.copyIndex(pl.data.return_type), .param_types = try copyParams(ctx, pl.data.param_types), } }; return initPayload(new_pl); }, } } fn createFromType(store: *Store.Promoted, ty: Type, target: Target, kind: Kind) !CType { var convert: Convert = undefined; try convert.initType(ty, kind, .{ .imm = .{ .set = &store.set, .target = target } }); return createFromConvert(store, ty, target, kind, &convert); } fn createFromConvert( store: *Store.Promoted, ty: Type, target: Target, kind: Kind, convert: Convert, ) !CType { const arena = store.arena.allocator(); switch (convert.value) { .cty => |c| return c.copy(arena), .tag => |t| switch (t) { .fwd_anon_struct, .fwd_anon_union, .unnamed_struct, .unnamed_union, .packed_unnamed_struct, .packed_unnamed_union, .anon_struct, .anon_union, .@"struct", .@"union", .packed_struct, .packed_union, => { const zig_ty_tag = ty.zigTypeTag(); const fields_len = switch (zig_ty_tag) { .Struct => ty.structFieldCount(), .Union => ty.unionFields().count(), else => unreachable, }; var c_fields_len: usize = 0; for (0..fields_len) |field_i| { const field_ty = ty.structFieldType(field_i); if ((zig_ty_tag == .Struct and ty.structFieldIsComptime(field_i)) or !field_ty.hasRuntimeBitsIgnoreComptime()) continue; c_fields_len += 1; } const fields_pl = try arena.alloc(Payload.Fields.Field, c_fields_len); var c_field_i: usize = 0; for (0..fields_len) |field_i| { const field_ty = ty.structFieldType(field_i); if ((zig_ty_tag == .Struct and ty.structFieldIsComptime(field_i)) or !field_ty.hasRuntimeBitsIgnoreComptime()) continue; defer c_field_i += 1; fields_pl[c_field_i] = .{ .name = try if (ty.isSimpleTuple()) std.fmt.allocPrintZ(arena, "f{}", .{field_i}) else arena.dupeZ(u8, switch (zig_ty_tag) { .Struct => ty.structFieldName(field_i), .Union => ty.unionFields().keys()[field_i], else => unreachable, }), .type = store.set.typeToIndex(field_ty, target, switch (kind) { .forward, .forward_parameter => .forward, .complete, .parameter, .payload => .complete, .global => .global, }).?, .alignas = AlignAs.fieldAlign(ty, field_i, target), }; } switch (t) { .fwd_anon_struct, .fwd_anon_union, => { const anon_pl = try arena.create(Payload.Fields); anon_pl.* = .{ .base = .{ .tag = t }, .data = fields_pl }; return initPayload(anon_pl); }, .unnamed_struct, .unnamed_union, .packed_unnamed_struct, .packed_unnamed_union, => { const unnamed_pl = try arena.create(Payload.Unnamed); unnamed_pl.* = .{ .base = .{ .tag = t }, .data = .{ .fields = fields_pl, .owner_decl = ty.getOwnerDecl(), .id = if (ty.unionTagTypeSafety()) |_| 0 else unreachable, } }; return initPayload(unnamed_pl); }, .anon_struct, .anon_union, .@"struct", .@"union", .packed_struct, .packed_union, => { const struct_pl = try arena.create(Payload.Aggregate); struct_pl.* = .{ .base = .{ .tag = t }, .data = .{ .fields = fields_pl, .fwd_decl = store.set.typeToIndex(ty, target, .forward).?, } }; return initPayload(struct_pl); }, else => unreachable, } }, .function, .varargs_function, => { const info = ty.fnInfo(); assert(!info.is_generic); const param_kind: Kind = switch (kind) { .forward, .forward_parameter => .forward_parameter, .complete, .parameter, .global => .parameter, .payload => unreachable, }; var c_params_len: usize = 0; for (info.param_types) |param_type| { if (!param_type.hasRuntimeBitsIgnoreComptime()) continue; c_params_len += 1; } const params_pl = try arena.alloc(Index, c_params_len); var c_param_i: usize = 0; for (info.param_types) |param_type| { if (!param_type.hasRuntimeBitsIgnoreComptime()) continue; params_pl[c_param_i] = store.set.typeToIndex(param_type, target, param_kind).?; c_param_i += 1; } const fn_pl = try arena.create(Payload.Function); fn_pl.* = .{ .base = .{ .tag = t }, .data = .{ .return_type = store.set.typeToIndex(info.return_type, target, param_kind).?, .param_types = params_pl, } }; return initPayload(fn_pl); }, else => unreachable, }, } } pub const TypeAdapter64 = struct { kind: Kind, lookup: Convert.Lookup, convert: *const Convert, fn eqlRecurse(self: @This(), ty: Type, cty: Index, kind: Kind) bool { assert(!self.lookup.isMutable()); var convert: Convert = undefined; convert.initType(ty, kind, self.lookup) catch unreachable; const self_recurse = @This(){ .kind = kind, .lookup = self.lookup, .convert = &convert }; return self_recurse.eql(ty, self.lookup.indexToCType(cty).?); } pub fn eql(self: @This(), ty: Type, cty: CType) bool { switch (self.convert.value) { .cty => |c| return c.eql(cty), .tag => |t| { if (t != cty.tag()) return false; const target = self.lookup.getTarget(); switch (t) { .fwd_anon_struct, .fwd_anon_union, => { if (!ty.isTupleOrAnonStruct()) return false; var name_buf: [ std.fmt.count("f{}", .{std.math.maxInt(usize)}) ]u8 = undefined; const c_fields = cty.cast(Payload.Fields).?.data; const zig_ty_tag = ty.zigTypeTag(); var c_field_i: usize = 0; for (0..switch (zig_ty_tag) { .Struct => ty.structFieldCount(), .Union => ty.unionFields().count(), else => unreachable, }) |field_i| { const field_ty = ty.structFieldType(field_i); if ((zig_ty_tag == .Struct and ty.structFieldIsComptime(field_i)) or !field_ty.hasRuntimeBitsIgnoreComptime()) continue; defer c_field_i += 1; const c_field = &c_fields[c_field_i]; if (!self.eqlRecurse(field_ty, c_field.type, switch (self.kind) { .forward, .forward_parameter => .forward, .complete, .parameter => .complete, .global => .global, .payload => unreachable, }) or !mem.eql( u8, if (ty.isSimpleTuple()) std.fmt.bufPrint(&name_buf, "f{}", .{field_i}) catch unreachable else switch (zig_ty_tag) { .Struct => ty.structFieldName(field_i), .Union => ty.unionFields().keys()[field_i], else => unreachable, }, mem.span(c_field.name), ) or AlignAs.fieldAlign(ty, field_i, target).@"align" != c_field.alignas.@"align") return false; } return true; }, .unnamed_struct, .unnamed_union, .packed_unnamed_struct, .packed_unnamed_union, => switch (self.kind) { .forward, .forward_parameter, .complete, .parameter, .global => unreachable, .payload => if (ty.unionTagTypeSafety()) |_| { const data = cty.cast(Payload.Unnamed).?.data; return ty.getOwnerDecl() == data.owner_decl and data.id == 0; } else unreachable, }, .anon_struct, .anon_union, .@"struct", .@"union", .packed_struct, .packed_union, => return self.eqlRecurse( ty, cty.cast(Payload.Aggregate).?.data.fwd_decl, .forward, ), .function, .varargs_function, => { if (ty.zigTypeTag() != .Fn) return false; const info = ty.fnInfo(); assert(!info.is_generic); const data = cty.cast(Payload.Function).?.data; const param_kind: Kind = switch (self.kind) { .forward, .forward_parameter => .forward_parameter, .complete, .parameter, .global => .parameter, .payload => unreachable, }; if (!self.eqlRecurse(info.return_type, data.return_type, param_kind)) return false; var c_param_i: usize = 0; for (info.param_types) |param_type| { if (!param_type.hasRuntimeBitsIgnoreComptime()) continue; if (c_param_i >= data.param_types.len) return false; const param_cty = data.param_types[c_param_i]; c_param_i += 1; if (!self.eqlRecurse(param_type, param_cty, param_kind)) return false; } return c_param_i == data.param_types.len; }, else => unreachable, } }, } } pub fn hash(self: @This(), ty: Type) u64 { var hasher = std.hash.Wyhash.init(0); self.updateHasher(&hasher, ty); return hasher.final(); } fn updateHasherRecurse(self: @This(), hasher: anytype, ty: Type, kind: Kind) void { assert(!self.lookup.isMutable()); var convert: Convert = undefined; convert.initType(ty, kind, self.lookup) catch unreachable; const self_recurse = @This(){ .kind = kind, .lookup = self.lookup, .convert = &convert }; self_recurse.updateHasher(hasher, ty); } pub fn updateHasher(self: @This(), hasher: anytype, ty: Type) void { switch (self.convert.value) { .cty => |c| return c.updateHasher(hasher, self.lookup.getSet().?.*), .tag => |t| { autoHash(hasher, t); const target = self.lookup.getTarget(); switch (t) { .fwd_anon_struct, .fwd_anon_union, => { var name_buf: [ std.fmt.count("f{}", .{std.math.maxInt(usize)}) ]u8 = undefined; const zig_ty_tag = ty.zigTypeTag(); for (0..switch (ty.zigTypeTag()) { .Struct => ty.structFieldCount(), .Union => ty.unionFields().count(), else => unreachable, }) |field_i| { const field_ty = ty.structFieldType(field_i); if ((zig_ty_tag == .Struct and ty.structFieldIsComptime(field_i)) or !field_ty.hasRuntimeBitsIgnoreComptime()) continue; self.updateHasherRecurse(hasher, field_ty, switch (self.kind) { .forward, .forward_parameter => .forward, .complete, .parameter => .complete, .global => .global, .payload => unreachable, }); hasher.update(if (ty.isSimpleTuple()) std.fmt.bufPrint(&name_buf, "f{}", .{field_i}) catch unreachable else switch (zig_ty_tag) { .Struct => ty.structFieldName(field_i), .Union => ty.unionFields().keys()[field_i], else => unreachable, }); autoHash(hasher, AlignAs.fieldAlign(ty, field_i, target).@"align"); } }, .unnamed_struct, .unnamed_union, .packed_unnamed_struct, .packed_unnamed_union, => switch (self.kind) { .forward, .forward_parameter, .complete, .parameter, .global => unreachable, .payload => if (ty.unionTagTypeSafety()) |_| { autoHash(hasher, ty.getOwnerDecl()); autoHash(hasher, @as(u32, 0)); } else unreachable, }, .anon_struct, .anon_union, .@"struct", .@"union", .packed_struct, .packed_union, => self.updateHasherRecurse(hasher, ty, .forward), .function, .varargs_function, => { const info = ty.fnInfo(); assert(!info.is_generic); const param_kind: Kind = switch (self.kind) { .forward, .forward_parameter => .forward_parameter, .complete, .parameter, .global => .parameter, .payload => unreachable, }; self.updateHasherRecurse(hasher, info.return_type, param_kind); for (info.param_types) |param_type| { if (!param_type.hasRuntimeBitsIgnoreComptime()) continue; self.updateHasherRecurse(hasher, param_type, param_kind); } }, else => unreachable, } }, } } }; pub const TypeAdapter32 = struct { kind: Kind, lookup: Convert.Lookup, convert: *const Convert, fn to64(self: @This()) TypeAdapter64 { return .{ .kind = self.kind, .lookup = self.lookup, .convert = self.convert }; } pub fn eql(self: @This(), ty: Type, cty: CType, cty_index: usize) bool { _ = cty_index; return self.to64().eql(ty, cty); } pub fn hash(self: @This(), ty: Type) u32 { return @truncate(u32, self.to64().hash(ty)); } }; };