zig/src/InternPool.zig
mlugg 26a94e8481
Zcu: eliminate Decl.alive field
Legacy anon decls now have three uses:
* Type owner decls
* Function owner decls
* `@export` and `@extern`

Therefore, there are no longer any cases where we wish to explicitly
omit legacy anon decls from the binary. This means we can remove the
concept of an "alive" vs "dead" `Decl`, which also allows us to remove
the separate `anon_work_queue` in `Compilation`.
2024-03-26 13:48:06 +00:00

9280 lines
342 KiB
Zig

//! All interned objects have both a value and a type.
//! This data structure is self-contained, with the following exceptions:
//! * Module.Namespace has a pointer to Module.File
/// Maps `Key` to `Index`. `Key` objects are not stored anywhere; they are
/// constructed lazily.
map: std.AutoArrayHashMapUnmanaged(void, void) = .{},
items: std.MultiArrayList(Item) = .{},
extra: std.ArrayListUnmanaged(u32) = .{},
/// On 32-bit systems, this array is ignored and extra is used for everything.
/// On 64-bit systems, this array is used for big integers and associated metadata.
/// Use the helper methods instead of accessing this directly in order to not
/// violate the above mechanism.
limbs: std.ArrayListUnmanaged(u64) = .{},
/// In order to store references to strings in fewer bytes, we copy all
/// string bytes into here. String bytes can be null. It is up to whomever
/// is referencing the data here whether they want to store both index and length,
/// thus allowing null bytes, or store only index, and use null-termination. The
/// `string_bytes` array is agnostic to either usage.
string_bytes: std.ArrayListUnmanaged(u8) = .{},
/// Rather than allocating Decl objects with an Allocator, we instead allocate
/// them with this SegmentedList. This provides four advantages:
/// * Stable memory so that one thread can access a Decl object while another
/// thread allocates additional Decl objects from this list.
/// * It allows us to use u32 indexes to reference Decl objects rather than
/// pointers, saving memory in Type, Value, and dependency sets.
/// * Using integers to reference Decl objects rather than pointers makes
/// serialization trivial.
/// * It provides a unique integer to be used for anonymous symbol names, avoiding
/// multi-threaded contention on an atomic counter.
allocated_decls: std.SegmentedList(Module.Decl, 0) = .{},
/// When a Decl object is freed from `allocated_decls`, it is pushed into this stack.
decls_free_list: std.ArrayListUnmanaged(DeclIndex) = .{},
/// Same pattern as with `allocated_decls`.
allocated_namespaces: std.SegmentedList(Module.Namespace, 0) = .{},
/// Same pattern as with `decls_free_list`.
namespaces_free_list: std.ArrayListUnmanaged(NamespaceIndex) = .{},
/// Some types such as enums, structs, and unions need to store mappings from field names
/// to field index, or value to field index. In such cases, they will store the underlying
/// field names and values directly, relying on one of these maps, stored separately,
/// to provide lookup.
/// These are not serialized; it is computed upon deserialization.
maps: std.ArrayListUnmanaged(FieldMap) = .{},
/// Used for finding the index inside `string_bytes`.
string_table: std.HashMapUnmanaged(
u32,
void,
std.hash_map.StringIndexContext,
std.hash_map.default_max_load_percentage,
) = .{},
/// An index into `tracked_insts` gives a reference to a single ZIR instruction which
/// persists across incremental updates.
tracked_insts: std.AutoArrayHashMapUnmanaged(TrackedInst, void) = .{},
/// Dependencies on the source code hash associated with a ZIR instruction.
/// * For a `declaration`, this is the entire declaration body.
/// * For a `struct_decl`, `union_decl`, etc, this is the source of the fields (but not declarations).
/// * For a `func`, this is the source of the full function signature.
/// These are also invalidated if tracking fails for this instruction.
/// Value is index into `dep_entries` of the first dependency on this hash.
src_hash_deps: std.AutoArrayHashMapUnmanaged(TrackedInst.Index, DepEntry.Index) = .{},
/// Dependencies on the value of a Decl.
/// Value is index into `dep_entries` of the first dependency on this Decl value.
decl_val_deps: std.AutoArrayHashMapUnmanaged(DeclIndex, DepEntry.Index) = .{},
/// Dependencies on the IES of a runtime function.
/// Value is index into `dep_entries` of the first dependency on this Decl value.
func_ies_deps: std.AutoArrayHashMapUnmanaged(Index, DepEntry.Index) = .{},
/// Dependencies on the full set of names in a ZIR namespace.
/// Key refers to a `struct_decl`, `union_decl`, etc.
/// Value is index into `dep_entries` of the first dependency on this namespace.
namespace_deps: std.AutoArrayHashMapUnmanaged(TrackedInst.Index, DepEntry.Index) = .{},
/// Dependencies on the (non-)existence of some name in a namespace.
/// Value is index into `dep_entries` of the first dependency on this name.
namespace_name_deps: std.AutoArrayHashMapUnmanaged(NamespaceNameKey, DepEntry.Index) = .{},
/// Given a `Depender`, points to an entry in `dep_entries` whose `depender`
/// matches. The `next_dependee` field can be used to iterate all such entries
/// and remove them from the corresponding lists.
first_dependency: std.AutoArrayHashMapUnmanaged(Depender, DepEntry.Index) = .{},
/// Stores dependency information. The hashmaps declared above are used to look
/// up entries in this list as required. This is not stored in `extra` so that
/// we can use `free_dep_entries` to track free indices, since dependencies are
/// removed frequently.
dep_entries: std.ArrayListUnmanaged(DepEntry) = .{},
/// Stores unused indices in `dep_entries` which can be reused without a full
/// garbage collection pass.
free_dep_entries: std.ArrayListUnmanaged(DepEntry.Index) = .{},
pub const TrackedInst = extern struct {
path_digest: Cache.BinDigest,
inst: Zir.Inst.Index,
comptime {
// The fields should be tightly packed. See also serialiation logic in `Compilation.saveState`.
assert(@sizeOf(@This()) == Cache.bin_digest_len + @sizeOf(Zir.Inst.Index));
}
pub const Index = enum(u32) {
_,
pub fn resolve(i: TrackedInst.Index, ip: *const InternPool) Zir.Inst.Index {
return ip.tracked_insts.keys()[@intFromEnum(i)].inst;
}
pub fn toOptional(i: TrackedInst.Index) Optional {
return @enumFromInt(@intFromEnum(i));
}
pub const Optional = enum(u32) {
none = std.math.maxInt(u32),
_,
pub fn unwrap(opt: Optional) ?TrackedInst.Index {
return switch (opt) {
.none => null,
_ => @enumFromInt(@intFromEnum(opt)),
};
}
};
};
};
pub fn trackZir(ip: *InternPool, gpa: Allocator, file: *Module.File, inst: Zir.Inst.Index) Allocator.Error!TrackedInst.Index {
const key: TrackedInst = .{
.path_digest = file.path_digest,
.inst = inst,
};
const gop = try ip.tracked_insts.getOrPut(gpa, key);
return @enumFromInt(gop.index);
}
/// Reperesents the "source" of a dependency edge, i.e. either a Decl or a
/// runtime function (represented as an InternPool index).
/// MSB is 0 for a Decl, 1 for a function.
pub const Depender = enum(u32) {
_,
pub const Unwrapped = union(enum) {
decl: DeclIndex,
func: InternPool.Index,
};
pub fn unwrap(dep: Depender) Unwrapped {
const tag: u1 = @truncate(@intFromEnum(dep) >> 31);
const val: u31 = @truncate(@intFromEnum(dep));
return switch (tag) {
0 => .{ .decl = @enumFromInt(val) },
1 => .{ .func = @enumFromInt(val) },
};
}
pub fn wrap(raw: Unwrapped) Depender {
return @enumFromInt(switch (raw) {
.decl => |decl| @intFromEnum(decl),
.func => |func| (1 << 31) | @intFromEnum(func),
});
}
pub fn toOptional(dep: Depender) Optional {
return @enumFromInt(@intFromEnum(dep));
}
pub const Optional = enum(u32) {
none = std.math.maxInt(u32),
_,
pub fn unwrap(opt: Optional) ?Depender {
return switch (opt) {
.none => null,
_ => @enumFromInt(@intFromEnum(opt)),
};
}
};
};
pub const Dependee = union(enum) {
src_hash: TrackedInst.Index,
decl_val: DeclIndex,
func_ies: Index,
namespace: TrackedInst.Index,
namespace_name: NamespaceNameKey,
};
pub fn removeDependenciesForDepender(ip: *InternPool, gpa: Allocator, depender: Depender) void {
var opt_idx = (ip.first_dependency.fetchSwapRemove(depender) orelse return).value.toOptional();
while (opt_idx.unwrap()) |idx| {
const dep = ip.dep_entries.items[@intFromEnum(idx)];
opt_idx = dep.next_dependee;
const prev_idx = dep.prev.unwrap() orelse {
// This entry is the start of a list in some `*_deps`.
// We cannot easily remove this mapping, so this must remain as a dummy entry.
ip.dep_entries.items[@intFromEnum(idx)].depender = .none;
continue;
};
ip.dep_entries.items[@intFromEnum(prev_idx)].next = dep.next;
if (dep.next.unwrap()) |next_idx| {
ip.dep_entries.items[@intFromEnum(next_idx)].prev = dep.prev;
}
ip.free_dep_entries.append(gpa, idx) catch {
// This memory will be reclaimed on the next garbage collection.
// Thus, we do not need to propagate this error.
};
}
}
pub const DependencyIterator = struct {
ip: *const InternPool,
next_entry: DepEntry.Index.Optional,
pub fn next(it: *DependencyIterator) ?Depender {
const idx = it.next_entry.unwrap() orelse return null;
const entry = it.ip.dep_entries.items[@intFromEnum(idx)];
it.next_entry = entry.next;
return entry.depender.unwrap().?;
}
};
pub fn dependencyIterator(ip: *const InternPool, dependee: Dependee) DependencyIterator {
const first_entry = switch (dependee) {
.src_hash => |x| ip.src_hash_deps.get(x),
.decl_val => |x| ip.decl_val_deps.get(x),
.func_ies => |x| ip.func_ies_deps.get(x),
.namespace => |x| ip.namespace_deps.get(x),
.namespace_name => |x| ip.namespace_name_deps.get(x),
} orelse return .{
.ip = ip,
.next_entry = .none,
};
if (ip.dep_entries.items[@intFromEnum(first_entry)].depender == .none) return .{
.ip = ip,
.next_entry = .none,
};
return .{
.ip = ip,
.next_entry = first_entry.toOptional(),
};
}
pub fn addDependency(ip: *InternPool, gpa: Allocator, depender: Depender, dependee: Dependee) Allocator.Error!void {
const first_depender_dep: DepEntry.Index.Optional = if (ip.first_dependency.get(depender)) |idx| dep: {
// The entry already exists, so there is capacity to overwrite it later.
break :dep idx.toOptional();
} else none: {
// Ensure there is capacity available to add this dependency later.
try ip.first_dependency.ensureUnusedCapacity(gpa, 1);
break :none .none;
};
// We're very likely to need space for a new entry - reserve it now to avoid
// the need for error cleanup logic.
if (ip.free_dep_entries.items.len == 0) {
try ip.dep_entries.ensureUnusedCapacity(gpa, 1);
}
// This block should allocate an entry and prepend it to the relevant `*_deps` list.
// The `next` field should be correctly initialized; all other fields may be undefined.
const new_index: DepEntry.Index = switch (dependee) {
inline else => |dependee_payload, tag| new_index: {
const gop = try switch (tag) {
.src_hash => ip.src_hash_deps,
.decl_val => ip.decl_val_deps,
.func_ies => ip.func_ies_deps,
.namespace => ip.namespace_deps,
.namespace_name => ip.namespace_name_deps,
}.getOrPut(gpa, dependee_payload);
if (gop.found_existing and ip.dep_entries.items[@intFromEnum(gop.value_ptr.*)].depender == .none) {
// Dummy entry, so we can reuse it rather than allocating a new one!
ip.dep_entries.items[@intFromEnum(gop.value_ptr.*)].next = .none;
break :new_index gop.value_ptr.*;
}
// Prepend a new dependency.
const new_index: DepEntry.Index, const ptr = if (ip.free_dep_entries.popOrNull()) |new_index| new: {
break :new .{ new_index, &ip.dep_entries.items[@intFromEnum(new_index)] };
} else .{ @enumFromInt(ip.dep_entries.items.len), ip.dep_entries.addOneAssumeCapacity() };
ptr.next = if (gop.found_existing) gop.value_ptr.*.toOptional() else .none;
gop.value_ptr.* = new_index;
break :new_index new_index;
},
};
ip.dep_entries.items[@intFromEnum(new_index)].depender = depender.toOptional();
ip.dep_entries.items[@intFromEnum(new_index)].prev = .none;
ip.dep_entries.items[@intFromEnum(new_index)].next_dependee = first_depender_dep;
ip.first_dependency.putAssumeCapacity(depender, new_index);
}
/// String is the name whose existence the dependency is on.
/// DepEntry.Index refers to the first such dependency.
pub const NamespaceNameKey = struct {
/// The instruction (`struct_decl` etc) which owns the namespace in question.
namespace: TrackedInst.Index,
/// The name whose existence the dependency is on.
name: NullTerminatedString,
};
pub const DepEntry = extern struct {
/// If null, this is a dummy entry - all other fields are `undefined`. It is
/// the first and only entry in one of `intern_pool.*_deps`, and does not
/// appear in any list by `first_dependency`, but is not in
/// `free_dep_entries` since `*_deps` stores a reference to it.
depender: Depender.Optional,
/// Index into `dep_entries` forming a doubly linked list of all dependencies on this dependee.
/// Used to iterate all dependers for a given dependee during an update.
/// null if this is the end of the list.
next: DepEntry.Index.Optional,
/// The other link for `next`.
/// null if this is the start of the list.
prev: DepEntry.Index.Optional,
/// Index into `dep_entries` forming a singly linked list of dependencies *of* `depender`.
/// Used to efficiently remove all `DepEntry`s for a single `depender` when it is re-analyzed.
/// null if this is the end of the list.
next_dependee: DepEntry.Index.Optional,
pub const Index = enum(u32) {
_,
pub fn toOptional(dep: DepEntry.Index) Optional {
return @enumFromInt(@intFromEnum(dep));
}
pub const Optional = enum(u32) {
none = std.math.maxInt(u32),
_,
pub fn unwrap(opt: Optional) ?DepEntry.Index {
return switch (opt) {
.none => null,
_ => @enumFromInt(@intFromEnum(opt)),
};
}
};
};
};
const FieldMap = std.ArrayHashMapUnmanaged(void, void, std.array_hash_map.AutoContext(void), false);
const builtin = @import("builtin");
const std = @import("std");
const Allocator = std.mem.Allocator;
const assert = std.debug.assert;
const BigIntConst = std.math.big.int.Const;
const BigIntMutable = std.math.big.int.Mutable;
const Cache = std.Build.Cache;
const Limb = std.math.big.Limb;
const Hash = std.hash.Wyhash;
const InternPool = @This();
const Module = @import("Module.zig");
const Zcu = Module;
const Zir = std.zig.Zir;
const KeyAdapter = struct {
intern_pool: *const InternPool,
pub fn eql(ctx: @This(), a: Key, b_void: void, b_map_index: usize) bool {
_ = b_void;
if (ctx.intern_pool.items.items(.tag)[b_map_index] == .removed) return false;
return ctx.intern_pool.indexToKey(@as(Index, @enumFromInt(b_map_index))).eql(a, ctx.intern_pool);
}
pub fn hash(ctx: @This(), a: Key) u32 {
return a.hash32(ctx.intern_pool);
}
};
/// An index into `maps` which might be `none`.
pub const OptionalMapIndex = enum(u32) {
none = std.math.maxInt(u32),
_,
pub fn unwrap(oi: OptionalMapIndex) ?MapIndex {
if (oi == .none) return null;
return @enumFromInt(@intFromEnum(oi));
}
};
/// An index into `maps`.
pub const MapIndex = enum(u32) {
_,
pub fn toOptional(i: MapIndex) OptionalMapIndex {
return @enumFromInt(@intFromEnum(i));
}
};
pub const RuntimeIndex = enum(u32) {
zero = 0,
comptime_field_ptr = std.math.maxInt(u32),
_,
pub fn increment(ri: *RuntimeIndex) void {
ri.* = @as(RuntimeIndex, @enumFromInt(@intFromEnum(ri.*) + 1));
}
};
pub const ComptimeAllocIndex = enum(u32) { _ };
pub const DeclIndex = std.zig.DeclIndex;
pub const OptionalDeclIndex = std.zig.OptionalDeclIndex;
pub const NamespaceIndex = enum(u32) {
_,
pub fn toOptional(i: NamespaceIndex) OptionalNamespaceIndex {
return @enumFromInt(@intFromEnum(i));
}
};
pub const OptionalNamespaceIndex = enum(u32) {
none = std.math.maxInt(u32),
_,
pub fn init(oi: ?NamespaceIndex) OptionalNamespaceIndex {
return @enumFromInt(@intFromEnum(oi orelse return .none));
}
pub fn unwrap(oi: OptionalNamespaceIndex) ?NamespaceIndex {
if (oi == .none) return null;
return @enumFromInt(@intFromEnum(oi));
}
};
/// An index into `string_bytes`.
pub const String = enum(u32) {
_,
};
/// An index into `string_bytes`.
pub const NullTerminatedString = enum(u32) {
/// This is distinct from `none` - it is a valid index that represents empty string.
empty = 0,
_,
/// An array of `NullTerminatedString` existing within the `extra` array.
/// This type exists to provide a struct with lifetime that is
/// not invalidated when items are added to the `InternPool`.
pub const Slice = struct {
start: u32,
len: u32,
pub fn get(slice: Slice, ip: *const InternPool) []NullTerminatedString {
return @ptrCast(ip.extra.items[slice.start..][0..slice.len]);
}
};
pub fn toString(self: NullTerminatedString) String {
return @enumFromInt(@intFromEnum(self));
}
pub fn toOptional(self: NullTerminatedString) OptionalNullTerminatedString {
return @enumFromInt(@intFromEnum(self));
}
const Adapter = struct {
strings: []const NullTerminatedString,
pub fn eql(ctx: @This(), a: NullTerminatedString, b_void: void, b_map_index: usize) bool {
_ = b_void;
return a == ctx.strings[b_map_index];
}
pub fn hash(ctx: @This(), a: NullTerminatedString) u32 {
_ = ctx;
return std.hash.uint32(@intFromEnum(a));
}
};
/// Compare based on integer value alone, ignoring the string contents.
pub fn indexLessThan(ctx: void, a: NullTerminatedString, b: NullTerminatedString) bool {
_ = ctx;
return @intFromEnum(a) < @intFromEnum(b);
}
pub fn toUnsigned(self: NullTerminatedString, ip: *const InternPool) ?u32 {
const s = ip.stringToSlice(self);
if (s.len > 1 and s[0] == '0') return null;
if (std.mem.indexOfScalar(u8, s, '_')) |_| return null;
return std.fmt.parseUnsigned(u32, s, 10) catch null;
}
const FormatData = struct {
string: NullTerminatedString,
ip: *const InternPool,
};
fn format(
data: FormatData,
comptime specifier: []const u8,
_: std.fmt.FormatOptions,
writer: anytype,
) @TypeOf(writer).Error!void {
const s = data.ip.stringToSlice(data.string);
if (comptime std.mem.eql(u8, specifier, "")) {
try writer.writeAll(s);
} else if (comptime std.mem.eql(u8, specifier, "i")) {
try writer.print("{}", .{std.zig.fmtId(s)});
} else @compileError("invalid format string '" ++ specifier ++ "' for '" ++ @typeName(NullTerminatedString) ++ "'");
}
pub fn fmt(self: NullTerminatedString, ip: *const InternPool) std.fmt.Formatter(format) {
return .{ .data = .{ .string = self, .ip = ip } };
}
};
/// An index into `string_bytes` which might be `none`.
pub const OptionalNullTerminatedString = enum(u32) {
/// This is distinct from `none` - it is a valid index that represents empty string.
empty = 0,
none = std.math.maxInt(u32),
_,
pub fn unwrap(oi: OptionalNullTerminatedString) ?NullTerminatedString {
if (oi == .none) return null;
return @enumFromInt(@intFromEnum(oi));
}
};
/// A single value captured in the closure of a namespace type. This is not a plain
/// `Index` because we must differentiate between the following cases:
/// * runtime-known value (where we store the type)
/// * comptime-known value (where we store the value)
/// * decl val (so that we can analyze the value lazily)
/// * decl ref (so that we can analyze the reference lazily)
pub const CaptureValue = packed struct(u32) {
tag: enum { @"comptime", runtime, decl_val, decl_ref },
idx: u30,
pub fn wrap(val: Unwrapped) CaptureValue {
return switch (val) {
.@"comptime" => |i| .{ .tag = .@"comptime", .idx = @intCast(@intFromEnum(i)) },
.runtime => |i| .{ .tag = .runtime, .idx = @intCast(@intFromEnum(i)) },
.decl_val => |i| .{ .tag = .decl_val, .idx = @intCast(@intFromEnum(i)) },
.decl_ref => |i| .{ .tag = .decl_ref, .idx = @intCast(@intFromEnum(i)) },
};
}
pub fn unwrap(val: CaptureValue) Unwrapped {
return switch (val.tag) {
.@"comptime" => .{ .@"comptime" = @enumFromInt(val.idx) },
.runtime => .{ .runtime = @enumFromInt(val.idx) },
.decl_val => .{ .decl_val = @enumFromInt(val.idx) },
.decl_ref => .{ .decl_ref = @enumFromInt(val.idx) },
};
}
pub const Unwrapped = union(enum) {
/// Index refers to the value.
@"comptime": Index,
/// Index refers to the type.
runtime: Index,
decl_val: DeclIndex,
decl_ref: DeclIndex,
};
pub const Slice = struct {
start: u32,
len: u32,
pub fn get(slice: Slice, ip: *const InternPool) []CaptureValue {
return @ptrCast(ip.extra.items[slice.start..][0..slice.len]);
}
};
};
pub const Key = union(enum) {
int_type: IntType,
ptr_type: PtrType,
array_type: ArrayType,
vector_type: VectorType,
opt_type: Index,
/// `anyframe->T`. The payload is the child type, which may be `none` to indicate
/// `anyframe`.
anyframe_type: Index,
error_union_type: ErrorUnionType,
simple_type: SimpleType,
/// This represents a struct that has been explicitly declared in source code,
/// or was created with `@Type`. It is unique and based on a declaration.
/// It may be a tuple, if declared like this: `struct {A, B, C}`.
struct_type: NamespaceType,
/// This is an anonymous struct or tuple type which has no corresponding
/// declaration. It is used for types that have no `struct` keyword in the
/// source code, and were not created via `@Type`.
anon_struct_type: AnonStructType,
union_type: NamespaceType,
opaque_type: NamespaceType,
enum_type: NamespaceType,
func_type: FuncType,
error_set_type: ErrorSetType,
/// The payload is the function body, either a `func_decl` or `func_instance`.
inferred_error_set_type: Index,
/// Typed `undefined`. This will never be `none`; untyped `undefined` is represented
/// via `simple_value` and has a named `Index` tag for it.
undef: Index,
simple_value: SimpleValue,
variable: Variable,
extern_func: ExternFunc,
func: Func,
int: Key.Int,
err: Error,
error_union: ErrorUnion,
enum_literal: NullTerminatedString,
/// A specific enum tag, indicated by the integer tag value.
enum_tag: EnumTag,
/// An empty enum or union. TODO: this value's existence is strange, because such a type in
/// reality has no values. See #15909.
/// Payload is the type for which we are an empty value.
empty_enum_value: Index,
float: Float,
ptr: Ptr,
slice: Slice,
opt: Opt,
/// An instance of a struct, array, or vector.
/// Each element/field stored as an `Index`.
/// In the case of sentinel-terminated arrays, the sentinel value *is* stored,
/// so the slice length will be one more than the type's array length.
aggregate: Aggregate,
/// An instance of a union.
un: Union,
/// A comptime function call with a memoized result.
memoized_call: Key.MemoizedCall,
pub const TypeValue = extern struct {
ty: Index,
val: Index,
};
pub const IntType = std.builtin.Type.Int;
/// Extern for hashing via memory reinterpretation.
pub const ErrorUnionType = extern struct {
error_set_type: Index,
payload_type: Index,
};
pub const ErrorSetType = struct {
/// Set of error names, sorted by null terminated string index.
names: NullTerminatedString.Slice,
/// This is ignored by `get` but will always be provided by `indexToKey`.
names_map: OptionalMapIndex = .none,
/// Look up field index based on field name.
pub fn nameIndex(self: ErrorSetType, ip: *const InternPool, name: NullTerminatedString) ?u32 {
const map = &ip.maps.items[@intFromEnum(self.names_map.unwrap().?)];
const adapter: NullTerminatedString.Adapter = .{ .strings = self.names.get(ip) };
const field_index = map.getIndexAdapted(name, adapter) orelse return null;
return @intCast(field_index);
}
};
/// Extern layout so it can be hashed with `std.mem.asBytes`.
pub const PtrType = extern struct {
child: Index,
sentinel: Index = .none,
flags: Flags = .{},
packed_offset: PackedOffset = .{ .bit_offset = 0, .host_size = 0 },
pub const VectorIndex = enum(u16) {
none = std.math.maxInt(u16),
runtime = std.math.maxInt(u16) - 1,
_,
};
pub const Flags = packed struct(u32) {
size: Size = .One,
/// `none` indicates the ABI alignment of the pointee_type. In this
/// case, this field *must* be set to `none`, otherwise the
/// `InternPool` equality and hashing functions will return incorrect
/// results.
alignment: Alignment = .none,
is_const: bool = false,
is_volatile: bool = false,
is_allowzero: bool = false,
/// See src/target.zig defaultAddressSpace function for how to obtain
/// an appropriate value for this field.
address_space: AddressSpace = .generic,
vector_index: VectorIndex = .none,
};
pub const PackedOffset = packed struct(u32) {
/// If this is non-zero it means the pointer points to a sub-byte
/// range of data, which is backed by a "host integer" with this
/// number of bytes.
/// When host_size=pointee_abi_size and bit_offset=0, this must be
/// represented with host_size=0 instead.
host_size: u16,
bit_offset: u16,
};
pub const Size = std.builtin.Type.Pointer.Size;
pub const AddressSpace = std.builtin.AddressSpace;
};
/// Extern so that hashing can be done via memory reinterpreting.
pub const ArrayType = extern struct {
len: u64,
child: Index,
sentinel: Index = .none,
};
/// Extern so that hashing can be done via memory reinterpreting.
pub const VectorType = extern struct {
len: u32,
child: Index,
};
pub const AnonStructType = struct {
types: Index.Slice,
/// This may be empty, indicating this is a tuple.
names: NullTerminatedString.Slice,
/// These elements may be `none`, indicating runtime-known.
values: Index.Slice,
pub fn isTuple(self: AnonStructType) bool {
return self.names.len == 0;
}
pub fn fieldName(
self: AnonStructType,
ip: *const InternPool,
index: u32,
) OptionalNullTerminatedString {
if (self.names.len == 0)
return .none;
return self.names.get(ip)[index].toOptional();
}
};
/// This is the hashmap key. To fetch other data associated with the type, see:
/// * `loadStructType`
/// * `loadUnionType`
/// * `loadEnumType`
/// * `loadOpaqueType`
pub const NamespaceType = union(enum) {
/// This type corresponds to an actual source declaration, e.g. `struct { ... }`.
/// It is hashed based on its ZIR instruction index and set of captures.
declared: struct {
/// A `struct_decl`, `union_decl`, `enum_decl`, or `opaque_decl` instruction.
zir_index: TrackedInst.Index,
/// The captured values of this type. These values must be fully resolved per the language spec.
captures: union(enum) {
owned: CaptureValue.Slice,
external: []const CaptureValue,
},
},
/// This type is an automatically-generated enum tag type for a union.
/// It is hashed based on the index of the union type it corresponds to.
generated_tag: struct {
/// The union for which this is a tag type.
union_type: Index,
},
/// This type originates from a reification via `@Type`.
/// It is hased based on its ZIR instruction index and fields, attributes, etc.
/// To avoid making this key overly complex, the type-specific data is hased by Sema.
reified: struct {
/// A `reify` instruction.
zir_index: TrackedInst.Index,
/// A hash of this type's attributes, fields, etc, generated by Sema.
type_hash: u64,
},
/// This type is `@TypeOf(.{})`.
/// TODO: can we change the language spec to not special-case this type?
empty_struct: void,
};
pub const FuncType = struct {
param_types: Index.Slice,
return_type: Index,
/// Tells whether a parameter is comptime. See `paramIsComptime` helper
/// method for accessing this.
comptime_bits: u32,
/// Tells whether a parameter is noalias. See `paramIsNoalias` helper
/// method for accessing this.
noalias_bits: u32,
cc: std.builtin.CallingConvention,
is_var_args: bool,
is_generic: bool,
is_noinline: bool,
cc_is_generic: bool,
section_is_generic: bool,
addrspace_is_generic: bool,
pub fn paramIsComptime(self: @This(), i: u5) bool {
assert(i < self.param_types.len);
return @as(u1, @truncate(self.comptime_bits >> i)) != 0;
}
pub fn paramIsNoalias(self: @This(), i: u5) bool {
assert(i < self.param_types.len);
return @as(u1, @truncate(self.noalias_bits >> i)) != 0;
}
pub fn eql(a: FuncType, b: FuncType, ip: *const InternPool) bool {
return std.mem.eql(Index, a.param_types.get(ip), b.param_types.get(ip)) and
a.return_type == b.return_type and
a.comptime_bits == b.comptime_bits and
a.noalias_bits == b.noalias_bits and
a.cc == b.cc and
a.is_var_args == b.is_var_args and
a.is_generic == b.is_generic and
a.is_noinline == b.is_noinline;
}
pub fn hash(self: FuncType, hasher: *Hash, ip: *const InternPool) void {
for (self.param_types.get(ip)) |param_type| {
std.hash.autoHash(hasher, param_type);
}
std.hash.autoHash(hasher, self.return_type);
std.hash.autoHash(hasher, self.comptime_bits);
std.hash.autoHash(hasher, self.noalias_bits);
std.hash.autoHash(hasher, self.cc);
std.hash.autoHash(hasher, self.is_var_args);
std.hash.autoHash(hasher, self.is_generic);
std.hash.autoHash(hasher, self.is_noinline);
}
};
pub const Variable = struct {
ty: Index,
init: Index,
decl: DeclIndex,
lib_name: OptionalNullTerminatedString,
is_extern: bool,
is_const: bool,
is_threadlocal: bool,
is_weak_linkage: bool,
};
pub const ExternFunc = struct {
ty: Index,
/// The Decl that corresponds to the function itself.
decl: DeclIndex,
/// Library name if specified.
/// For example `extern "c" fn write(...) usize` would have 'c' as library name.
/// Index into the string table bytes.
lib_name: OptionalNullTerminatedString,
};
pub const Func = struct {
/// In the case of a generic function, this type will potentially have fewer parameters
/// than the generic owner's type, because the comptime parameters will be deleted.
ty: Index,
/// If this is a function body that has been coerced to a different type, for example
/// ```
/// fn f2() !void {}
/// const f: fn()anyerror!void = f2;
/// ```
/// then it contains the original type of the function body.
uncoerced_ty: Index,
/// Index into extra array of the `FuncAnalysis` corresponding to this function.
/// Used for mutating that data.
analysis_extra_index: u32,
/// Index into extra array of the `zir_body_inst` corresponding to this function.
/// Used for mutating that data.
zir_body_inst_extra_index: u32,
/// Index into extra array of the resolved inferred error set for this function.
/// Used for mutating that data.
/// 0 when the function does not have an inferred error set.
resolved_error_set_extra_index: u32,
/// When a generic function is instantiated, branch_quota is inherited from the
/// active Sema context. Importantly, this value is also updated when an existing
/// generic function instantiation is found and called.
/// This field contains the index into the extra array of this value,
/// so that it can be mutated.
/// This will be 0 when the function is not a generic function instantiation.
branch_quota_extra_index: u32,
/// The Decl that corresponds to the function itself.
owner_decl: DeclIndex,
/// The ZIR instruction that is a function instruction. Use this to find
/// the body. We store this rather than the body directly so that when ZIR
/// is regenerated on update(), we can map this to the new corresponding
/// ZIR instruction.
zir_body_inst: TrackedInst.Index,
/// Relative to owner Decl.
lbrace_line: u32,
/// Relative to owner Decl.
rbrace_line: u32,
lbrace_column: u32,
rbrace_column: u32,
/// The `func_decl` which is the generic function from whence this instance was spawned.
/// If this is `none` it means the function is not a generic instantiation.
generic_owner: Index,
/// If this is a generic function instantiation, this will be non-empty.
/// Corresponds to the parameters of the `generic_owner` type, which
/// may have more parameters than `ty`.
/// Each element is the comptime-known value the generic function was instantiated with,
/// or `none` if the element is runtime-known.
/// TODO: as a follow-up optimization, don't store `none` values here since that data
/// is redundant with `comptime_bits` stored elsewhere.
comptime_args: Index.Slice,
/// Returns a pointer that becomes invalid after any additions to the `InternPool`.
pub fn analysis(func: *const Func, ip: *const InternPool) *FuncAnalysis {
return @ptrCast(&ip.extra.items[func.analysis_extra_index]);
}
/// Returns a pointer that becomes invalid after any additions to the `InternPool`.
pub fn zirBodyInst(func: *const Func, ip: *const InternPool) *TrackedInst.Index {
return @ptrCast(&ip.extra.items[func.zir_body_inst_extra_index]);
}
/// Returns a pointer that becomes invalid after any additions to the `InternPool`.
pub fn branchQuota(func: *const Func, ip: *const InternPool) *u32 {
return &ip.extra.items[func.branch_quota_extra_index];
}
/// Returns a pointer that becomes invalid after any additions to the `InternPool`.
pub fn resolvedErrorSet(func: *const Func, ip: *const InternPool) *Index {
assert(func.analysis(ip).inferred_error_set);
return @ptrCast(&ip.extra.items[func.resolved_error_set_extra_index]);
}
};
pub const Int = struct {
ty: Index,
storage: Storage,
pub const Storage = union(enum) {
u64: u64,
i64: i64,
big_int: BigIntConst,
lazy_align: Index,
lazy_size: Index,
/// Big enough to fit any non-BigInt value
pub const BigIntSpace = struct {
/// The +1 is headroom so that operations such as incrementing once
/// or decrementing once are possible without using an allocator.
limbs: [(@sizeOf(u64) / @sizeOf(std.math.big.Limb)) + 1]std.math.big.Limb,
};
pub fn toBigInt(storage: Storage, space: *BigIntSpace) BigIntConst {
return switch (storage) {
.big_int => |x| x,
inline .u64, .i64 => |x| BigIntMutable.init(&space.limbs, x).toConst(),
.lazy_align, .lazy_size => unreachable,
};
}
};
};
pub const Error = extern struct {
ty: Index,
name: NullTerminatedString,
};
pub const ErrorUnion = struct {
ty: Index,
val: Value,
pub const Value = union(enum) {
err_name: NullTerminatedString,
payload: Index,
};
};
pub const EnumTag = extern struct {
/// The enum type.
ty: Index,
/// The integer tag value which has the integer tag type of the enum.
int: Index,
};
pub const Float = struct {
ty: Index,
/// The storage used must match the size of the float type being represented.
storage: Storage,
pub const Storage = union(enum) {
f16: f16,
f32: f32,
f64: f64,
f80: f80,
f128: f128,
};
};
pub const Ptr = struct {
/// This is the pointer type, not the element type.
ty: Index,
/// The value of the address that the pointer points to.
addr: Addr,
pub const Addr = union(enum) {
const Tag = @typeInfo(Addr).Union.tag_type.?;
decl: DeclIndex,
comptime_alloc: ComptimeAllocIndex,
anon_decl: AnonDecl,
comptime_field: Index,
int: Index,
eu_payload: Index,
opt_payload: Index,
elem: BaseIndex,
field: BaseIndex,
pub const MutDecl = struct {
decl: DeclIndex,
runtime_index: RuntimeIndex,
};
pub const BaseIndex = struct {
base: Index,
index: u64,
};
pub const AnonDecl = extern struct {
val: Index,
/// Contains the canonical pointer type of the anonymous
/// declaration. This may equal `ty` of the `Ptr` or it may be
/// different. Importantly, when lowering the anonymous decl,
/// the original pointer type alignment must be used.
orig_ty: Index,
};
};
};
pub const Slice = struct {
/// This is the slice type, not the element type.
ty: Index,
/// The slice's `ptr` field. Must be a many-ptr with the same properties as `ty`.
ptr: Index,
/// The slice's `len` field. Must be a `usize`.
len: Index,
};
/// `null` is represented by the `val` field being `none`.
pub const Opt = extern struct {
/// This is the optional type; not the payload type.
ty: Index,
/// This could be `none`, indicating the optional is `null`.
val: Index,
};
pub const Union = extern struct {
/// This is the union type; not the field type.
ty: Index,
/// Indicates the active field. This could be `none`, which indicates the tag is not known. `none` is only a valid value for extern and packed unions.
/// In those cases, the type of `val` is:
/// extern: a u8 array of the same byte length as the union
/// packed: an unsigned integer with the same bit size as the union
tag: Index,
/// The value of the active field.
val: Index,
};
pub const Aggregate = struct {
ty: Index,
storage: Storage,
pub const Storage = union(enum) {
bytes: []const u8,
elems: []const Index,
repeated_elem: Index,
pub fn values(self: *const Storage) []const Index {
return switch (self.*) {
.bytes => &.{},
.elems => |elems| elems,
.repeated_elem => |*elem| @as(*const [1]Index, elem),
};
}
};
};
pub const MemoizedCall = struct {
func: Index,
arg_values: []const Index,
result: Index,
};
pub fn hash32(key: Key, ip: *const InternPool) u32 {
return @truncate(key.hash64(ip));
}
pub fn hash64(key: Key, ip: *const InternPool) u64 {
const asBytes = std.mem.asBytes;
const KeyTag = @typeInfo(Key).Union.tag_type.?;
const seed = @intFromEnum(@as(KeyTag, key));
return switch (key) {
// TODO: assert no padding in these types
inline .ptr_type,
.array_type,
.vector_type,
.opt_type,
.anyframe_type,
.error_union_type,
.simple_type,
.simple_value,
.opt,
.undef,
.err,
.enum_literal,
.enum_tag,
.empty_enum_value,
.inferred_error_set_type,
.un,
=> |x| Hash.hash(seed, asBytes(&x)),
.int_type => |x| Hash.hash(seed + @intFromEnum(x.signedness), asBytes(&x.bits)),
.error_union => |x| switch (x.val) {
.err_name => |y| Hash.hash(seed + 0, asBytes(&x.ty) ++ asBytes(&y)),
.payload => |y| Hash.hash(seed + 1, asBytes(&x.ty) ++ asBytes(&y)),
},
.variable => |variable| Hash.hash(seed, asBytes(&variable.decl)),
.opaque_type,
.enum_type,
.union_type,
.struct_type,
=> |namespace_type| {
var hasher = Hash.init(seed);
std.hash.autoHash(&hasher, std.meta.activeTag(namespace_type));
switch (namespace_type) {
.declared => |declared| {
std.hash.autoHash(&hasher, declared.zir_index);
const captures = switch (declared.captures) {
.owned => |cvs| cvs.get(ip),
.external => |cvs| cvs,
};
for (captures) |cv| {
std.hash.autoHash(&hasher, cv);
}
},
.generated_tag => |generated_tag| {
std.hash.autoHash(&hasher, generated_tag.union_type);
},
.reified => |reified| {
std.hash.autoHash(&hasher, reified.zir_index);
std.hash.autoHash(&hasher, reified.type_hash);
},
.empty_struct => {},
}
return hasher.final();
},
.int => |int| {
var hasher = Hash.init(seed);
// Canonicalize all integers by converting them to BigIntConst.
switch (int.storage) {
.u64, .i64, .big_int => {
var buffer: Key.Int.Storage.BigIntSpace = undefined;
const big_int = int.storage.toBigInt(&buffer);
std.hash.autoHash(&hasher, int.ty);
std.hash.autoHash(&hasher, big_int.positive);
for (big_int.limbs) |limb| std.hash.autoHash(&hasher, limb);
},
.lazy_align, .lazy_size => |lazy_ty| {
std.hash.autoHash(
&hasher,
@as(@typeInfo(Key.Int.Storage).Union.tag_type.?, int.storage),
);
std.hash.autoHash(&hasher, lazy_ty);
},
}
return hasher.final();
},
.float => |float| {
var hasher = Hash.init(seed);
std.hash.autoHash(&hasher, float.ty);
switch (float.storage) {
inline else => |val| std.hash.autoHash(
&hasher,
@as(std.meta.Int(.unsigned, @bitSizeOf(@TypeOf(val))), @bitCast(val)),
),
}
return hasher.final();
},
.slice => |slice| Hash.hash(seed, asBytes(&slice.ty) ++ asBytes(&slice.ptr) ++ asBytes(&slice.len)),
.ptr => |ptr| {
// Int-to-ptr pointers are hashed separately than decl-referencing pointers.
// This is sound due to pointer provenance rules.
const addr: @typeInfo(Key.Ptr.Addr).Union.tag_type.? = ptr.addr;
const seed2 = seed + @intFromEnum(addr);
const common = asBytes(&ptr.ty);
return switch (ptr.addr) {
inline .decl,
.comptime_alloc,
.anon_decl,
.int,
.eu_payload,
.opt_payload,
.comptime_field,
=> |x| Hash.hash(seed2, common ++ asBytes(&x)),
.elem, .field => |x| Hash.hash(
seed2,
common ++ asBytes(&x.base) ++ asBytes(&x.index),
),
};
},
.aggregate => |aggregate| {
var hasher = Hash.init(seed);
std.hash.autoHash(&hasher, aggregate.ty);
const len = ip.aggregateTypeLen(aggregate.ty);
const child = switch (ip.indexToKey(aggregate.ty)) {
.array_type => |array_type| array_type.child,
.vector_type => |vector_type| vector_type.child,
.anon_struct_type, .struct_type => .none,
else => unreachable,
};
if (child == .u8_type) {
switch (aggregate.storage) {
.bytes => |bytes| for (bytes[0..@intCast(len)]) |byte| {
std.hash.autoHash(&hasher, KeyTag.int);
std.hash.autoHash(&hasher, byte);
},
.elems => |elems| for (elems[0..@intCast(len)]) |elem| {
const elem_key = ip.indexToKey(elem);
std.hash.autoHash(&hasher, @as(KeyTag, elem_key));
switch (elem_key) {
.undef => {},
.int => |int| std.hash.autoHash(
&hasher,
@as(u8, @intCast(int.storage.u64)),
),
else => unreachable,
}
},
.repeated_elem => |elem| {
const elem_key = ip.indexToKey(elem);
var remaining = len;
while (remaining > 0) : (remaining -= 1) {
std.hash.autoHash(&hasher, @as(KeyTag, elem_key));
switch (elem_key) {
.undef => {},
.int => |int| std.hash.autoHash(
&hasher,
@as(u8, @intCast(int.storage.u64)),
),
else => unreachable,
}
}
},
}
return hasher.final();
}
switch (aggregate.storage) {
.bytes => unreachable,
.elems => |elems| for (elems[0..@as(usize, @intCast(len))]) |elem|
std.hash.autoHash(&hasher, elem),
.repeated_elem => |elem| {
var remaining = len;
while (remaining > 0) : (remaining -= 1) std.hash.autoHash(&hasher, elem);
},
}
return hasher.final();
},
.error_set_type => |x| Hash.hash(seed, std.mem.sliceAsBytes(x.names.get(ip))),
.anon_struct_type => |anon_struct_type| {
var hasher = Hash.init(seed);
for (anon_struct_type.types.get(ip)) |elem| std.hash.autoHash(&hasher, elem);
for (anon_struct_type.values.get(ip)) |elem| std.hash.autoHash(&hasher, elem);
for (anon_struct_type.names.get(ip)) |elem| std.hash.autoHash(&hasher, elem);
return hasher.final();
},
.func_type => |func_type| {
var hasher = Hash.init(seed);
func_type.hash(&hasher, ip);
return hasher.final();
},
.memoized_call => |memoized_call| {
var hasher = Hash.init(seed);
std.hash.autoHash(&hasher, memoized_call.func);
for (memoized_call.arg_values) |arg| std.hash.autoHash(&hasher, arg);
return hasher.final();
},
.func => |func| {
// In the case of a function with an inferred error set, we
// must not include the inferred error set type in the hash,
// otherwise we would get false negatives for interning generic
// function instances which have inferred error sets.
if (func.generic_owner == .none and func.resolved_error_set_extra_index == 0) {
const bytes = asBytes(&func.owner_decl) ++ asBytes(&func.ty) ++
[1]u8{@intFromBool(func.uncoerced_ty == func.ty)};
return Hash.hash(seed, bytes);
}
var hasher = Hash.init(seed);
std.hash.autoHash(&hasher, func.generic_owner);
std.hash.autoHash(&hasher, func.uncoerced_ty == func.ty);
for (func.comptime_args.get(ip)) |arg| std.hash.autoHash(&hasher, arg);
if (func.resolved_error_set_extra_index == 0) {
std.hash.autoHash(&hasher, func.ty);
} else {
var ty_info = ip.indexToFuncType(func.ty).?;
ty_info.return_type = ip.errorUnionPayload(ty_info.return_type);
ty_info.hash(&hasher, ip);
}
return hasher.final();
},
.extern_func => |x| Hash.hash(seed, asBytes(&x.ty) ++ asBytes(&x.decl)),
};
}
pub fn eql(a: Key, b: Key, ip: *const InternPool) bool {
const KeyTag = @typeInfo(Key).Union.tag_type.?;
const a_tag: KeyTag = a;
const b_tag: KeyTag = b;
if (a_tag != b_tag) return false;
switch (a) {
.int_type => |a_info| {
const b_info = b.int_type;
return std.meta.eql(a_info, b_info);
},
.ptr_type => |a_info| {
const b_info = b.ptr_type;
return std.meta.eql(a_info, b_info);
},
.array_type => |a_info| {
const b_info = b.array_type;
return std.meta.eql(a_info, b_info);
},
.vector_type => |a_info| {
const b_info = b.vector_type;
return std.meta.eql(a_info, b_info);
},
.opt_type => |a_info| {
const b_info = b.opt_type;
return a_info == b_info;
},
.anyframe_type => |a_info| {
const b_info = b.anyframe_type;
return a_info == b_info;
},
.error_union_type => |a_info| {
const b_info = b.error_union_type;
return std.meta.eql(a_info, b_info);
},
.simple_type => |a_info| {
const b_info = b.simple_type;
return a_info == b_info;
},
.simple_value => |a_info| {
const b_info = b.simple_value;
return a_info == b_info;
},
.undef => |a_info| {
const b_info = b.undef;
return a_info == b_info;
},
.opt => |a_info| {
const b_info = b.opt;
return std.meta.eql(a_info, b_info);
},
.un => |a_info| {
const b_info = b.un;
return std.meta.eql(a_info, b_info);
},
.err => |a_info| {
const b_info = b.err;
return std.meta.eql(a_info, b_info);
},
.error_union => |a_info| {
const b_info = b.error_union;
return std.meta.eql(a_info, b_info);
},
.enum_literal => |a_info| {
const b_info = b.enum_literal;
return a_info == b_info;
},
.enum_tag => |a_info| {
const b_info = b.enum_tag;
return std.meta.eql(a_info, b_info);
},
.empty_enum_value => |a_info| {
const b_info = b.empty_enum_value;
return a_info == b_info;
},
.variable => |a_info| {
const b_info = b.variable;
return a_info.decl == b_info.decl;
},
.extern_func => |a_info| {
const b_info = b.extern_func;
return a_info.ty == b_info.ty and a_info.decl == b_info.decl;
},
.func => |a_info| {
const b_info = b.func;
if (a_info.generic_owner != b_info.generic_owner)
return false;
if (a_info.generic_owner == .none) {
if (a_info.owner_decl != b_info.owner_decl)
return false;
} else {
if (!std.mem.eql(
Index,
a_info.comptime_args.get(ip),
b_info.comptime_args.get(ip),
)) return false;
}
if ((a_info.ty == a_info.uncoerced_ty) !=
(b_info.ty == b_info.uncoerced_ty))
{
return false;
}
if (a_info.ty == b_info.ty)
return true;
// There is one case where the types may be inequal but we
// still want to find the same function body instance. In the
// case of the functions having an inferred error set, the key
// used to find an existing function body will necessarily have
// a unique inferred error set type, because it refers to the
// function body InternPool Index. To make this case work we
// omit the inferred error set from the equality check.
if (a_info.resolved_error_set_extra_index == 0 or
b_info.resolved_error_set_extra_index == 0)
{
return false;
}
var a_ty_info = ip.indexToFuncType(a_info.ty).?;
a_ty_info.return_type = ip.errorUnionPayload(a_ty_info.return_type);
var b_ty_info = ip.indexToFuncType(b_info.ty).?;
b_ty_info.return_type = ip.errorUnionPayload(b_ty_info.return_type);
return a_ty_info.eql(b_ty_info, ip);
},
.slice => |a_info| {
const b_info = b.slice;
if (a_info.ty != b_info.ty) return false;
if (a_info.ptr != b_info.ptr) return false;
if (a_info.len != b_info.len) return false;
return true;
},
.ptr => |a_info| {
const b_info = b.ptr;
if (a_info.ty != b_info.ty) return false;
const AddrTag = @typeInfo(Key.Ptr.Addr).Union.tag_type.?;
if (@as(AddrTag, a_info.addr) != @as(AddrTag, b_info.addr)) return false;
return switch (a_info.addr) {
.decl => |a_decl| a_decl == b_info.addr.decl,
.comptime_alloc => |a_alloc| a_alloc == b_info.addr.comptime_alloc,
.anon_decl => |ad| ad.val == b_info.addr.anon_decl.val and
ad.orig_ty == b_info.addr.anon_decl.orig_ty,
.int => |a_int| a_int == b_info.addr.int,
.eu_payload => |a_eu_payload| a_eu_payload == b_info.addr.eu_payload,
.opt_payload => |a_opt_payload| a_opt_payload == b_info.addr.opt_payload,
.comptime_field => |a_comptime_field| a_comptime_field == b_info.addr.comptime_field,
.elem => |a_elem| std.meta.eql(a_elem, b_info.addr.elem),
.field => |a_field| std.meta.eql(a_field, b_info.addr.field),
};
},
.int => |a_info| {
const b_info = b.int;
if (a_info.ty != b_info.ty)
return false;
return switch (a_info.storage) {
.u64 => |aa| switch (b_info.storage) {
.u64 => |bb| aa == bb,
.i64 => |bb| aa == bb,
.big_int => |bb| bb.orderAgainstScalar(aa) == .eq,
.lazy_align, .lazy_size => false,
},
.i64 => |aa| switch (b_info.storage) {
.u64 => |bb| aa == bb,
.i64 => |bb| aa == bb,
.big_int => |bb| bb.orderAgainstScalar(aa) == .eq,
.lazy_align, .lazy_size => false,
},
.big_int => |aa| switch (b_info.storage) {
.u64 => |bb| aa.orderAgainstScalar(bb) == .eq,
.i64 => |bb| aa.orderAgainstScalar(bb) == .eq,
.big_int => |bb| aa.eql(bb),
.lazy_align, .lazy_size => false,
},
.lazy_align => |aa| switch (b_info.storage) {
.u64, .i64, .big_int, .lazy_size => false,
.lazy_align => |bb| aa == bb,
},
.lazy_size => |aa| switch (b_info.storage) {
.u64, .i64, .big_int, .lazy_align => false,
.lazy_size => |bb| aa == bb,
},
};
},
.float => |a_info| {
const b_info = b.float;
if (a_info.ty != b_info.ty)
return false;
if (a_info.ty == .c_longdouble_type and a_info.storage != .f80) {
// These are strange: we'll sometimes represent them as f128, even if the
// underlying type is smaller. f80 is an exception: see float_c_longdouble_f80.
const a_val = switch (a_info.storage) {
inline else => |val| @as(u128, @bitCast(@as(f128, @floatCast(val)))),
};
const b_val = switch (b_info.storage) {
inline else => |val| @as(u128, @bitCast(@as(f128, @floatCast(val)))),
};
return a_val == b_val;
}
const StorageTag = @typeInfo(Key.Float.Storage).Union.tag_type.?;
assert(@as(StorageTag, a_info.storage) == @as(StorageTag, b_info.storage));
switch (a_info.storage) {
inline else => |val, tag| {
const Bits = std.meta.Int(.unsigned, @bitSizeOf(@TypeOf(val)));
const a_bits: Bits = @bitCast(val);
const b_bits: Bits = @bitCast(@field(b_info.storage, @tagName(tag)));
return a_bits == b_bits;
},
}
},
inline .opaque_type, .enum_type, .union_type, .struct_type => |a_info, a_tag_ct| {
const b_info = @field(b, @tagName(a_tag_ct));
if (std.meta.activeTag(a_info) != b_info) return false;
switch (a_info) {
.declared => |a_d| {
const b_d = b_info.declared;
if (a_d.zir_index != b_d.zir_index) return false;
const a_captures = switch (a_d.captures) {
.owned => |s| s.get(ip),
.external => |cvs| cvs,
};
const b_captures = switch (b_d.captures) {
.owned => |s| s.get(ip),
.external => |cvs| cvs,
};
return std.mem.eql(u32, @ptrCast(a_captures), @ptrCast(b_captures));
},
.generated_tag => |a_gt| return a_gt.union_type == b_info.generated_tag.union_type,
.reified => |a_r| {
const b_r = b_info.reified;
return a_r.zir_index == b_r.zir_index and
a_r.type_hash == b_r.type_hash;
},
.empty_struct => return true,
}
},
.aggregate => |a_info| {
const b_info = b.aggregate;
if (a_info.ty != b_info.ty) return false;
const len = ip.aggregateTypeLen(a_info.ty);
const StorageTag = @typeInfo(Key.Aggregate.Storage).Union.tag_type.?;
if (@as(StorageTag, a_info.storage) != @as(StorageTag, b_info.storage)) {
for (0..@as(usize, @intCast(len))) |elem_index| {
const a_elem = switch (a_info.storage) {
.bytes => |bytes| ip.getIfExists(.{ .int = .{
.ty = .u8_type,
.storage = .{ .u64 = bytes[elem_index] },
} }) orelse return false,
.elems => |elems| elems[elem_index],
.repeated_elem => |elem| elem,
};
const b_elem = switch (b_info.storage) {
.bytes => |bytes| ip.getIfExists(.{ .int = .{
.ty = .u8_type,
.storage = .{ .u64 = bytes[elem_index] },
} }) orelse return false,
.elems => |elems| elems[elem_index],
.repeated_elem => |elem| elem,
};
if (a_elem != b_elem) return false;
}
return true;
}
switch (a_info.storage) {
.bytes => |a_bytes| {
const b_bytes = b_info.storage.bytes;
return std.mem.eql(
u8,
a_bytes[0..@as(usize, @intCast(len))],
b_bytes[0..@as(usize, @intCast(len))],
);
},
.elems => |a_elems| {
const b_elems = b_info.storage.elems;
return std.mem.eql(
Index,
a_elems[0..@as(usize, @intCast(len))],
b_elems[0..@as(usize, @intCast(len))],
);
},
.repeated_elem => |a_elem| {
const b_elem = b_info.storage.repeated_elem;
return a_elem == b_elem;
},
}
},
.anon_struct_type => |a_info| {
const b_info = b.anon_struct_type;
return std.mem.eql(Index, a_info.types.get(ip), b_info.types.get(ip)) and
std.mem.eql(Index, a_info.values.get(ip), b_info.values.get(ip)) and
std.mem.eql(NullTerminatedString, a_info.names.get(ip), b_info.names.get(ip));
},
.error_set_type => |a_info| {
const b_info = b.error_set_type;
return std.mem.eql(NullTerminatedString, a_info.names.get(ip), b_info.names.get(ip));
},
.inferred_error_set_type => |a_info| {
const b_info = b.inferred_error_set_type;
return a_info == b_info;
},
.func_type => |a_info| {
const b_info = b.func_type;
return Key.FuncType.eql(a_info, b_info, ip);
},
.memoized_call => |a_info| {
const b_info = b.memoized_call;
return a_info.func == b_info.func and
std.mem.eql(Index, a_info.arg_values, b_info.arg_values);
},
}
}
pub fn typeOf(key: Key) Index {
return switch (key) {
.int_type,
.ptr_type,
.array_type,
.vector_type,
.opt_type,
.anyframe_type,
.error_union_type,
.error_set_type,
.inferred_error_set_type,
.simple_type,
.struct_type,
.union_type,
.opaque_type,
.enum_type,
.anon_struct_type,
.func_type,
=> .type_type,
inline .ptr,
.slice,
.int,
.float,
.opt,
.variable,
.extern_func,
.func,
.err,
.error_union,
.enum_tag,
.aggregate,
.un,
=> |x| x.ty,
.enum_literal => .enum_literal_type,
.undef => |x| x,
.empty_enum_value => |x| x,
.simple_value => |s| switch (s) {
.undefined => .undefined_type,
.void => .void_type,
.null => .null_type,
.false, .true => .bool_type,
.empty_struct => .empty_struct_type,
.@"unreachable" => .noreturn_type,
.generic_poison => .generic_poison_type,
},
.memoized_call => unreachable,
};
}
};
pub const RequiresComptime = enum(u2) { no, yes, unknown, wip };
// Unlike `Tag.TypeUnion` which is an encoding, and `Key.UnionType` which is a
// minimal hashmap key, this type is a convenience type that contains info
// needed by semantic analysis.
pub const LoadedUnionType = struct {
/// The index of the `Tag.TypeUnion` payload.
extra_index: u32,
/// The Decl that corresponds to the union itself.
decl: DeclIndex,
/// Represents the declarations inside this union.
namespace: OptionalNamespaceIndex,
/// The enum tag type.
enum_tag_ty: Index,
/// List of field types in declaration order.
/// These are `none` until `status` is `have_field_types` or `have_layout`.
field_types: Index.Slice,
/// List of field alignments in declaration order.
/// `none` means the ABI alignment of the type.
/// If this slice has length 0 it means all elements are `none`.
field_aligns: Alignment.Slice,
/// Index of the union_decl or reify ZIR instruction.
zir_index: TrackedInst.Index,
captures: CaptureValue.Slice,
pub const RuntimeTag = enum(u2) {
none,
safety,
tagged,
pub fn hasTag(self: RuntimeTag) bool {
return switch (self) {
.none => false,
.tagged, .safety => true,
};
}
};
pub const Status = enum(u3) {
none,
field_types_wip,
have_field_types,
layout_wip,
have_layout,
fully_resolved_wip,
/// The types and all its fields have had their layout resolved.
/// Even through pointer, which `have_layout` does not ensure.
fully_resolved,
pub fn haveFieldTypes(status: Status) bool {
return switch (status) {
.none,
.field_types_wip,
=> false,
.have_field_types,
.layout_wip,
.have_layout,
.fully_resolved_wip,
.fully_resolved,
=> true,
};
}
pub fn haveLayout(status: Status) bool {
return switch (status) {
.none,
.field_types_wip,
.have_field_types,
.layout_wip,
=> false,
.have_layout,
.fully_resolved_wip,
.fully_resolved,
=> true,
};
}
};
pub fn loadTagType(self: LoadedUnionType, ip: *InternPool) LoadedEnumType {
return ip.loadEnumType(self.enum_tag_ty);
}
/// Pointer to an enum type which is used for the tag of the union.
/// This type is created even for untagged unions, even when the memory
/// layout does not store the tag.
/// Whether zig chooses this type or the user specifies it, it is stored here.
/// This will be set to the null type until status is `have_field_types`.
/// This accessor is provided so that the tag type can be mutated, and so that
/// when it is mutated, the mutations are observed.
/// The returned pointer expires with any addition to the `InternPool`.
pub fn tagTypePtr(self: LoadedUnionType, ip: *const InternPool) *Index {
const field_index = std.meta.fieldIndex(Tag.TypeUnion, "tag_ty").?;
return @ptrCast(&ip.extra.items[self.extra_index + field_index]);
}
/// The returned pointer expires with any addition to the `InternPool`.
pub fn flagsPtr(self: LoadedUnionType, ip: *const InternPool) *Tag.TypeUnion.Flags {
const field_index = std.meta.fieldIndex(Tag.TypeUnion, "flags").?;
return @ptrCast(&ip.extra.items[self.extra_index + field_index]);
}
/// The returned pointer expires with any addition to the `InternPool`.
pub fn size(self: LoadedUnionType, ip: *const InternPool) *u32 {
const field_index = std.meta.fieldIndex(Tag.TypeUnion, "size").?;
return &ip.extra.items[self.extra_index + field_index];
}
/// The returned pointer expires with any addition to the `InternPool`.
pub fn padding(self: LoadedUnionType, ip: *const InternPool) *u32 {
const field_index = std.meta.fieldIndex(Tag.TypeUnion, "padding").?;
return &ip.extra.items[self.extra_index + field_index];
}
pub fn hasTag(self: LoadedUnionType, ip: *const InternPool) bool {
return self.flagsPtr(ip).runtime_tag.hasTag();
}
pub fn haveFieldTypes(self: LoadedUnionType, ip: *const InternPool) bool {
return self.flagsPtr(ip).status.haveFieldTypes();
}
pub fn haveLayout(self: LoadedUnionType, ip: *const InternPool) bool {
return self.flagsPtr(ip).status.haveLayout();
}
pub fn getLayout(self: LoadedUnionType, ip: *const InternPool) std.builtin.Type.ContainerLayout {
return self.flagsPtr(ip).layout;
}
pub fn fieldAlign(self: LoadedUnionType, ip: *const InternPool, field_index: u32) Alignment {
if (self.field_aligns.len == 0) return .none;
return self.field_aligns.get(ip)[field_index];
}
/// This does not mutate the field of LoadedUnionType.
pub fn setZirIndex(self: LoadedUnionType, ip: *InternPool, new_zir_index: TrackedInst.Index.Optional) void {
const flags_field_index = std.meta.fieldIndex(Tag.TypeUnion, "flags").?;
const zir_index_field_index = std.meta.fieldIndex(Tag.TypeUnion, "zir_index").?;
const ptr: *TrackedInst.Index.Optional =
@ptrCast(&ip.extra.items[self.flags_index - flags_field_index + zir_index_field_index]);
ptr.* = new_zir_index;
}
pub fn setFieldTypes(self: LoadedUnionType, ip: *const InternPool, types: []const Index) void {
@memcpy(self.field_types.get(ip), types);
}
pub fn setFieldAligns(self: LoadedUnionType, ip: *const InternPool, aligns: []const Alignment) void {
if (aligns.len == 0) return;
assert(self.flagsPtr(ip).any_aligned_fields);
@memcpy(self.field_aligns.get(ip), aligns);
}
};
pub fn loadUnionType(ip: *const InternPool, index: Index) LoadedUnionType {
const data = ip.items.items(.data)[@intFromEnum(index)];
const type_union = ip.extraDataTrail(Tag.TypeUnion, data);
const fields_len = type_union.data.fields_len;
var extra_index = type_union.end;
const captures_len = if (type_union.data.flags.any_captures) c: {
const len = ip.extra.items[extra_index];
extra_index += 1;
break :c len;
} else 0;
const captures: CaptureValue.Slice = .{
.start = extra_index,
.len = captures_len,
};
extra_index += captures_len;
if (type_union.data.flags.is_reified) {
extra_index += 2; // PackedU64
}
const field_types: Index.Slice = .{
.start = extra_index,
.len = fields_len,
};
extra_index += fields_len;
const field_aligns: Alignment.Slice = if (type_union.data.flags.any_aligned_fields) a: {
const a: Alignment.Slice = .{
.start = extra_index,
.len = fields_len,
};
extra_index += std.math.divCeil(u32, fields_len, 4) catch unreachable;
break :a a;
} else .{ .start = 0, .len = 0 };
return .{
.extra_index = data,
.decl = type_union.data.decl,
.namespace = type_union.data.namespace,
.enum_tag_ty = type_union.data.tag_ty,
.field_types = field_types,
.field_aligns = field_aligns,
.zir_index = type_union.data.zir_index,
.captures = captures,
};
}
pub const LoadedStructType = struct {
/// The index of the `Tag.TypeStruct` or `Tag.TypeStructPacked` payload.
extra_index: u32,
/// The struct's owner Decl. `none` when the struct is `@TypeOf(.{})`.
decl: OptionalDeclIndex,
/// `none` when the struct has no declarations.
namespace: OptionalNamespaceIndex,
/// Index of the `struct_decl` or `reify` ZIR instruction.
/// Only `none` when the struct is `@TypeOf(.{})`.
zir_index: TrackedInst.Index.Optional,
layout: std.builtin.Type.ContainerLayout,
field_names: NullTerminatedString.Slice,
field_types: Index.Slice,
field_inits: Index.Slice,
field_aligns: Alignment.Slice,
runtime_order: RuntimeOrder.Slice,
comptime_bits: ComptimeBits,
offsets: Offsets,
names_map: OptionalMapIndex,
captures: CaptureValue.Slice,
pub const ComptimeBits = struct {
start: u32,
/// This is the number of u32 elements, not the number of struct fields.
len: u32,
pub fn get(this: ComptimeBits, ip: *const InternPool) []u32 {
return ip.extra.items[this.start..][0..this.len];
}
pub fn getBit(this: ComptimeBits, ip: *const InternPool, i: usize) bool {
if (this.len == 0) return false;
return @as(u1, @truncate(this.get(ip)[i / 32] >> @intCast(i % 32))) != 0;
}
pub fn setBit(this: ComptimeBits, ip: *const InternPool, i: usize) void {
this.get(ip)[i / 32] |= @as(u32, 1) << @intCast(i % 32);
}
pub fn clearBit(this: ComptimeBits, ip: *const InternPool, i: usize) void {
this.get(ip)[i / 32] &= ~(@as(u32, 1) << @intCast(i % 32));
}
};
pub const Offsets = struct {
start: u32,
len: u32,
pub fn get(this: Offsets, ip: *const InternPool) []u32 {
return @ptrCast(ip.extra.items[this.start..][0..this.len]);
}
};
pub const RuntimeOrder = enum(u32) {
/// Placeholder until layout is resolved.
unresolved = std.math.maxInt(u32) - 0,
/// Field not present at runtime
omitted = std.math.maxInt(u32) - 1,
_,
pub const Slice = struct {
start: u32,
len: u32,
pub fn get(slice: RuntimeOrder.Slice, ip: *const InternPool) []RuntimeOrder {
return @ptrCast(ip.extra.items[slice.start..][0..slice.len]);
}
};
pub fn toInt(i: RuntimeOrder) ?u32 {
return switch (i) {
.omitted => null,
.unresolved => unreachable,
else => @intFromEnum(i),
};
}
};
/// Look up field index based on field name.
pub fn nameIndex(self: LoadedStructType, ip: *const InternPool, name: NullTerminatedString) ?u32 {
const names_map = self.names_map.unwrap() orelse {
const i = name.toUnsigned(ip) orelse return null;
if (i >= self.field_types.len) return null;
return i;
};
const map = &ip.maps.items[@intFromEnum(names_map)];
const adapter: NullTerminatedString.Adapter = .{ .strings = self.field_names.get(ip) };
const field_index = map.getIndexAdapted(name, adapter) orelse return null;
return @intCast(field_index);
}
/// Returns the already-existing field with the same name, if any.
pub fn addFieldName(
self: @This(),
ip: *InternPool,
name: NullTerminatedString,
) ?u32 {
return ip.addFieldName(self.names_map.unwrap().?, self.field_names.start, name);
}
pub fn fieldAlign(s: @This(), ip: *const InternPool, i: usize) Alignment {
if (s.field_aligns.len == 0) return .none;
return s.field_aligns.get(ip)[i];
}
pub fn fieldInit(s: @This(), ip: *const InternPool, i: usize) Index {
if (s.field_inits.len == 0) return .none;
assert(s.haveFieldInits(ip));
return s.field_inits.get(ip)[i];
}
/// Returns `none` in the case the struct is a tuple.
pub fn fieldName(s: @This(), ip: *const InternPool, i: usize) OptionalNullTerminatedString {
if (s.field_names.len == 0) return .none;
return s.field_names.get(ip)[i].toOptional();
}
pub fn fieldIsComptime(s: @This(), ip: *const InternPool, i: usize) bool {
return s.comptime_bits.getBit(ip, i);
}
pub fn setFieldComptime(s: @This(), ip: *InternPool, i: usize) void {
s.comptime_bits.setBit(ip, i);
}
/// Reads the non-opv flag calculated during AstGen. Used to short-circuit more
/// complicated logic.
pub fn knownNonOpv(s: @This(), ip: *InternPool) bool {
return switch (s.layout) {
.@"packed" => false,
.auto, .@"extern" => s.flagsPtr(ip).known_non_opv,
};
}
/// The returned pointer expires with any addition to the `InternPool`.
/// Asserts the struct is not packed.
pub fn flagsPtr(self: @This(), ip: *const InternPool) *Tag.TypeStruct.Flags {
assert(self.layout != .@"packed");
const flags_field_index = std.meta.fieldIndex(Tag.TypeStruct, "flags").?;
return @ptrCast(&ip.extra.items[self.extra_index + flags_field_index]);
}
/// The returned pointer expires with any addition to the `InternPool`.
/// Asserts that the struct is packed.
pub fn packedFlagsPtr(self: @This(), ip: *const InternPool) *Tag.TypeStructPacked.Flags {
assert(self.layout == .@"packed");
const flags_field_index = std.meta.fieldIndex(Tag.TypeStructPacked, "flags").?;
return @ptrCast(&ip.extra.items[self.extra_index + flags_field_index]);
}
pub fn assumeRuntimeBitsIfFieldTypesWip(s: @This(), ip: *InternPool) bool {
if (s.layout == .@"packed") return false;
const flags_ptr = s.flagsPtr(ip);
if (flags_ptr.field_types_wip) {
flags_ptr.assumed_runtime_bits = true;
return true;
}
return false;
}
pub fn setTypesWip(s: @This(), ip: *InternPool) bool {
if (s.layout == .@"packed") return false;
const flags_ptr = s.flagsPtr(ip);
if (flags_ptr.field_types_wip) return true;
flags_ptr.field_types_wip = true;
return false;
}
pub fn clearTypesWip(s: @This(), ip: *InternPool) void {
if (s.layout == .@"packed") return;
s.flagsPtr(ip).field_types_wip = false;
}
pub fn setLayoutWip(s: @This(), ip: *InternPool) bool {
if (s.layout == .@"packed") return false;
const flags_ptr = s.flagsPtr(ip);
if (flags_ptr.layout_wip) return true;
flags_ptr.layout_wip = true;
return false;
}
pub fn clearLayoutWip(s: @This(), ip: *InternPool) void {
if (s.layout == .@"packed") return;
s.flagsPtr(ip).layout_wip = false;
}
pub fn setAlignmentWip(s: @This(), ip: *InternPool) bool {
if (s.layout == .@"packed") return false;
const flags_ptr = s.flagsPtr(ip);
if (flags_ptr.alignment_wip) return true;
flags_ptr.alignment_wip = true;
return false;
}
pub fn clearAlignmentWip(s: @This(), ip: *InternPool) void {
if (s.layout == .@"packed") return;
s.flagsPtr(ip).alignment_wip = false;
}
pub fn setInitsWip(s: @This(), ip: *InternPool) bool {
switch (s.layout) {
.@"packed" => {
const flag = &s.packedFlagsPtr(ip).field_inits_wip;
if (flag.*) return true;
flag.* = true;
return false;
},
.auto, .@"extern" => {
const flag = &s.flagsPtr(ip).field_inits_wip;
if (flag.*) return true;
flag.* = true;
return false;
},
}
}
pub fn clearInitsWip(s: @This(), ip: *InternPool) void {
switch (s.layout) {
.@"packed" => s.packedFlagsPtr(ip).field_inits_wip = false,
.auto, .@"extern" => s.flagsPtr(ip).field_inits_wip = false,
}
}
pub fn setFullyResolved(s: @This(), ip: *InternPool) bool {
if (s.layout == .@"packed") return true;
const flags_ptr = s.flagsPtr(ip);
if (flags_ptr.fully_resolved) return true;
flags_ptr.fully_resolved = true;
return false;
}
pub fn clearFullyResolved(s: @This(), ip: *InternPool) void {
s.flagsPtr(ip).fully_resolved = false;
}
/// The returned pointer expires with any addition to the `InternPool`.
/// Asserts the struct is not packed.
pub fn size(self: @This(), ip: *InternPool) *u32 {
assert(self.layout != .@"packed");
const size_field_index = std.meta.fieldIndex(Tag.TypeStruct, "size").?;
return @ptrCast(&ip.extra.items[self.extra_index + size_field_index]);
}
/// The backing integer type of the packed struct. Whether zig chooses
/// this type or the user specifies it, it is stored here. This will be
/// set to `none` until the layout is resolved.
/// Asserts the struct is packed.
pub fn backingIntType(s: @This(), ip: *const InternPool) *Index {
assert(s.layout == .@"packed");
const field_index = std.meta.fieldIndex(Tag.TypeStructPacked, "backing_int_ty").?;
return @ptrCast(&ip.extra.items[s.extra_index + field_index]);
}
/// Asserts the struct is not packed.
pub fn setZirIndex(s: @This(), ip: *InternPool, new_zir_index: TrackedInst.Index.Optional) void {
assert(s.layout != .@"packed");
const field_index = std.meta.fieldIndex(Tag.TypeStruct, "zir_index").?;
ip.extra.items[s.extra_index + field_index] = @intFromEnum(new_zir_index);
}
pub fn haveFieldTypes(s: @This(), ip: *const InternPool) bool {
const types = s.field_types.get(ip);
return types.len == 0 or types[0] != .none;
}
pub fn haveFieldInits(s: @This(), ip: *const InternPool) bool {
return switch (s.layout) {
.@"packed" => s.packedFlagsPtr(ip).inits_resolved,
.auto, .@"extern" => s.flagsPtr(ip).inits_resolved,
};
}
pub fn setHaveFieldInits(s: @This(), ip: *InternPool) void {
switch (s.layout) {
.@"packed" => s.packedFlagsPtr(ip).inits_resolved = true,
.auto, .@"extern" => s.flagsPtr(ip).inits_resolved = true,
}
}
pub fn haveLayout(s: @This(), ip: *InternPool) bool {
return switch (s.layout) {
.@"packed" => s.backingIntType(ip).* != .none,
.auto, .@"extern" => s.flagsPtr(ip).layout_resolved,
};
}
pub fn isTuple(s: @This(), ip: *InternPool) bool {
return s.layout != .@"packed" and s.flagsPtr(ip).is_tuple;
}
pub fn hasReorderedFields(s: @This()) bool {
return s.layout == .auto;
}
pub const RuntimeOrderIterator = struct {
ip: *InternPool,
field_index: u32,
struct_type: InternPool.LoadedStructType,
pub fn next(it: *@This()) ?u32 {
var i = it.field_index;
if (i >= it.struct_type.field_types.len)
return null;
if (it.struct_type.hasReorderedFields()) {
it.field_index += 1;
return it.struct_type.runtime_order.get(it.ip)[i].toInt();
}
while (it.struct_type.fieldIsComptime(it.ip, i)) {
i += 1;
if (i >= it.struct_type.field_types.len)
return null;
}
it.field_index = i + 1;
return i;
}
};
/// Iterates over non-comptime fields in the order they are laid out in memory at runtime.
/// May or may not include zero-bit fields.
/// Asserts the struct is not packed.
pub fn iterateRuntimeOrder(s: @This(), ip: *InternPool) RuntimeOrderIterator {
assert(s.layout != .@"packed");
return .{
.ip = ip,
.field_index = 0,
.struct_type = s,
};
}
};
pub fn loadStructType(ip: *const InternPool, index: Index) LoadedStructType {
const item = ip.items.get(@intFromEnum(index));
switch (item.tag) {
.type_struct => {
if (item.data == 0) return .{
.extra_index = 0,
.decl = .none,
.namespace = .none,
.zir_index = .none,
.layout = .auto,
.field_names = .{ .start = 0, .len = 0 },
.field_types = .{ .start = 0, .len = 0 },
.field_inits = .{ .start = 0, .len = 0 },
.field_aligns = .{ .start = 0, .len = 0 },
.runtime_order = .{ .start = 0, .len = 0 },
.comptime_bits = .{ .start = 0, .len = 0 },
.offsets = .{ .start = 0, .len = 0 },
.names_map = .none,
.captures = .{ .start = 0, .len = 0 },
};
const extra = ip.extraDataTrail(Tag.TypeStruct, item.data);
const fields_len = extra.data.fields_len;
var extra_index = extra.end;
const captures_len = if (extra.data.flags.any_captures) c: {
const len = ip.extra.items[extra_index];
extra_index += 1;
break :c len;
} else 0;
const captures: CaptureValue.Slice = .{
.start = extra_index,
.len = captures_len,
};
extra_index += captures_len;
if (extra.data.flags.is_reified) {
extra_index += 2; // PackedU64
}
const field_types: Index.Slice = .{
.start = extra_index,
.len = fields_len,
};
extra_index += fields_len;
const names_map: OptionalMapIndex, const names: NullTerminatedString.Slice = if (!extra.data.flags.is_tuple) n: {
const names_map: OptionalMapIndex = @enumFromInt(ip.extra.items[extra_index]);
extra_index += 1;
const names: NullTerminatedString.Slice = .{ .start = extra_index, .len = fields_len };
extra_index += fields_len;
break :n .{ names_map, names };
} else .{ .none, .{ .start = 0, .len = 0 } };
const inits: Index.Slice = if (extra.data.flags.any_default_inits) i: {
const inits: Index.Slice = .{ .start = extra_index, .len = fields_len };
extra_index += fields_len;
break :i inits;
} else .{ .start = 0, .len = 0 };
const namespace: OptionalNamespaceIndex = if (extra.data.flags.has_namespace) n: {
const n: NamespaceIndex = @enumFromInt(ip.extra.items[extra_index]);
extra_index += 1;
break :n n.toOptional();
} else .none;
const aligns: Alignment.Slice = if (extra.data.flags.any_aligned_fields) a: {
const a: Alignment.Slice = .{ .start = extra_index, .len = fields_len };
extra_index += std.math.divCeil(u32, fields_len, 4) catch unreachable;
break :a a;
} else .{ .start = 0, .len = 0 };
const comptime_bits: LoadedStructType.ComptimeBits = if (extra.data.flags.any_comptime_fields) c: {
const len = std.math.divCeil(u32, fields_len, 32) catch unreachable;
const c: LoadedStructType.ComptimeBits = .{ .start = extra_index, .len = len };
extra_index += len;
break :c c;
} else .{ .start = 0, .len = 0 };
const runtime_order: LoadedStructType.RuntimeOrder.Slice = if (!extra.data.flags.is_extern) ro: {
const ro: LoadedStructType.RuntimeOrder.Slice = .{ .start = extra_index, .len = fields_len };
extra_index += fields_len;
break :ro ro;
} else .{ .start = 0, .len = 0 };
const offsets: LoadedStructType.Offsets = o: {
const o: LoadedStructType.Offsets = .{ .start = extra_index, .len = fields_len };
extra_index += fields_len;
break :o o;
};
return .{
.extra_index = item.data,
.decl = extra.data.decl.toOptional(),
.namespace = namespace,
.zir_index = extra.data.zir_index.toOptional(),
.layout = if (extra.data.flags.is_extern) .@"extern" else .auto,
.field_names = names,
.field_types = field_types,
.field_inits = inits,
.field_aligns = aligns,
.runtime_order = runtime_order,
.comptime_bits = comptime_bits,
.offsets = offsets,
.names_map = names_map,
.captures = captures,
};
},
.type_struct_packed, .type_struct_packed_inits => {
const extra = ip.extraDataTrail(Tag.TypeStructPacked, item.data);
const has_inits = item.tag == .type_struct_packed_inits;
const fields_len = extra.data.fields_len;
var extra_index = extra.end;
const captures_len = if (extra.data.flags.any_captures) c: {
const len = ip.extra.items[extra_index];
extra_index += 1;
break :c len;
} else 0;
const captures: CaptureValue.Slice = .{
.start = extra_index,
.len = captures_len,
};
extra_index += captures_len;
if (extra.data.flags.is_reified) {
extra_index += 2; // PackedU64
}
const field_types: Index.Slice = .{
.start = extra_index,
.len = fields_len,
};
extra_index += fields_len;
const field_names: NullTerminatedString.Slice = .{
.start = extra_index,
.len = fields_len,
};
extra_index += fields_len;
const field_inits: Index.Slice = if (has_inits) inits: {
const i: Index.Slice = .{
.start = extra_index,
.len = fields_len,
};
extra_index += fields_len;
break :inits i;
} else .{ .start = 0, .len = 0 };
return .{
.extra_index = item.data,
.decl = extra.data.decl.toOptional(),
.namespace = extra.data.namespace,
.zir_index = extra.data.zir_index.toOptional(),
.layout = .@"packed",
.field_names = field_names,
.field_types = field_types,
.field_inits = field_inits,
.field_aligns = .{ .start = 0, .len = 0 },
.runtime_order = .{ .start = 0, .len = 0 },
.comptime_bits = .{ .start = 0, .len = 0 },
.offsets = .{ .start = 0, .len = 0 },
.names_map = extra.data.names_map.toOptional(),
.captures = captures,
};
},
else => unreachable,
}
}
const LoadedEnumType = struct {
/// The Decl that corresponds to the enum itself.
decl: DeclIndex,
/// Represents the declarations inside this enum.
namespace: OptionalNamespaceIndex,
/// An integer type which is used for the numerical value of the enum.
/// This field is present regardless of whether the enum has an
/// explicitly provided tag type or auto-numbered.
tag_ty: Index,
/// Set of field names in declaration order.
names: NullTerminatedString.Slice,
/// Maps integer tag value to field index.
/// Entries are in declaration order, same as `fields`.
/// If this is empty, it means the enum tags are auto-numbered.
values: Index.Slice,
tag_mode: TagMode,
names_map: MapIndex,
/// This is guaranteed to not be `.none` if explicit values are provided.
values_map: OptionalMapIndex,
/// This is `none` only if this is a generated tag type.
zir_index: TrackedInst.Index.Optional,
captures: CaptureValue.Slice,
pub const TagMode = enum {
/// The integer tag type was auto-numbered by zig.
auto,
/// The integer tag type was provided by the enum declaration, and the enum
/// is exhaustive.
explicit,
/// The integer tag type was provided by the enum declaration, and the enum
/// is non-exhaustive.
nonexhaustive,
};
/// Look up field index based on field name.
pub fn nameIndex(self: LoadedEnumType, ip: *const InternPool, name: NullTerminatedString) ?u32 {
const map = &ip.maps.items[@intFromEnum(self.names_map)];
const adapter: NullTerminatedString.Adapter = .{ .strings = self.names.get(ip) };
const field_index = map.getIndexAdapted(name, adapter) orelse return null;
return @intCast(field_index);
}
/// Look up field index based on tag value.
/// Asserts that `values_map` is not `none`.
/// This function returns `null` when `tag_val` does not have the
/// integer tag type of the enum.
pub fn tagValueIndex(self: LoadedEnumType, ip: *const InternPool, tag_val: Index) ?u32 {
assert(tag_val != .none);
// TODO: we should probably decide a single interface for this function, but currently
// it's being called with both tag values and underlying ints. Fix this!
const int_tag_val = switch (ip.indexToKey(tag_val)) {
.enum_tag => |enum_tag| enum_tag.int,
.int => tag_val,
else => unreachable,
};
if (self.values_map.unwrap()) |values_map| {
const map = &ip.maps.items[@intFromEnum(values_map)];
const adapter: Index.Adapter = .{ .indexes = self.values.get(ip) };
const field_index = map.getIndexAdapted(int_tag_val, adapter) orelse return null;
return @intCast(field_index);
}
// Auto-numbered enum. Convert `int_tag_val` to field index.
const field_index = switch (ip.indexToKey(int_tag_val).int.storage) {
inline .u64, .i64 => |x| std.math.cast(u32, x) orelse return null,
.big_int => |x| x.to(u32) catch return null,
.lazy_align, .lazy_size => unreachable,
};
return if (field_index < self.names.len) field_index else null;
}
};
pub fn loadEnumType(ip: *const InternPool, index: Index) LoadedEnumType {
const item = ip.items.get(@intFromEnum(index));
const tag_mode: LoadedEnumType.TagMode = switch (item.tag) {
.type_enum_auto => {
const extra = ip.extraDataTrail(EnumAuto, item.data);
var extra_index: u32 = @intCast(extra.end);
if (extra.data.zir_index == .none) {
extra_index += 1; // owner_union
}
const captures_len = if (extra.data.captures_len == std.math.maxInt(u32)) c: {
extra_index += 2; // type_hash: PackedU64
break :c 0;
} else extra.data.captures_len;
return .{
.decl = extra.data.decl,
.namespace = extra.data.namespace,
.tag_ty = extra.data.int_tag_type,
.names = .{
.start = extra_index + captures_len,
.len = extra.data.fields_len,
},
.values = .{ .start = 0, .len = 0 },
.tag_mode = .auto,
.names_map = extra.data.names_map,
.values_map = .none,
.zir_index = extra.data.zir_index,
.captures = .{
.start = extra_index,
.len = captures_len,
},
};
},
.type_enum_explicit => .explicit,
.type_enum_nonexhaustive => .nonexhaustive,
else => unreachable,
};
const extra = ip.extraDataTrail(EnumExplicit, item.data);
var extra_index: u32 = @intCast(extra.end);
if (extra.data.zir_index == .none) {
extra_index += 1; // owner_union
}
const captures_len = if (extra.data.captures_len == std.math.maxInt(u32)) c: {
extra_index += 2; // type_hash: PackedU64
break :c 0;
} else extra.data.captures_len;
return .{
.decl = extra.data.decl,
.namespace = extra.data.namespace,
.tag_ty = extra.data.int_tag_type,
.names = .{
.start = extra_index + captures_len,
.len = extra.data.fields_len,
},
.values = .{
.start = extra_index + captures_len + extra.data.fields_len,
.len = if (extra.data.values_map != .none) extra.data.fields_len else 0,
},
.tag_mode = tag_mode,
.names_map = extra.data.names_map,
.values_map = extra.data.values_map,
.zir_index = extra.data.zir_index,
.captures = .{
.start = extra_index,
.len = captures_len,
},
};
}
/// Note that this type doubles as the payload for `Tag.type_opaque`.
pub const LoadedOpaqueType = struct {
/// The opaque's owner Decl.
decl: DeclIndex,
/// Contains the declarations inside this opaque.
namespace: OptionalNamespaceIndex,
/// Index of the `opaque_decl` or `reify` instruction.
zir_index: TrackedInst.Index,
captures: CaptureValue.Slice,
};
pub fn loadOpaqueType(ip: *const InternPool, index: Index) LoadedOpaqueType {
assert(ip.items.items(.tag)[@intFromEnum(index)] == .type_opaque);
const extra_index = ip.items.items(.data)[@intFromEnum(index)];
const extra = ip.extraDataTrail(Tag.TypeOpaque, extra_index);
const captures_len = if (extra.data.captures_len == std.math.maxInt(u32))
0
else
extra.data.captures_len;
return .{
.decl = extra.data.decl,
.namespace = extra.data.namespace,
.zir_index = extra.data.zir_index,
.captures = .{
.start = extra.end,
.len = captures_len,
},
};
}
pub const Item = struct {
tag: Tag,
/// The doc comments on the respective Tag explain how to interpret this.
data: u32,
};
/// Represents an index into `map`. It represents the canonical index
/// of a `Value` within this `InternPool`. The values are typed.
/// Two values which have the same type can be equality compared simply
/// by checking if their indexes are equal, provided they are both in
/// the same `InternPool`.
/// When adding a tag to this enum, consider adding a corresponding entry to
/// `primitives` in AstGen.zig.
pub const Index = enum(u32) {
pub const first_type: Index = .u0_type;
pub const last_type: Index = .empty_struct_type;
pub const first_value: Index = .undef;
pub const last_value: Index = .empty_struct;
u0_type,
i0_type,
u1_type,
u8_type,
i8_type,
u16_type,
i16_type,
u29_type,
u32_type,
i32_type,
u64_type,
i64_type,
u80_type,
u128_type,
i128_type,
usize_type,
isize_type,
c_char_type,
c_short_type,
c_ushort_type,
c_int_type,
c_uint_type,
c_long_type,
c_ulong_type,
c_longlong_type,
c_ulonglong_type,
c_longdouble_type,
f16_type,
f32_type,
f64_type,
f80_type,
f128_type,
anyopaque_type,
bool_type,
void_type,
type_type,
anyerror_type,
comptime_int_type,
comptime_float_type,
noreturn_type,
anyframe_type,
null_type,
undefined_type,
enum_literal_type,
atomic_order_type,
atomic_rmw_op_type,
calling_convention_type,
address_space_type,
float_mode_type,
reduce_op_type,
call_modifier_type,
prefetch_options_type,
export_options_type,
extern_options_type,
type_info_type,
manyptr_u8_type,
manyptr_const_u8_type,
manyptr_const_u8_sentinel_0_type,
single_const_pointer_to_comptime_int_type,
slice_const_u8_type,
slice_const_u8_sentinel_0_type,
optional_noreturn_type,
anyerror_void_error_union_type,
/// Used for the inferred error set of inline/comptime function calls.
adhoc_inferred_error_set_type,
generic_poison_type,
/// `@TypeOf(.{})`
empty_struct_type,
/// `undefined` (untyped)
undef,
/// `0` (comptime_int)
zero,
/// `0` (usize)
zero_usize,
/// `0` (u8)
zero_u8,
/// `1` (comptime_int)
one,
/// `1` (usize)
one_usize,
/// `1` (u8)
one_u8,
/// `4` (u8)
four_u8,
/// `-1` (comptime_int)
negative_one,
/// `std.builtin.CallingConvention.C`
calling_convention_c,
/// `std.builtin.CallingConvention.Inline`
calling_convention_inline,
/// `{}`
void_value,
/// `unreachable` (noreturn type)
unreachable_value,
/// `null` (untyped)
null_value,
/// `true`
bool_true,
/// `false`
bool_false,
/// `.{}` (untyped)
empty_struct,
/// Used for generic parameters where the type and value
/// is not known until generic function instantiation.
generic_poison,
/// Used by Air/Sema only.
var_args_param_type = std.math.maxInt(u32) - 1,
none = std.math.maxInt(u32),
_,
/// An array of `Index` existing within the `extra` array.
/// This type exists to provide a struct with lifetime that is
/// not invalidated when items are added to the `InternPool`.
pub const Slice = struct {
start: u32,
len: u32,
pub fn get(slice: Slice, ip: *const InternPool) []Index {
return @ptrCast(ip.extra.items[slice.start..][0..slice.len]);
}
};
/// Used for a map of `Index` values to the index within a list of `Index` values.
const Adapter = struct {
indexes: []const Index,
pub fn eql(ctx: @This(), a: Index, b_void: void, b_map_index: usize) bool {
_ = b_void;
return a == ctx.indexes[b_map_index];
}
pub fn hash(ctx: @This(), a: Index) u32 {
_ = ctx;
return std.hash.uint32(@intFromEnum(a));
}
};
/// This function is used in the debugger pretty formatters in tools/ to fetch the
/// Tag to encoding mapping to facilitate fancy debug printing for this type.
/// TODO merge this with `Tag.Payload`.
fn dbHelper(self: *Index, tag_to_encoding_map: *struct {
const DataIsIndex = struct { data: Index };
const DataIsExtraIndexOfEnumExplicit = struct {
const @"data.fields_len" = opaque {};
data: *EnumExplicit,
@"trailing.names.len": *@"data.fields_len",
@"trailing.values.len": *@"data.fields_len",
trailing: struct {
names: []NullTerminatedString,
values: []Index,
},
};
const DataIsExtraIndexOfTypeStructAnon = struct {
const @"data.fields_len" = opaque {};
data: *TypeStructAnon,
@"trailing.types.len": *@"data.fields_len",
@"trailing.values.len": *@"data.fields_len",
@"trailing.names.len": *@"data.fields_len",
trailing: struct {
types: []Index,
values: []Index,
names: []NullTerminatedString,
},
};
removed: void,
type_int_signed: struct { data: u32 },
type_int_unsigned: struct { data: u32 },
type_array_big: struct { data: *Array },
type_array_small: struct { data: *Vector },
type_vector: struct { data: *Vector },
type_pointer: struct { data: *Tag.TypePointer },
type_slice: DataIsIndex,
type_optional: DataIsIndex,
type_anyframe: DataIsIndex,
type_error_union: struct { data: *Key.ErrorUnionType },
type_anyerror_union: DataIsIndex,
type_error_set: struct {
const @"data.names_len" = opaque {};
data: *Tag.ErrorSet,
@"trailing.names.len": *@"data.names_len",
trailing: struct { names: []NullTerminatedString },
},
type_inferred_error_set: DataIsIndex,
type_enum_auto: struct {
const @"data.fields_len" = opaque {};
data: *EnumAuto,
@"trailing.names.len": *@"data.fields_len",
trailing: struct { names: []NullTerminatedString },
},
type_enum_explicit: DataIsExtraIndexOfEnumExplicit,
type_enum_nonexhaustive: DataIsExtraIndexOfEnumExplicit,
simple_type: struct { data: SimpleType },
type_opaque: struct { data: *Tag.TypeOpaque },
type_struct: struct { data: *Tag.TypeStruct },
type_struct_anon: DataIsExtraIndexOfTypeStructAnon,
type_struct_packed: struct { data: *Tag.TypeStructPacked },
type_struct_packed_inits: struct { data: *Tag.TypeStructPacked },
type_tuple_anon: DataIsExtraIndexOfTypeStructAnon,
type_union: struct { data: *Tag.TypeUnion },
type_function: struct {
const @"data.flags.has_comptime_bits" = opaque {};
const @"data.flags.has_noalias_bits" = opaque {};
const @"data.params_len" = opaque {};
data: *Tag.TypeFunction,
@"trailing.comptime_bits.len": *@"data.flags.has_comptime_bits",
@"trailing.noalias_bits.len": *@"data.flags.has_noalias_bits",
@"trailing.param_types.len": *@"data.params_len",
trailing: struct { comptime_bits: []u32, noalias_bits: []u32, param_types: []Index },
},
undef: DataIsIndex,
simple_value: struct { data: SimpleValue },
ptr_decl: struct { data: *PtrDecl },
ptr_comptime_alloc: struct { data: *PtrComptimeAlloc },
ptr_anon_decl: struct { data: *PtrAnonDecl },
ptr_anon_decl_aligned: struct { data: *PtrAnonDeclAligned },
ptr_comptime_field: struct { data: *PtrComptimeField },
ptr_int: struct { data: *PtrBase },
ptr_eu_payload: struct { data: *PtrBase },
ptr_opt_payload: struct { data: *PtrBase },
ptr_elem: struct { data: *PtrBaseIndex },
ptr_field: struct { data: *PtrBaseIndex },
ptr_slice: struct { data: *PtrSlice },
opt_payload: struct { data: *Tag.TypeValue },
opt_null: DataIsIndex,
int_u8: struct { data: u8 },
int_u16: struct { data: u16 },
int_u32: struct { data: u32 },
int_i32: struct { data: i32 },
int_usize: struct { data: u32 },
int_comptime_int_u32: struct { data: u32 },
int_comptime_int_i32: struct { data: i32 },
int_small: struct { data: *IntSmall },
int_positive: struct { data: u32 },
int_negative: struct { data: u32 },
int_lazy_align: struct { data: *IntLazy },
int_lazy_size: struct { data: *IntLazy },
error_set_error: struct { data: *Key.Error },
error_union_error: struct { data: *Key.Error },
error_union_payload: struct { data: *Tag.TypeValue },
enum_literal: struct { data: NullTerminatedString },
enum_tag: struct { data: *Tag.EnumTag },
float_f16: struct { data: f16 },
float_f32: struct { data: f32 },
float_f64: struct { data: *Float64 },
float_f80: struct { data: *Float80 },
float_f128: struct { data: *Float128 },
float_c_longdouble_f80: struct { data: *Float80 },
float_c_longdouble_f128: struct { data: *Float128 },
float_comptime_float: struct { data: *Float128 },
variable: struct { data: *Tag.Variable },
extern_func: struct { data: *Key.ExternFunc },
func_decl: struct {
const @"data.analysis.inferred_error_set" = opaque {};
data: *Tag.FuncDecl,
@"trailing.resolved_error_set.len": *@"data.analysis.inferred_error_set",
trailing: struct { resolved_error_set: []Index },
},
func_instance: struct {
const @"data.analysis.inferred_error_set" = opaque {};
const @"data.generic_owner.data.ty.data.params_len" = opaque {};
data: *Tag.FuncInstance,
@"trailing.resolved_error_set.len": *@"data.analysis.inferred_error_set",
@"trailing.comptime_args.len": *@"data.generic_owner.data.ty.data.params_len",
trailing: struct { resolved_error_set: []Index, comptime_args: []Index },
},
func_coerced: struct {
data: *Tag.FuncCoerced,
},
only_possible_value: DataIsIndex,
union_value: struct { data: *Key.Union },
bytes: struct { data: *Bytes },
aggregate: struct {
const @"data.ty.data.len orelse data.ty.data.fields_len" = opaque {};
data: *Tag.Aggregate,
@"trailing.element_values.len": *@"data.ty.data.len orelse data.ty.data.fields_len",
trailing: struct { element_values: []Index },
},
repeated: struct { data: *Repeated },
memoized_call: struct {
const @"data.args_len" = opaque {};
data: *MemoizedCall,
@"trailing.arg_values.len": *@"data.args_len",
trailing: struct { arg_values: []Index },
},
}) void {
_ = self;
const map_fields = @typeInfo(@typeInfo(@TypeOf(tag_to_encoding_map)).Pointer.child).Struct.fields;
@setEvalBranchQuota(2_000);
inline for (@typeInfo(Tag).Enum.fields, 0..) |tag, start| {
inline for (0..map_fields.len) |offset| {
if (comptime std.mem.eql(u8, tag.name, map_fields[(start + offset) % map_fields.len].name)) break;
} else {
@compileError(@typeName(Tag) ++ "." ++ tag.name ++ " missing dbHelper tag_to_encoding_map entry");
}
}
}
comptime {
if (!builtin.strip_debug_info) {
_ = &dbHelper;
}
}
};
pub const static_keys = [_]Key{
.{ .int_type = .{
.signedness = .unsigned,
.bits = 0,
} },
.{ .int_type = .{
.signedness = .signed,
.bits = 0,
} },
.{ .int_type = .{
.signedness = .unsigned,
.bits = 1,
} },
.{ .int_type = .{
.signedness = .unsigned,
.bits = 8,
} },
.{ .int_type = .{
.signedness = .signed,
.bits = 8,
} },
.{ .int_type = .{
.signedness = .unsigned,
.bits = 16,
} },
.{ .int_type = .{
.signedness = .signed,
.bits = 16,
} },
.{ .int_type = .{
.signedness = .unsigned,
.bits = 29,
} },
.{ .int_type = .{
.signedness = .unsigned,
.bits = 32,
} },
.{ .int_type = .{
.signedness = .signed,
.bits = 32,
} },
.{ .int_type = .{
.signedness = .unsigned,
.bits = 64,
} },
.{ .int_type = .{
.signedness = .signed,
.bits = 64,
} },
.{ .int_type = .{
.signedness = .unsigned,
.bits = 80,
} },
.{ .int_type = .{
.signedness = .unsigned,
.bits = 128,
} },
.{ .int_type = .{
.signedness = .signed,
.bits = 128,
} },
.{ .simple_type = .usize },
.{ .simple_type = .isize },
.{ .simple_type = .c_char },
.{ .simple_type = .c_short },
.{ .simple_type = .c_ushort },
.{ .simple_type = .c_int },
.{ .simple_type = .c_uint },
.{ .simple_type = .c_long },
.{ .simple_type = .c_ulong },
.{ .simple_type = .c_longlong },
.{ .simple_type = .c_ulonglong },
.{ .simple_type = .c_longdouble },
.{ .simple_type = .f16 },
.{ .simple_type = .f32 },
.{ .simple_type = .f64 },
.{ .simple_type = .f80 },
.{ .simple_type = .f128 },
.{ .simple_type = .anyopaque },
.{ .simple_type = .bool },
.{ .simple_type = .void },
.{ .simple_type = .type },
.{ .simple_type = .anyerror },
.{ .simple_type = .comptime_int },
.{ .simple_type = .comptime_float },
.{ .simple_type = .noreturn },
.{ .anyframe_type = .none },
.{ .simple_type = .null },
.{ .simple_type = .undefined },
.{ .simple_type = .enum_literal },
.{ .simple_type = .atomic_order },
.{ .simple_type = .atomic_rmw_op },
.{ .simple_type = .calling_convention },
.{ .simple_type = .address_space },
.{ .simple_type = .float_mode },
.{ .simple_type = .reduce_op },
.{ .simple_type = .call_modifier },
.{ .simple_type = .prefetch_options },
.{ .simple_type = .export_options },
.{ .simple_type = .extern_options },
.{ .simple_type = .type_info },
// [*]u8
.{ .ptr_type = .{
.child = .u8_type,
.flags = .{
.size = .Many,
},
} },
// [*]const u8
.{ .ptr_type = .{
.child = .u8_type,
.flags = .{
.size = .Many,
.is_const = true,
},
} },
// [*:0]const u8
.{ .ptr_type = .{
.child = .u8_type,
.sentinel = .zero_u8,
.flags = .{
.size = .Many,
.is_const = true,
},
} },
// comptime_int
.{ .ptr_type = .{
.child = .comptime_int_type,
.flags = .{
.size = .One,
.is_const = true,
},
} },
// []const u8
.{ .ptr_type = .{
.child = .u8_type,
.flags = .{
.size = .Slice,
.is_const = true,
},
} },
// [:0]const u8
.{ .ptr_type = .{
.child = .u8_type,
.sentinel = .zero_u8,
.flags = .{
.size = .Slice,
.is_const = true,
},
} },
// ?noreturn
.{ .opt_type = .noreturn_type },
// anyerror!void
.{ .error_union_type = .{
.error_set_type = .anyerror_type,
.payload_type = .void_type,
} },
// adhoc_inferred_error_set_type
.{ .simple_type = .adhoc_inferred_error_set },
// generic_poison_type
.{ .simple_type = .generic_poison },
// empty_struct_type
.{ .anon_struct_type = .{
.types = .{ .start = 0, .len = 0 },
.names = .{ .start = 0, .len = 0 },
.values = .{ .start = 0, .len = 0 },
} },
.{ .simple_value = .undefined },
.{ .int = .{
.ty = .comptime_int_type,
.storage = .{ .u64 = 0 },
} },
.{ .int = .{
.ty = .usize_type,
.storage = .{ .u64 = 0 },
} },
.{ .int = .{
.ty = .u8_type,
.storage = .{ .u64 = 0 },
} },
.{ .int = .{
.ty = .comptime_int_type,
.storage = .{ .u64 = 1 },
} },
.{ .int = .{
.ty = .usize_type,
.storage = .{ .u64 = 1 },
} },
// one_u8
.{ .int = .{
.ty = .u8_type,
.storage = .{ .u64 = 1 },
} },
// four_u8
.{ .int = .{
.ty = .u8_type,
.storage = .{ .u64 = 4 },
} },
// negative_one
.{ .int = .{
.ty = .comptime_int_type,
.storage = .{ .i64 = -1 },
} },
// calling_convention_c
.{ .enum_tag = .{
.ty = .calling_convention_type,
.int = .one_u8,
} },
// calling_convention_inline
.{ .enum_tag = .{
.ty = .calling_convention_type,
.int = .four_u8,
} },
.{ .simple_value = .void },
.{ .simple_value = .@"unreachable" },
.{ .simple_value = .null },
.{ .simple_value = .true },
.{ .simple_value = .false },
.{ .simple_value = .empty_struct },
.{ .simple_value = .generic_poison },
};
/// How many items in the InternPool are statically known.
/// This is specified with an integer literal and a corresponding comptime
/// assert below to break an unfortunate and arguably incorrect dependency loop
/// when compiling.
pub const static_len = Zir.Inst.Index.static_len;
comptime {
//@compileLog(static_keys.len);
assert(static_len == static_keys.len);
}
pub const Tag = enum(u8) {
/// This special tag represents a value which was removed from this pool via
/// `InternPool.remove`. The item remains allocated to preserve indices, but
/// lookups will consider it not equal to any other item, and all queries
/// assert not this tag. `data` is unused.
removed,
/// An integer type.
/// data is number of bits
type_int_signed,
/// An integer type.
/// data is number of bits
type_int_unsigned,
/// An array type whose length requires 64 bits or which has a sentinel.
/// data is payload to Array.
type_array_big,
/// An array type that has no sentinel and whose length fits in 32 bits.
/// data is payload to Vector.
type_array_small,
/// A vector type.
/// data is payload to Vector.
type_vector,
/// A fully explicitly specified pointer type.
type_pointer,
/// A slice type.
/// data is Index of underlying pointer type.
type_slice,
/// An optional type.
/// data is the child type.
type_optional,
/// The type `anyframe->T`.
/// data is the child type.
/// If the child type is `none`, the type is `anyframe`.
type_anyframe,
/// An error union type.
/// data is payload to `Key.ErrorUnionType`.
type_error_union,
/// An error union type of the form `anyerror!T`.
/// data is `Index` of payload type.
type_anyerror_union,
/// An error set type.
/// data is payload to `ErrorSet`.
type_error_set,
/// The inferred error set type of a function.
/// data is `Index` of a `func_decl` or `func_instance`.
type_inferred_error_set,
/// An enum type with auto-numbered tag values.
/// The enum is exhaustive.
/// data is payload index to `EnumAuto`.
type_enum_auto,
/// An enum type with an explicitly provided integer tag type.
/// The enum is exhaustive.
/// data is payload index to `EnumExplicit`.
type_enum_explicit,
/// An enum type with an explicitly provided integer tag type.
/// The enum is non-exhaustive.
/// data is payload index to `EnumExplicit`.
type_enum_nonexhaustive,
/// A type that can be represented with only an enum tag.
/// data is SimpleType enum value.
simple_type,
/// An opaque type.
/// data is index of Tag.TypeOpaque in extra.
type_opaque,
/// A non-packed struct type.
/// data is 0 or extra index of `TypeStruct`.
/// data == 0 represents `@TypeOf(.{})`.
type_struct,
/// An AnonStructType which stores types, names, and values for fields.
/// data is extra index of `TypeStructAnon`.
type_struct_anon,
/// A packed struct, no fields have any init values.
/// data is extra index of `TypeStructPacked`.
type_struct_packed,
/// A packed struct, one or more fields have init values.
/// data is extra index of `TypeStructPacked`.
type_struct_packed_inits,
/// An AnonStructType which has only types and values for fields.
/// data is extra index of `TypeStructAnon`.
type_tuple_anon,
/// A union type.
/// `data` is extra index of `TypeUnion`.
type_union,
/// A function body type.
/// `data` is extra index to `TypeFunction`.
type_function,
/// Typed `undefined`.
/// `data` is `Index` of the type.
/// Untyped `undefined` is stored instead via `simple_value`.
undef,
/// A value that can be represented with only an enum tag.
/// data is SimpleValue enum value.
simple_value,
/// A pointer to a decl.
/// data is extra index of `PtrDecl`, which contains the type and address.
ptr_decl,
/// A pointer to a decl that can be mutated at comptime.
/// data is extra index of `PtrComptimeAlloc`, which contains the type and address.
ptr_comptime_alloc,
/// A pointer to an anonymous decl.
/// data is extra index of `PtrAnonDecl`, which contains the pointer type and decl value.
/// The alignment of the anonymous decl is communicated via the pointer type.
ptr_anon_decl,
/// A pointer to an anonymous decl.
/// data is extra index of `PtrAnonDeclAligned`, which contains the pointer
/// type and decl value.
/// The original pointer type is also provided, which will be different than `ty`.
/// This encoding is only used when a pointer to an anonymous decl is
/// coerced to a different pointer type with a different alignment.
ptr_anon_decl_aligned,
/// data is extra index of `PtrComptimeField`, which contains the pointer type and field value.
ptr_comptime_field,
/// A pointer with an integer value.
/// data is extra index of `PtrBase`, which contains the type and address.
/// Only pointer types are allowed to have this encoding. Optional types must use
/// `opt_payload` or `opt_null`.
ptr_int,
/// A pointer to the payload of an error union.
/// data is extra index of `PtrBase`, which contains the type and base pointer.
ptr_eu_payload,
/// A pointer to the payload of an optional.
/// data is extra index of `PtrBase`, which contains the type and base pointer.
ptr_opt_payload,
/// A pointer to an array element.
/// data is extra index of PtrBaseIndex, which contains the base array and element index.
/// In order to use this encoding, one must ensure that the `InternPool`
/// already contains the elem pointer type corresponding to this payload.
ptr_elem,
/// A pointer to a container field.
/// data is extra index of PtrBaseIndex, which contains the base container and field index.
ptr_field,
/// A slice.
/// data is extra index of PtrSlice, which contains the ptr and len values
ptr_slice,
/// An optional value that is non-null.
/// data is extra index of `TypeValue`.
/// The type is the optional type (not the payload type).
opt_payload,
/// An optional value that is null.
/// data is Index of the optional type.
opt_null,
/// Type: u8
/// data is integer value
int_u8,
/// Type: u16
/// data is integer value
int_u16,
/// Type: u32
/// data is integer value
int_u32,
/// Type: i32
/// data is integer value bitcasted to u32.
int_i32,
/// A usize that fits in 32 bits.
/// data is integer value.
int_usize,
/// A comptime_int that fits in a u32.
/// data is integer value.
int_comptime_int_u32,
/// A comptime_int that fits in an i32.
/// data is integer value bitcasted to u32.
int_comptime_int_i32,
/// An integer value that fits in 32 bits with an explicitly provided type.
/// data is extra index of `IntSmall`.
int_small,
/// A positive integer value.
/// data is a limbs index to `Int`.
int_positive,
/// A negative integer value.
/// data is a limbs index to `Int`.
int_negative,
/// The ABI alignment of a lazy type.
/// data is extra index of `IntLazy`.
int_lazy_align,
/// The ABI size of a lazy type.
/// data is extra index of `IntLazy`.
int_lazy_size,
/// An error value.
/// data is extra index of `Key.Error`.
error_set_error,
/// An error union error.
/// data is extra index of `Key.Error`.
error_union_error,
/// An error union payload.
/// data is extra index of `TypeValue`.
error_union_payload,
/// An enum literal value.
/// data is `NullTerminatedString` of the error name.
enum_literal,
/// An enum tag value.
/// data is extra index of `EnumTag`.
enum_tag,
/// An f16 value.
/// data is float value bitcasted to u16 and zero-extended.
float_f16,
/// An f32 value.
/// data is float value bitcasted to u32.
float_f32,
/// An f64 value.
/// data is extra index to Float64.
float_f64,
/// An f80 value.
/// data is extra index to Float80.
float_f80,
/// An f128 value.
/// data is extra index to Float128.
float_f128,
/// A c_longdouble value of 80 bits.
/// data is extra index to Float80.
/// This is used when a c_longdouble value is provided as an f80, because f80 has unnormalized
/// values which cannot be losslessly represented as f128. It should only be used when the type
/// underlying c_longdouble for the target is 80 bits.
float_c_longdouble_f80,
/// A c_longdouble value of 128 bits.
/// data is extra index to Float128.
/// This is used when a c_longdouble value is provided as any type other than an f80, since all
/// other float types can be losslessly converted to and from f128.
float_c_longdouble_f128,
/// A comptime_float value.
/// data is extra index to Float128.
float_comptime_float,
/// A global variable.
/// data is extra index to Variable.
variable,
/// An extern function.
/// data is extra index to ExternFunc.
extern_func,
/// A non-extern function corresponding directly to the AST node from whence it originated.
/// data is extra index to `FuncDecl`.
/// Only the owner Decl is used for hashing and equality because the other
/// fields can get patched up during incremental compilation.
func_decl,
/// A generic function instantiation.
/// data is extra index to `FuncInstance`.
func_instance,
/// A `func_decl` or a `func_instance` that has been coerced to a different type.
/// data is extra index to `FuncCoerced`.
func_coerced,
/// This represents the only possible value for *some* types which have
/// only one possible value. Not all only-possible-values are encoded this way;
/// for example structs which have all comptime fields are not encoded this way.
/// The set of values that are encoded this way is:
/// * An array or vector which has length 0.
/// * A struct which has all fields comptime-known.
/// * An empty enum or union. TODO: this value's existence is strange, because such a type in reality has no values. See #15909
/// data is Index of the type, which is known to be zero bits at runtime.
only_possible_value,
/// data is extra index to Key.Union.
union_value,
/// An array of bytes.
/// data is extra index to `Bytes`.
bytes,
/// An instance of a struct, array, or vector.
/// data is extra index to `Aggregate`.
aggregate,
/// An instance of an array or vector with every element being the same value.
/// data is extra index to `Repeated`.
repeated,
/// A memoized comptime function call result.
/// data is extra index to `MemoizedCall`
memoized_call,
const ErrorUnionType = Key.ErrorUnionType;
const TypeValue = Key.TypeValue;
const Error = Key.Error;
const EnumTag = Key.EnumTag;
const ExternFunc = Key.ExternFunc;
const Union = Key.Union;
const TypePointer = Key.PtrType;
fn Payload(comptime tag: Tag) type {
return switch (tag) {
.removed => unreachable,
.type_int_signed => unreachable,
.type_int_unsigned => unreachable,
.type_array_big => Array,
.type_array_small => Vector,
.type_vector => Vector,
.type_pointer => TypePointer,
.type_slice => unreachable,
.type_optional => unreachable,
.type_anyframe => unreachable,
.type_error_union => ErrorUnionType,
.type_anyerror_union => unreachable,
.type_error_set => ErrorSet,
.type_inferred_error_set => unreachable,
.type_enum_auto => EnumAuto,
.type_enum_explicit => EnumExplicit,
.type_enum_nonexhaustive => EnumExplicit,
.simple_type => unreachable,
.type_opaque => TypeOpaque,
.type_struct => TypeStruct,
.type_struct_anon => TypeStructAnon,
.type_struct_packed, .type_struct_packed_inits => TypeStructPacked,
.type_tuple_anon => TypeStructAnon,
.type_union => TypeUnion,
.type_function => TypeFunction,
.undef => unreachable,
.simple_value => unreachable,
.ptr_decl => PtrDecl,
.ptr_comptime_alloc => PtrComptimeAlloc,
.ptr_anon_decl => PtrAnonDecl,
.ptr_anon_decl_aligned => PtrAnonDeclAligned,
.ptr_comptime_field => PtrComptimeField,
.ptr_int => PtrBase,
.ptr_eu_payload => PtrBase,
.ptr_opt_payload => PtrBase,
.ptr_elem => PtrBaseIndex,
.ptr_field => PtrBaseIndex,
.ptr_slice => PtrSlice,
.opt_payload => TypeValue,
.opt_null => unreachable,
.int_u8 => unreachable,
.int_u16 => unreachable,
.int_u32 => unreachable,
.int_i32 => unreachable,
.int_usize => unreachable,
.int_comptime_int_u32 => unreachable,
.int_comptime_int_i32 => unreachable,
.int_small => IntSmall,
.int_positive => unreachable,
.int_negative => unreachable,
.int_lazy_align => IntLazy,
.int_lazy_size => IntLazy,
.error_set_error => Error,
.error_union_error => Error,
.error_union_payload => TypeValue,
.enum_literal => unreachable,
.enum_tag => EnumTag,
.float_f16 => unreachable,
.float_f32 => unreachable,
.float_f64 => unreachable,
.float_f80 => unreachable,
.float_f128 => unreachable,
.float_c_longdouble_f80 => unreachable,
.float_c_longdouble_f128 => unreachable,
.float_comptime_float => unreachable,
.variable => Variable,
.extern_func => ExternFunc,
.func_decl => FuncDecl,
.func_instance => FuncInstance,
.func_coerced => FuncCoerced,
.only_possible_value => unreachable,
.union_value => Union,
.bytes => Bytes,
.aggregate => Aggregate,
.repeated => Repeated,
.memoized_call => MemoizedCall,
};
}
pub const Variable = struct {
ty: Index,
/// May be `none`.
init: Index,
decl: DeclIndex,
/// Library name if specified.
/// For example `extern "c" var stderrp = ...` would have 'c' as library name.
lib_name: OptionalNullTerminatedString,
flags: Flags,
pub const Flags = packed struct(u32) {
is_extern: bool,
is_const: bool,
is_threadlocal: bool,
is_weak_linkage: bool,
_: u28 = 0,
};
};
/// Trailing:
/// 0. element: Index for each len
/// len is determined by the aggregate type.
pub const Aggregate = struct {
/// The type of the aggregate.
ty: Index,
};
/// Trailing:
/// 0. If `analysis.inferred_error_set` is `true`, `Index` of an `error_set` which
/// is a regular error set corresponding to the finished inferred error set.
/// A `none` value marks that the inferred error set is not resolved yet.
pub const FuncDecl = struct {
analysis: FuncAnalysis,
owner_decl: DeclIndex,
ty: Index,
zir_body_inst: TrackedInst.Index,
lbrace_line: u32,
rbrace_line: u32,
lbrace_column: u32,
rbrace_column: u32,
};
/// Trailing:
/// 0. If `analysis.inferred_error_set` is `true`, `Index` of an `error_set` which
/// is a regular error set corresponding to the finished inferred error set.
/// A `none` value marks that the inferred error set is not resolved yet.
/// 1. For each parameter of generic_owner: `Index` if comptime, otherwise `none`
pub const FuncInstance = struct {
analysis: FuncAnalysis,
// Needed by the linker for codegen. Not part of hashing or equality.
owner_decl: DeclIndex,
ty: Index,
branch_quota: u32,
/// Points to a `FuncDecl`.
generic_owner: Index,
};
pub const FuncCoerced = struct {
ty: Index,
func: Index,
};
/// Trailing:
/// 0. name: NullTerminatedString for each names_len
pub const ErrorSet = struct {
names_len: u32,
/// Maps error names to declaration index.
names_map: MapIndex,
};
/// Trailing:
/// 0. comptime_bits: u32, // if has_comptime_bits
/// 1. noalias_bits: u32, // if has_noalias_bits
/// 2. param_type: Index for each params_len
pub const TypeFunction = struct {
params_len: u32,
return_type: Index,
flags: Flags,
pub const Flags = packed struct(u32) {
cc: std.builtin.CallingConvention,
is_var_args: bool,
is_generic: bool,
has_comptime_bits: bool,
has_noalias_bits: bool,
is_noinline: bool,
cc_is_generic: bool,
section_is_generic: bool,
addrspace_is_generic: bool,
_: u16 = 0,
};
};
/// Trailing:
/// 0. captures_len: u32 // if `any_captures`
/// 1. capture: CaptureValue // for each `captures_len`
/// 2. type_hash: PackedU64 // if `is_reified`
/// 3. field type: Index for each field; declaration order
/// 4. field align: Alignment for each field; declaration order
pub const TypeUnion = struct {
flags: Flags,
/// This could be provided through the tag type, but it is more convenient
/// to store it directly. This is also necessary for `dumpStatsFallible` to
/// work on unresolved types.
fields_len: u32,
/// Only valid after .have_layout
size: u32,
/// Only valid after .have_layout
padding: u32,
decl: DeclIndex,
namespace: OptionalNamespaceIndex,
/// The enum that provides the list of field names and values.
tag_ty: Index,
zir_index: TrackedInst.Index,
pub const Flags = packed struct(u32) {
any_captures: bool,
runtime_tag: LoadedUnionType.RuntimeTag,
/// If false, the field alignment trailing data is omitted.
any_aligned_fields: bool,
layout: std.builtin.Type.ContainerLayout,
status: LoadedUnionType.Status,
requires_comptime: RequiresComptime,
assumed_runtime_bits: bool,
assumed_pointer_aligned: bool,
alignment: Alignment,
is_reified: bool,
_: u12 = 0,
};
};
/// Trailing:
/// 0. captures_len: u32 // if `any_captures`
/// 1. capture: CaptureValue // for each `captures_len`
/// 2. type_hash: PackedU64 // if `is_reified`
/// 3. type: Index for each fields_len
/// 4. name: NullTerminatedString for each fields_len
/// 5. init: Index for each fields_len // if tag is type_struct_packed_inits
pub const TypeStructPacked = struct {
decl: DeclIndex,
zir_index: TrackedInst.Index,
fields_len: u32,
namespace: OptionalNamespaceIndex,
backing_int_ty: Index,
names_map: MapIndex,
flags: Flags,
pub const Flags = packed struct(u32) {
any_captures: bool,
/// Dependency loop detection when resolving field inits.
field_inits_wip: bool,
inits_resolved: bool,
is_reified: bool,
_: u28 = 0,
};
};
/// At first I thought of storing the denormalized data externally, such as...
///
/// * runtime field order
/// * calculated field offsets
/// * size and alignment of the struct
///
/// ...since these can be computed based on the other data here. However,
/// this data does need to be memoized, and therefore stored in memory
/// while the compiler is running, in order to avoid O(N^2) logic in many
/// places. Since the data can be stored compactly in the InternPool
/// representation, it is better for memory usage to store denormalized data
/// here, and potentially also better for performance as well. It's also simpler
/// than coming up with some other scheme for the data.
///
/// Trailing:
/// 0. captures_len: u32 // if `any_captures`
/// 1. capture: CaptureValue // for each `captures_len`
/// 2. type_hash: PackedU64 // if `is_reified`
/// 3. type: Index for each field in declared order
/// 4. if not is_tuple:
/// names_map: MapIndex,
/// name: NullTerminatedString // for each field in declared order
/// 5. if any_default_inits:
/// init: Index // for each field in declared order
/// 6. if has_namespace:
/// namespace: NamespaceIndex
/// 7. if any_aligned_fields:
/// align: Alignment // for each field in declared order
/// 8. if any_comptime_fields:
/// field_is_comptime_bits: u32 // minimal number of u32s needed, LSB is field 0
/// 9. if not is_extern:
/// field_index: RuntimeOrder // for each field in runtime order
/// 10. field_offset: u32 // for each field in declared order, undef until layout_resolved
pub const TypeStruct = struct {
decl: DeclIndex,
zir_index: TrackedInst.Index,
fields_len: u32,
flags: Flags,
size: u32,
pub const Flags = packed struct(u32) {
any_captures: bool,
is_extern: bool,
known_non_opv: bool,
requires_comptime: RequiresComptime,
is_tuple: bool,
assumed_runtime_bits: bool,
assumed_pointer_aligned: bool,
has_namespace: bool,
any_comptime_fields: bool,
any_default_inits: bool,
any_aligned_fields: bool,
/// `.none` until layout_resolved
alignment: Alignment,
/// Dependency loop detection when resolving struct alignment.
alignment_wip: bool,
/// Dependency loop detection when resolving field types.
field_types_wip: bool,
/// Dependency loop detection when resolving struct layout.
layout_wip: bool,
/// Indicates whether `size`, `alignment`, runtime field order, and
/// field offets are populated.
layout_resolved: bool,
/// Dependency loop detection when resolving field inits.
field_inits_wip: bool,
/// Indicates whether `field_inits` has been resolved.
inits_resolved: bool,
// The types and all its fields have had their layout resolved. Even through pointer,
// which `layout_resolved` does not ensure.
fully_resolved: bool,
is_reified: bool,
_: u6 = 0,
};
};
/// Trailing:
/// 0. capture: CaptureValue // for each `captures_len`
pub const TypeOpaque = struct {
/// The opaque's owner Decl.
decl: DeclIndex,
/// Contains the declarations inside this opaque.
namespace: OptionalNamespaceIndex,
/// The index of the `opaque_decl` instruction.
zir_index: TrackedInst.Index,
/// `std.math.maxInt(u32)` indicates this type is reified.
captures_len: u32,
};
};
/// State that is mutable during semantic analysis. This data is not used for
/// equality or hashing, except for `inferred_error_set` which is considered
/// to be part of the type of the function.
pub const FuncAnalysis = packed struct(u32) {
state: State,
is_cold: bool,
is_noinline: bool,
calls_or_awaits_errorable_fn: bool,
stack_alignment: Alignment,
/// True if this function has an inferred error set.
inferred_error_set: bool,
_: u14 = 0,
pub const State = enum(u8) {
/// This function has not yet undergone analysis, because we have not
/// seen a potential runtime call. It may be analyzed in future.
none,
/// Analysis for this function has been queued, but not yet completed.
queued,
/// This function intentionally only has ZIR generated because it is marked
/// inline, which means no runtime version of the function will be generated.
inline_only,
in_progress,
/// There will be a corresponding ErrorMsg in Module.failed_decls
sema_failure,
/// This function might be OK but it depends on another Decl which did not
/// successfully complete semantic analysis.
dependency_failure,
/// There will be a corresponding ErrorMsg in Module.failed_decls.
/// Indicates that semantic analysis succeeded, but code generation for
/// this function failed.
codegen_failure,
/// Semantic analysis and code generation of this function succeeded.
success,
};
};
pub const Bytes = struct {
/// The type of the aggregate
ty: Index,
/// Index into string_bytes, of len ip.aggregateTypeLen(ty)
bytes: String,
};
pub const Repeated = struct {
/// The type of the aggregate.
ty: Index,
/// The value of every element.
elem_val: Index,
};
/// Trailing:
/// 0. type: Index for each fields_len
/// 1. value: Index for each fields_len
/// 2. name: NullTerminatedString for each fields_len
/// The set of field names is omitted when the `Tag` is `type_tuple_anon`.
pub const TypeStructAnon = struct {
fields_len: u32,
};
/// Having `SimpleType` and `SimpleValue` in separate enums makes it easier to
/// implement logic that only wants to deal with types because the logic can
/// ignore all simple values. Note that technically, types are values.
pub const SimpleType = enum(u32) {
f16,
f32,
f64,
f80,
f128,
usize,
isize,
c_char,
c_short,
c_ushort,
c_int,
c_uint,
c_long,
c_ulong,
c_longlong,
c_ulonglong,
c_longdouble,
anyopaque,
bool,
void,
type,
anyerror,
comptime_int,
comptime_float,
noreturn,
null,
undefined,
enum_literal,
atomic_order,
atomic_rmw_op,
calling_convention,
address_space,
float_mode,
reduce_op,
call_modifier,
prefetch_options,
export_options,
extern_options,
type_info,
adhoc_inferred_error_set,
generic_poison,
};
pub const SimpleValue = enum(u32) {
/// This is untyped `undefined`.
undefined,
void,
/// This is untyped `null`.
null,
/// This is the untyped empty struct literal: `.{}`
empty_struct,
true,
false,
@"unreachable",
generic_poison,
};
/// Stored as a power-of-two, with one special value to indicate none.
pub const Alignment = enum(u6) {
@"1" = 0,
@"2" = 1,
@"4" = 2,
@"8" = 3,
@"16" = 4,
@"32" = 5,
@"64" = 6,
none = std.math.maxInt(u6),
_,
pub fn toByteUnitsOptional(a: Alignment) ?u64 {
return switch (a) {
.none => null,
else => @as(u64, 1) << @intFromEnum(a),
};
}
pub fn toByteUnits(a: Alignment, default: u64) u64 {
return switch (a) {
.none => default,
else => @as(u64, 1) << @intFromEnum(a),
};
}
pub fn fromByteUnits(n: u64) Alignment {
if (n == 0) return .none;
assert(std.math.isPowerOfTwo(n));
return @enumFromInt(@ctz(n));
}
pub fn fromNonzeroByteUnits(n: u64) Alignment {
assert(n != 0);
return fromByteUnits(n);
}
pub fn toLog2Units(a: Alignment) u6 {
assert(a != .none);
return @intFromEnum(a);
}
/// This is just a glorified `@enumFromInt` but using it can help
/// document the intended conversion.
/// The parameter uses a u32 for convenience at the callsite.
pub fn fromLog2Units(a: u32) Alignment {
assert(a != @intFromEnum(Alignment.none));
return @enumFromInt(a);
}
pub fn order(lhs: Alignment, rhs: Alignment) std.math.Order {
assert(lhs != .none);
assert(rhs != .none);
return std.math.order(@intFromEnum(lhs), @intFromEnum(rhs));
}
/// Relaxed comparison. We have this as default because a lot of callsites
/// were upgraded from directly using comparison operators on byte units,
/// with the `none` value represented by zero.
/// Prefer `compareStrict` if possible.
pub fn compare(lhs: Alignment, op: std.math.CompareOperator, rhs: Alignment) bool {
return std.math.compare(lhs.toRelaxedCompareUnits(), op, rhs.toRelaxedCompareUnits());
}
pub fn compareStrict(lhs: Alignment, op: std.math.CompareOperator, rhs: Alignment) bool {
assert(lhs != .none);
assert(rhs != .none);
return std.math.compare(@intFromEnum(lhs), op, @intFromEnum(rhs));
}
/// Treats `none` as zero.
/// This matches previous behavior of using `@max` directly on byte units.
/// Prefer `maxStrict` if possible.
pub fn max(lhs: Alignment, rhs: Alignment) Alignment {
if (lhs == .none) return rhs;
if (rhs == .none) return lhs;
return maxStrict(lhs, rhs);
}
pub fn maxStrict(lhs: Alignment, rhs: Alignment) Alignment {
assert(lhs != .none);
assert(rhs != .none);
return @enumFromInt(@max(@intFromEnum(lhs), @intFromEnum(rhs)));
}
/// Treats `none` as zero.
/// This matches previous behavior of using `@min` directly on byte units.
/// Prefer `minStrict` if possible.
pub fn min(lhs: Alignment, rhs: Alignment) Alignment {
if (lhs == .none) return lhs;
if (rhs == .none) return rhs;
return minStrict(lhs, rhs);
}
pub fn minStrict(lhs: Alignment, rhs: Alignment) Alignment {
assert(lhs != .none);
assert(rhs != .none);
return @enumFromInt(@min(@intFromEnum(lhs), @intFromEnum(rhs)));
}
/// Align an address forwards to this alignment.
pub fn forward(a: Alignment, addr: u64) u64 {
assert(a != .none);
const x = (@as(u64, 1) << @intFromEnum(a)) - 1;
return (addr + x) & ~x;
}
/// Align an address backwards to this alignment.
pub fn backward(a: Alignment, addr: u64) u64 {
assert(a != .none);
const x = (@as(u64, 1) << @intFromEnum(a)) - 1;
return addr & ~x;
}
/// Check if an address is aligned to this amount.
pub fn check(a: Alignment, addr: u64) bool {
assert(a != .none);
return @ctz(addr) >= @intFromEnum(a);
}
/// An array of `Alignment` objects existing within the `extra` array.
/// This type exists to provide a struct with lifetime that is
/// not invalidated when items are added to the `InternPool`.
pub const Slice = struct {
start: u32,
/// This is the number of alignment values, not the number of u32 elements.
len: u32,
pub fn get(slice: Slice, ip: *const InternPool) []Alignment {
// TODO: implement @ptrCast between slices changing the length
//const bytes: []u8 = @ptrCast(ip.extra.items[slice.start..]);
const bytes: []u8 = std.mem.sliceAsBytes(ip.extra.items[slice.start..]);
return @ptrCast(bytes[0..slice.len]);
}
};
pub fn toRelaxedCompareUnits(a: Alignment) u8 {
const n: u8 = @intFromEnum(a);
assert(n <= @intFromEnum(Alignment.none));
if (n == @intFromEnum(Alignment.none)) return 0;
return n + 1;
}
const LlvmBuilderAlignment = @import("codegen/llvm/Builder.zig").Alignment;
pub fn toLlvm(this: @This()) LlvmBuilderAlignment {
return @enumFromInt(@intFromEnum(this));
}
pub fn fromLlvm(other: LlvmBuilderAlignment) @This() {
return @enumFromInt(@intFromEnum(other));
}
};
/// Used for non-sentineled arrays that have length fitting in u32, as well as
/// vectors.
pub const Vector = struct {
len: u32,
child: Index,
};
pub const Array = struct {
len0: u32,
len1: u32,
child: Index,
sentinel: Index,
pub const Length = PackedU64;
pub fn getLength(a: Array) u64 {
return (PackedU64{
.a = a.len0,
.b = a.len1,
}).get();
}
};
/// Trailing:
/// 0. owner_union: Index // if `zir_index == .none`
/// 1. capture: CaptureValue // for each `captures_len`
/// 2. type_hash: PackedU64 // if reified (`captures_len == std.math.maxInt(u32)`)
/// 3. field name: NullTerminatedString for each fields_len; declaration order
/// 4. tag value: Index for each fields_len; declaration order
pub const EnumExplicit = struct {
/// The Decl that corresponds to the enum itself.
decl: DeclIndex,
/// `std.math.maxInt(u32)` indicates this type is reified.
captures_len: u32,
/// This may be `none` if there are no declarations.
namespace: OptionalNamespaceIndex,
/// An integer type which is used for the numerical value of the enum, which
/// has been explicitly provided by the enum declaration.
int_tag_type: Index,
fields_len: u32,
/// Maps field names to declaration index.
names_map: MapIndex,
/// Maps field values to declaration index.
/// If this is `none`, it means the trailing tag values are absent because
/// they are auto-numbered.
values_map: OptionalMapIndex,
/// `none` means this is a generated tag type.
/// There will be a trailing union type for which this is a tag.
zir_index: TrackedInst.Index.Optional,
};
/// Trailing:
/// 0. owner_union: Index // if `zir_index == .none`
/// 1. capture: CaptureValue // for each `captures_len`
/// 2. type_hash: PackedU64 // if reified (`captures_len == std.math.maxInt(u32)`)
/// 3. field name: NullTerminatedString for each fields_len; declaration order
pub const EnumAuto = struct {
/// The Decl that corresponds to the enum itself.
decl: DeclIndex,
/// `std.math.maxInt(u32)` indicates this type is reified.
captures_len: u32,
/// This may be `none` if there are no declarations.
namespace: OptionalNamespaceIndex,
/// An integer type which is used for the numerical value of the enum, which
/// was inferred by Zig based on the number of tags.
int_tag_type: Index,
fields_len: u32,
/// Maps field names to declaration index.
names_map: MapIndex,
/// `none` means this is a generated tag type.
/// There will be a trailing union type for which this is a tag.
zir_index: TrackedInst.Index.Optional,
};
pub const PackedU64 = packed struct(u64) {
a: u32,
b: u32,
pub fn get(x: PackedU64) u64 {
return @bitCast(x);
}
pub fn init(x: u64) PackedU64 {
return @bitCast(x);
}
};
pub const PtrDecl = struct {
ty: Index,
decl: DeclIndex,
};
pub const PtrAnonDecl = struct {
ty: Index,
val: Index,
};
pub const PtrAnonDeclAligned = struct {
ty: Index,
val: Index,
/// Must be nonequal to `ty`. Only the alignment from this value is important.
orig_ty: Index,
};
pub const PtrComptimeAlloc = struct {
ty: Index,
index: ComptimeAllocIndex,
};
pub const PtrComptimeField = struct {
ty: Index,
field_val: Index,
};
pub const PtrBase = struct {
ty: Index,
base: Index,
};
pub const PtrBaseIndex = struct {
ty: Index,
base: Index,
index: Index,
};
pub const PtrSlice = struct {
/// The slice type.
ty: Index,
/// A many pointer value.
ptr: Index,
/// A usize value.
len: Index,
};
/// Trailing: Limb for every limbs_len
pub const Int = struct {
ty: Index,
limbs_len: u32,
};
pub const IntSmall = struct {
ty: Index,
value: u32,
};
pub const IntLazy = struct {
ty: Index,
lazy_ty: Index,
};
/// A f64 value, broken up into 2 u32 parts.
pub const Float64 = struct {
piece0: u32,
piece1: u32,
pub fn get(self: Float64) f64 {
const int_bits = @as(u64, self.piece0) | (@as(u64, self.piece1) << 32);
return @bitCast(int_bits);
}
fn pack(val: f64) Float64 {
const bits = @as(u64, @bitCast(val));
return .{
.piece0 = @as(u32, @truncate(bits)),
.piece1 = @as(u32, @truncate(bits >> 32)),
};
}
};
/// A f80 value, broken up into 2 u32 parts and a u16 part zero-padded to a u32.
pub const Float80 = struct {
piece0: u32,
piece1: u32,
piece2: u32, // u16 part, top bits
pub fn get(self: Float80) f80 {
const int_bits = @as(u80, self.piece0) |
(@as(u80, self.piece1) << 32) |
(@as(u80, self.piece2) << 64);
return @bitCast(int_bits);
}
fn pack(val: f80) Float80 {
const bits = @as(u80, @bitCast(val));
return .{
.piece0 = @as(u32, @truncate(bits)),
.piece1 = @as(u32, @truncate(bits >> 32)),
.piece2 = @as(u16, @truncate(bits >> 64)),
};
}
};
/// A f128 value, broken up into 4 u32 parts.
pub const Float128 = struct {
piece0: u32,
piece1: u32,
piece2: u32,
piece3: u32,
pub fn get(self: Float128) f128 {
const int_bits = @as(u128, self.piece0) |
(@as(u128, self.piece1) << 32) |
(@as(u128, self.piece2) << 64) |
(@as(u128, self.piece3) << 96);
return @bitCast(int_bits);
}
fn pack(val: f128) Float128 {
const bits = @as(u128, @bitCast(val));
return .{
.piece0 = @as(u32, @truncate(bits)),
.piece1 = @as(u32, @truncate(bits >> 32)),
.piece2 = @as(u32, @truncate(bits >> 64)),
.piece3 = @as(u32, @truncate(bits >> 96)),
};
}
};
/// Trailing:
/// 0. arg value: Index for each args_len
pub const MemoizedCall = struct {
func: Index,
args_len: u32,
result: Index,
};
pub fn init(ip: *InternPool, gpa: Allocator) !void {
assert(ip.items.len == 0);
// Reserve string index 0 for an empty string.
assert((try ip.getOrPutString(gpa, "")) == .empty);
// So that we can use `catch unreachable` below.
try ip.items.ensureUnusedCapacity(gpa, static_keys.len);
try ip.map.ensureUnusedCapacity(gpa, static_keys.len);
try ip.extra.ensureUnusedCapacity(gpa, static_keys.len);
// This inserts all the statically-known values into the intern pool in the
// order expected.
for (static_keys[0..@intFromEnum(Index.empty_struct_type)]) |key| {
_ = ip.get(gpa, key) catch unreachable;
}
_ = ip.getAnonStructType(gpa, .{
.types = &.{},
.names = &.{},
.values = &.{},
}) catch unreachable;
for (static_keys[@intFromEnum(Index.empty_struct_type) + 1 ..]) |key| {
_ = ip.get(gpa, key) catch unreachable;
}
if (std.debug.runtime_safety) {
// Sanity check.
assert(ip.indexToKey(.bool_true).simple_value == .true);
assert(ip.indexToKey(.bool_false).simple_value == .false);
const cc_inline = ip.indexToKey(.calling_convention_inline).enum_tag.int;
const cc_c = ip.indexToKey(.calling_convention_c).enum_tag.int;
assert(ip.indexToKey(cc_inline).int.storage.u64 ==
@intFromEnum(std.builtin.CallingConvention.Inline));
assert(ip.indexToKey(cc_c).int.storage.u64 ==
@intFromEnum(std.builtin.CallingConvention.C));
assert(ip.indexToKey(ip.typeOf(cc_inline)).int_type.bits ==
@typeInfo(@typeInfo(std.builtin.CallingConvention).Enum.tag_type).Int.bits);
}
assert(ip.items.len == static_keys.len);
}
pub fn deinit(ip: *InternPool, gpa: Allocator) void {
ip.map.deinit(gpa);
ip.items.deinit(gpa);
ip.extra.deinit(gpa);
ip.limbs.deinit(gpa);
ip.string_bytes.deinit(gpa);
ip.decls_free_list.deinit(gpa);
ip.allocated_decls.deinit(gpa);
ip.namespaces_free_list.deinit(gpa);
ip.allocated_namespaces.deinit(gpa);
for (ip.maps.items) |*map| map.deinit(gpa);
ip.maps.deinit(gpa);
ip.string_table.deinit(gpa);
ip.tracked_insts.deinit(gpa);
ip.src_hash_deps.deinit(gpa);
ip.decl_val_deps.deinit(gpa);
ip.func_ies_deps.deinit(gpa);
ip.namespace_deps.deinit(gpa);
ip.namespace_name_deps.deinit(gpa);
ip.first_dependency.deinit(gpa);
ip.dep_entries.deinit(gpa);
ip.free_dep_entries.deinit(gpa);
ip.* = undefined;
}
pub fn indexToKey(ip: *const InternPool, index: Index) Key {
assert(index != .none);
const item = ip.items.get(@intFromEnum(index));
const data = item.data;
return switch (item.tag) {
.removed => unreachable,
.type_int_signed => .{
.int_type = .{
.signedness = .signed,
.bits = @as(u16, @intCast(data)),
},
},
.type_int_unsigned => .{
.int_type = .{
.signedness = .unsigned,
.bits = @as(u16, @intCast(data)),
},
},
.type_array_big => {
const array_info = ip.extraData(Array, data);
return .{ .array_type = .{
.len = array_info.getLength(),
.child = array_info.child,
.sentinel = array_info.sentinel,
} };
},
.type_array_small => {
const array_info = ip.extraData(Vector, data);
return .{ .array_type = .{
.len = array_info.len,
.child = array_info.child,
.sentinel = .none,
} };
},
.simple_type => .{ .simple_type = @as(SimpleType, @enumFromInt(data)) },
.simple_value => .{ .simple_value = @as(SimpleValue, @enumFromInt(data)) },
.type_vector => {
const vector_info = ip.extraData(Vector, data);
return .{ .vector_type = .{
.len = vector_info.len,
.child = vector_info.child,
} };
},
.type_pointer => .{ .ptr_type = ip.extraData(Tag.TypePointer, data) },
.type_slice => {
assert(ip.items.items(.tag)[data] == .type_pointer);
var ptr_info = ip.extraData(Tag.TypePointer, ip.items.items(.data)[data]);
ptr_info.flags.size = .Slice;
return .{ .ptr_type = ptr_info };
},
.type_optional => .{ .opt_type = @enumFromInt(data) },
.type_anyframe => .{ .anyframe_type = @enumFromInt(data) },
.type_error_union => .{ .error_union_type = ip.extraData(Key.ErrorUnionType, data) },
.type_anyerror_union => .{ .error_union_type = .{
.error_set_type = .anyerror_type,
.payload_type = @enumFromInt(data),
} },
.type_error_set => .{ .error_set_type = ip.extraErrorSet(data) },
.type_inferred_error_set => .{
.inferred_error_set_type = @enumFromInt(data),
},
.type_opaque => .{ .opaque_type = ns: {
const extra = ip.extraDataTrail(Tag.TypeOpaque, data);
if (extra.data.captures_len == std.math.maxInt(u32)) {
break :ns .{ .reified = .{
.zir_index = extra.data.zir_index,
.type_hash = 0,
} };
}
break :ns .{ .declared = .{
.zir_index = extra.data.zir_index,
.captures = .{ .owned = .{
.start = extra.end,
.len = extra.data.captures_len,
} },
} };
} },
.type_struct => .{ .struct_type = ns: {
if (data == 0) break :ns .empty_struct;
const extra = ip.extraDataTrail(Tag.TypeStruct, data);
if (extra.data.flags.is_reified) {
assert(!extra.data.flags.any_captures);
break :ns .{ .reified = .{
.zir_index = extra.data.zir_index,
.type_hash = ip.extraData(PackedU64, extra.end).get(),
} };
}
break :ns .{ .declared = .{
.zir_index = extra.data.zir_index,
.captures = .{ .owned = if (extra.data.flags.any_captures) .{
.start = extra.end + 1,
.len = ip.extra.items[extra.end],
} else .{ .start = 0, .len = 0 } },
} };
} },
.type_struct_packed, .type_struct_packed_inits => .{ .struct_type = ns: {
const extra = ip.extraDataTrail(Tag.TypeStructPacked, data);
if (extra.data.flags.is_reified) {
assert(!extra.data.flags.any_captures);
break :ns .{ .reified = .{
.zir_index = extra.data.zir_index,
.type_hash = ip.extraData(PackedU64, extra.end).get(),
} };
}
break :ns .{ .declared = .{
.zir_index = extra.data.zir_index,
.captures = .{ .owned = if (extra.data.flags.any_captures) .{
.start = extra.end + 1,
.len = ip.extra.items[extra.end],
} else .{ .start = 0, .len = 0 } },
} };
} },
.type_struct_anon => .{ .anon_struct_type = extraTypeStructAnon(ip, data) },
.type_tuple_anon => .{ .anon_struct_type = extraTypeTupleAnon(ip, data) },
.type_union => .{ .union_type = ns: {
const extra = ip.extraDataTrail(Tag.TypeUnion, data);
if (extra.data.flags.is_reified) {
assert(!extra.data.flags.any_captures);
break :ns .{ .reified = .{
.zir_index = extra.data.zir_index,
.type_hash = ip.extraData(PackedU64, extra.end).get(),
} };
}
break :ns .{ .declared = .{
.zir_index = extra.data.zir_index,
.captures = .{ .owned = if (extra.data.flags.any_captures) .{
.start = extra.end + 1,
.len = ip.extra.items[extra.end],
} else .{ .start = 0, .len = 0 } },
} };
} },
.type_enum_auto => .{ .enum_type = ns: {
const extra = ip.extraDataTrail(EnumAuto, data);
const zir_index = extra.data.zir_index.unwrap() orelse {
assert(extra.data.captures_len == 0);
break :ns .{ .generated_tag = .{
.union_type = @enumFromInt(ip.extra.items[extra.end]),
} };
};
if (extra.data.captures_len == std.math.maxInt(u32)) {
break :ns .{ .reified = .{
.zir_index = zir_index,
.type_hash = ip.extraData(PackedU64, extra.end).get(),
} };
}
break :ns .{ .declared = .{
.zir_index = zir_index,
.captures = .{ .owned = .{
.start = extra.end,
.len = extra.data.captures_len,
} },
} };
} },
.type_enum_explicit, .type_enum_nonexhaustive => .{ .enum_type = ns: {
const extra = ip.extraDataTrail(EnumExplicit, data);
const zir_index = extra.data.zir_index.unwrap() orelse {
assert(extra.data.captures_len == 0);
break :ns .{ .generated_tag = .{
.union_type = @enumFromInt(ip.extra.items[extra.end]),
} };
};
if (extra.data.captures_len == std.math.maxInt(u32)) {
break :ns .{ .reified = .{
.zir_index = zir_index,
.type_hash = ip.extraData(PackedU64, extra.end).get(),
} };
}
break :ns .{ .declared = .{
.zir_index = zir_index,
.captures = .{ .owned = .{
.start = extra.end,
.len = extra.data.captures_len,
} },
} };
} },
.type_function => .{ .func_type = ip.extraFuncType(data) },
.undef => .{ .undef = @as(Index, @enumFromInt(data)) },
.opt_null => .{ .opt = .{
.ty = @as(Index, @enumFromInt(data)),
.val = .none,
} },
.opt_payload => {
const extra = ip.extraData(Tag.TypeValue, data);
return .{ .opt = .{
.ty = extra.ty,
.val = extra.val,
} };
},
.ptr_decl => {
const info = ip.extraData(PtrDecl, data);
return .{ .ptr = .{
.ty = info.ty,
.addr = .{ .decl = info.decl },
} };
},
.ptr_comptime_alloc => {
const info = ip.extraData(PtrComptimeAlloc, data);
return .{ .ptr = .{
.ty = info.ty,
.addr = .{ .comptime_alloc = info.index },
} };
},
.ptr_anon_decl => {
const info = ip.extraData(PtrAnonDecl, data);
return .{ .ptr = .{
.ty = info.ty,
.addr = .{ .anon_decl = .{
.val = info.val,
.orig_ty = info.ty,
} },
} };
},
.ptr_anon_decl_aligned => {
const info = ip.extraData(PtrAnonDeclAligned, data);
return .{ .ptr = .{
.ty = info.ty,
.addr = .{ .anon_decl = .{
.val = info.val,
.orig_ty = info.orig_ty,
} },
} };
},
.ptr_comptime_field => {
const info = ip.extraData(PtrComptimeField, data);
return .{ .ptr = .{
.ty = info.ty,
.addr = .{ .comptime_field = info.field_val },
} };
},
.ptr_int => {
const info = ip.extraData(PtrBase, data);
return .{ .ptr = .{
.ty = info.ty,
.addr = .{ .int = info.base },
} };
},
.ptr_eu_payload => {
const info = ip.extraData(PtrBase, data);
return .{ .ptr = .{
.ty = info.ty,
.addr = .{ .eu_payload = info.base },
} };
},
.ptr_opt_payload => {
const info = ip.extraData(PtrBase, data);
return .{ .ptr = .{
.ty = info.ty,
.addr = .{ .opt_payload = info.base },
} };
},
.ptr_elem => {
// Avoid `indexToKey` recursion by asserting the tag encoding.
const info = ip.extraData(PtrBaseIndex, data);
const index_item = ip.items.get(@intFromEnum(info.index));
return switch (index_item.tag) {
.int_usize => .{ .ptr = .{
.ty = info.ty,
.addr = .{ .elem = .{
.base = info.base,
.index = index_item.data,
} },
} },
.int_positive => @panic("TODO"), // implement along with behavior test coverage
else => unreachable,
};
},
.ptr_field => {
// Avoid `indexToKey` recursion by asserting the tag encoding.
const info = ip.extraData(PtrBaseIndex, data);
const index_item = ip.items.get(@intFromEnum(info.index));
return switch (index_item.tag) {
.int_usize => .{ .ptr = .{
.ty = info.ty,
.addr = .{ .field = .{
.base = info.base,
.index = index_item.data,
} },
} },
.int_positive => @panic("TODO"), // implement along with behavior test coverage
else => unreachable,
};
},
.ptr_slice => {
const info = ip.extraData(PtrSlice, data);
return .{ .slice = .{
.ty = info.ty,
.ptr = info.ptr,
.len = info.len,
} };
},
.int_u8 => .{ .int = .{
.ty = .u8_type,
.storage = .{ .u64 = data },
} },
.int_u16 => .{ .int = .{
.ty = .u16_type,
.storage = .{ .u64 = data },
} },
.int_u32 => .{ .int = .{
.ty = .u32_type,
.storage = .{ .u64 = data },
} },
.int_i32 => .{ .int = .{
.ty = .i32_type,
.storage = .{ .i64 = @as(i32, @bitCast(data)) },
} },
.int_usize => .{ .int = .{
.ty = .usize_type,
.storage = .{ .u64 = data },
} },
.int_comptime_int_u32 => .{ .int = .{
.ty = .comptime_int_type,
.storage = .{ .u64 = data },
} },
.int_comptime_int_i32 => .{ .int = .{
.ty = .comptime_int_type,
.storage = .{ .i64 = @as(i32, @bitCast(data)) },
} },
.int_positive => ip.indexToKeyBigInt(data, true),
.int_negative => ip.indexToKeyBigInt(data, false),
.int_small => {
const info = ip.extraData(IntSmall, data);
return .{ .int = .{
.ty = info.ty,
.storage = .{ .u64 = info.value },
} };
},
.int_lazy_align, .int_lazy_size => |tag| {
const info = ip.extraData(IntLazy, data);
return .{ .int = .{
.ty = info.ty,
.storage = switch (tag) {
.int_lazy_align => .{ .lazy_align = info.lazy_ty },
.int_lazy_size => .{ .lazy_size = info.lazy_ty },
else => unreachable,
},
} };
},
.float_f16 => .{ .float = .{
.ty = .f16_type,
.storage = .{ .f16 = @as(f16, @bitCast(@as(u16, @intCast(data)))) },
} },
.float_f32 => .{ .float = .{
.ty = .f32_type,
.storage = .{ .f32 = @as(f32, @bitCast(data)) },
} },
.float_f64 => .{ .float = .{
.ty = .f64_type,
.storage = .{ .f64 = ip.extraData(Float64, data).get() },
} },
.float_f80 => .{ .float = .{
.ty = .f80_type,
.storage = .{ .f80 = ip.extraData(Float80, data).get() },
} },
.float_f128 => .{ .float = .{
.ty = .f128_type,
.storage = .{ .f128 = ip.extraData(Float128, data).get() },
} },
.float_c_longdouble_f80 => .{ .float = .{
.ty = .c_longdouble_type,
.storage = .{ .f80 = ip.extraData(Float80, data).get() },
} },
.float_c_longdouble_f128 => .{ .float = .{
.ty = .c_longdouble_type,
.storage = .{ .f128 = ip.extraData(Float128, data).get() },
} },
.float_comptime_float => .{ .float = .{
.ty = .comptime_float_type,
.storage = .{ .f128 = ip.extraData(Float128, data).get() },
} },
.variable => {
const extra = ip.extraData(Tag.Variable, data);
return .{ .variable = .{
.ty = extra.ty,
.init = extra.init,
.decl = extra.decl,
.lib_name = extra.lib_name,
.is_extern = extra.flags.is_extern,
.is_const = extra.flags.is_const,
.is_threadlocal = extra.flags.is_threadlocal,
.is_weak_linkage = extra.flags.is_weak_linkage,
} };
},
.extern_func => .{ .extern_func = ip.extraData(Tag.ExternFunc, data) },
.func_instance => .{ .func = ip.extraFuncInstance(data) },
.func_decl => .{ .func = ip.extraFuncDecl(data) },
.func_coerced => .{ .func = ip.extraFuncCoerced(data) },
.only_possible_value => {
const ty: Index = @enumFromInt(data);
const ty_item = ip.items.get(@intFromEnum(ty));
return switch (ty_item.tag) {
.type_array_big => {
const sentinel = @as(
*const [1]Index,
@ptrCast(&ip.extra.items[ty_item.data + std.meta.fieldIndex(Array, "sentinel").?]),
);
return .{ .aggregate = .{
.ty = ty,
.storage = .{ .elems = sentinel[0..@intFromBool(sentinel[0] != .none)] },
} };
},
.type_array_small,
.type_vector,
.type_struct_packed,
=> .{ .aggregate = .{
.ty = ty,
.storage = .{ .elems = &.{} },
} },
// There is only one possible value precisely due to the
// fact that this values slice is fully populated!
.type_struct, .type_struct_packed_inits => {
const info = loadStructType(ip, ty);
return .{ .aggregate = .{
.ty = ty,
.storage = .{ .elems = @ptrCast(info.field_inits.get(ip)) },
} };
},
// There is only one possible value precisely due to the
// fact that this values slice is fully populated!
.type_struct_anon, .type_tuple_anon => {
const type_struct_anon = ip.extraDataTrail(TypeStructAnon, ty_item.data);
const fields_len = type_struct_anon.data.fields_len;
const values = ip.extra.items[type_struct_anon.end + fields_len ..][0..fields_len];
return .{ .aggregate = .{
.ty = ty,
.storage = .{ .elems = @ptrCast(values) },
} };
},
.type_enum_auto,
.type_enum_explicit,
.type_union,
=> .{ .empty_enum_value = ty },
else => unreachable,
};
},
.bytes => {
const extra = ip.extraData(Bytes, data);
const len: u32 = @intCast(ip.aggregateTypeLenIncludingSentinel(extra.ty));
return .{ .aggregate = .{
.ty = extra.ty,
.storage = .{ .bytes = ip.string_bytes.items[@intFromEnum(extra.bytes)..][0..len] },
} };
},
.aggregate => {
const extra = ip.extraDataTrail(Tag.Aggregate, data);
const len: u32 = @intCast(ip.aggregateTypeLenIncludingSentinel(extra.data.ty));
const fields: []const Index = @ptrCast(ip.extra.items[extra.end..][0..len]);
return .{ .aggregate = .{
.ty = extra.data.ty,
.storage = .{ .elems = fields },
} };
},
.repeated => {
const extra = ip.extraData(Repeated, data);
return .{ .aggregate = .{
.ty = extra.ty,
.storage = .{ .repeated_elem = extra.elem_val },
} };
},
.union_value => .{ .un = ip.extraData(Key.Union, data) },
.error_set_error => .{ .err = ip.extraData(Key.Error, data) },
.error_union_error => {
const extra = ip.extraData(Key.Error, data);
return .{ .error_union = .{
.ty = extra.ty,
.val = .{ .err_name = extra.name },
} };
},
.error_union_payload => {
const extra = ip.extraData(Tag.TypeValue, data);
return .{ .error_union = .{
.ty = extra.ty,
.val = .{ .payload = extra.val },
} };
},
.enum_literal => .{ .enum_literal = @as(NullTerminatedString, @enumFromInt(data)) },
.enum_tag => .{ .enum_tag = ip.extraData(Tag.EnumTag, data) },
.memoized_call => {
const extra = ip.extraDataTrail(MemoizedCall, data);
return .{ .memoized_call = .{
.func = extra.data.func,
.arg_values = @as([]const Index, @ptrCast(ip.extra.items[extra.end..][0..extra.data.args_len])),
.result = extra.data.result,
} };
},
};
}
fn extraErrorSet(ip: *const InternPool, extra_index: u32) Key.ErrorSetType {
const error_set = ip.extraDataTrail(Tag.ErrorSet, extra_index);
return .{
.names = .{
.start = @intCast(error_set.end),
.len = error_set.data.names_len,
},
.names_map = error_set.data.names_map.toOptional(),
};
}
fn extraTypeStructAnon(ip: *const InternPool, extra_index: u32) Key.AnonStructType {
const type_struct_anon = ip.extraDataTrail(TypeStructAnon, extra_index);
const fields_len = type_struct_anon.data.fields_len;
return .{
.types = .{
.start = type_struct_anon.end,
.len = fields_len,
},
.values = .{
.start = type_struct_anon.end + fields_len,
.len = fields_len,
},
.names = .{
.start = type_struct_anon.end + fields_len + fields_len,
.len = fields_len,
},
};
}
fn extraTypeTupleAnon(ip: *const InternPool, extra_index: u32) Key.AnonStructType {
const type_struct_anon = ip.extraDataTrail(TypeStructAnon, extra_index);
const fields_len = type_struct_anon.data.fields_len;
return .{
.types = .{
.start = type_struct_anon.end,
.len = fields_len,
},
.values = .{
.start = type_struct_anon.end + fields_len,
.len = fields_len,
},
.names = .{
.start = 0,
.len = 0,
},
};
}
fn extraFuncType(ip: *const InternPool, extra_index: u32) Key.FuncType {
const type_function = ip.extraDataTrail(Tag.TypeFunction, extra_index);
var index: usize = type_function.end;
const comptime_bits: u32 = if (!type_function.data.flags.has_comptime_bits) 0 else b: {
const x = ip.extra.items[index];
index += 1;
break :b x;
};
const noalias_bits: u32 = if (!type_function.data.flags.has_noalias_bits) 0 else b: {
const x = ip.extra.items[index];
index += 1;
break :b x;
};
return .{
.param_types = .{
.start = @intCast(index),
.len = type_function.data.params_len,
},
.return_type = type_function.data.return_type,
.comptime_bits = comptime_bits,
.noalias_bits = noalias_bits,
.cc = type_function.data.flags.cc,
.is_var_args = type_function.data.flags.is_var_args,
.is_noinline = type_function.data.flags.is_noinline,
.cc_is_generic = type_function.data.flags.cc_is_generic,
.section_is_generic = type_function.data.flags.section_is_generic,
.addrspace_is_generic = type_function.data.flags.addrspace_is_generic,
.is_generic = type_function.data.flags.is_generic,
};
}
fn extraFuncDecl(ip: *const InternPool, extra_index: u32) Key.Func {
const P = Tag.FuncDecl;
const func_decl = ip.extraDataTrail(P, extra_index);
return .{
.ty = func_decl.data.ty,
.uncoerced_ty = func_decl.data.ty,
.analysis_extra_index = extra_index + std.meta.fieldIndex(P, "analysis").?,
.zir_body_inst_extra_index = extra_index + std.meta.fieldIndex(P, "zir_body_inst").?,
.resolved_error_set_extra_index = if (func_decl.data.analysis.inferred_error_set) func_decl.end else 0,
.branch_quota_extra_index = 0,
.owner_decl = func_decl.data.owner_decl,
.zir_body_inst = func_decl.data.zir_body_inst,
.lbrace_line = func_decl.data.lbrace_line,
.rbrace_line = func_decl.data.rbrace_line,
.lbrace_column = func_decl.data.lbrace_column,
.rbrace_column = func_decl.data.rbrace_column,
.generic_owner = .none,
.comptime_args = .{ .start = 0, .len = 0 },
};
}
fn extraFuncInstance(ip: *const InternPool, extra_index: u32) Key.Func {
const P = Tag.FuncInstance;
const fi = ip.extraDataTrail(P, extra_index);
const func_decl = ip.funcDeclInfo(fi.data.generic_owner);
return .{
.ty = fi.data.ty,
.uncoerced_ty = fi.data.ty,
.analysis_extra_index = extra_index + std.meta.fieldIndex(P, "analysis").?,
.zir_body_inst_extra_index = func_decl.zir_body_inst_extra_index,
.resolved_error_set_extra_index = if (fi.data.analysis.inferred_error_set) fi.end else 0,
.branch_quota_extra_index = extra_index + std.meta.fieldIndex(P, "branch_quota").?,
.owner_decl = fi.data.owner_decl,
.zir_body_inst = func_decl.zir_body_inst,
.lbrace_line = func_decl.lbrace_line,
.rbrace_line = func_decl.rbrace_line,
.lbrace_column = func_decl.lbrace_column,
.rbrace_column = func_decl.rbrace_column,
.generic_owner = fi.data.generic_owner,
.comptime_args = .{
.start = fi.end + @intFromBool(fi.data.analysis.inferred_error_set),
.len = ip.funcTypeParamsLen(func_decl.ty),
},
};
}
fn extraFuncCoerced(ip: *const InternPool, extra_index: u32) Key.Func {
const func_coerced = ip.extraData(Tag.FuncCoerced, extra_index);
const sub_item = ip.items.get(@intFromEnum(func_coerced.func));
var func: Key.Func = switch (sub_item.tag) {
.func_instance => ip.extraFuncInstance(sub_item.data),
.func_decl => ip.extraFuncDecl(sub_item.data),
else => unreachable,
};
func.ty = func_coerced.ty;
return func;
}
fn indexToKeyBigInt(ip: *const InternPool, limb_index: u32, positive: bool) Key {
const int_info = ip.limbData(Int, limb_index);
return .{ .int = .{
.ty = int_info.ty,
.storage = .{ .big_int = .{
.limbs = ip.limbSlice(Int, limb_index, int_info.limbs_len),
.positive = positive,
} },
} };
}
pub fn get(ip: *InternPool, gpa: Allocator, key: Key) Allocator.Error!Index {
const adapter: KeyAdapter = .{ .intern_pool = ip };
const gop = try ip.map.getOrPutAdapted(gpa, key, adapter);
if (gop.found_existing) return @enumFromInt(gop.index);
try ip.items.ensureUnusedCapacity(gpa, 1);
switch (key) {
.int_type => |int_type| {
const t: Tag = switch (int_type.signedness) {
.signed => .type_int_signed,
.unsigned => .type_int_unsigned,
};
ip.items.appendAssumeCapacity(.{
.tag = t,
.data = int_type.bits,
});
},
.ptr_type => |ptr_type| {
assert(ptr_type.child != .none);
assert(ptr_type.sentinel == .none or ip.typeOf(ptr_type.sentinel) == ptr_type.child);
if (ptr_type.flags.size == .Slice) {
_ = ip.map.pop();
var new_key = key;
new_key.ptr_type.flags.size = .Many;
const ptr_type_index = try ip.get(gpa, new_key);
assert(!(try ip.map.getOrPutAdapted(gpa, key, adapter)).found_existing);
try ip.items.ensureUnusedCapacity(gpa, 1);
ip.items.appendAssumeCapacity(.{
.tag = .type_slice,
.data = @intFromEnum(ptr_type_index),
});
return @enumFromInt(ip.items.len - 1);
}
var ptr_type_adjusted = ptr_type;
if (ptr_type.flags.size == .C) ptr_type_adjusted.flags.is_allowzero = true;
ip.items.appendAssumeCapacity(.{
.tag = .type_pointer,
.data = try ip.addExtra(gpa, ptr_type_adjusted),
});
},
.array_type => |array_type| {
assert(array_type.child != .none);
assert(array_type.sentinel == .none or ip.typeOf(array_type.sentinel) == array_type.child);
if (std.math.cast(u32, array_type.len)) |len| {
if (array_type.sentinel == .none) {
ip.items.appendAssumeCapacity(.{
.tag = .type_array_small,
.data = try ip.addExtra(gpa, Vector{
.len = len,
.child = array_type.child,
}),
});
return @enumFromInt(ip.items.len - 1);
}
}
const length = Array.Length.init(array_type.len);
ip.items.appendAssumeCapacity(.{
.tag = .type_array_big,
.data = try ip.addExtra(gpa, Array{
.len0 = length.a,
.len1 = length.b,
.child = array_type.child,
.sentinel = array_type.sentinel,
}),
});
},
.vector_type => |vector_type| {
ip.items.appendAssumeCapacity(.{
.tag = .type_vector,
.data = try ip.addExtra(gpa, Vector{
.len = vector_type.len,
.child = vector_type.child,
}),
});
},
.opt_type => |payload_type| {
assert(payload_type != .none);
ip.items.appendAssumeCapacity(.{
.tag = .type_optional,
.data = @intFromEnum(payload_type),
});
},
.anyframe_type => |payload_type| {
// payload_type might be none, indicating the type is `anyframe`.
ip.items.appendAssumeCapacity(.{
.tag = .type_anyframe,
.data = @intFromEnum(payload_type),
});
},
.error_union_type => |error_union_type| {
ip.items.appendAssumeCapacity(if (error_union_type.error_set_type == .anyerror_type) .{
.tag = .type_anyerror_union,
.data = @intFromEnum(error_union_type.payload_type),
} else .{
.tag = .type_error_union,
.data = try ip.addExtra(gpa, error_union_type),
});
},
.error_set_type => |error_set_type| {
assert(error_set_type.names_map == .none);
assert(std.sort.isSorted(NullTerminatedString, error_set_type.names.get(ip), {}, NullTerminatedString.indexLessThan));
const names = error_set_type.names.get(ip);
const names_map = try ip.addMap(gpa, names.len);
addStringsToMap(ip, names_map, names);
const names_len = error_set_type.names.len;
try ip.extra.ensureUnusedCapacity(gpa, @typeInfo(Tag.ErrorSet).Struct.fields.len + names_len);
ip.items.appendAssumeCapacity(.{
.tag = .type_error_set,
.data = ip.addExtraAssumeCapacity(Tag.ErrorSet{
.names_len = names_len,
.names_map = names_map,
}),
});
ip.extra.appendSliceAssumeCapacity(@ptrCast(error_set_type.names.get(ip)));
},
.inferred_error_set_type => |ies_index| {
ip.items.appendAssumeCapacity(.{
.tag = .type_inferred_error_set,
.data = @intFromEnum(ies_index),
});
},
.simple_type => |simple_type| {
ip.items.appendAssumeCapacity(.{
.tag = .simple_type,
.data = @intFromEnum(simple_type),
});
},
.simple_value => |simple_value| {
ip.items.appendAssumeCapacity(.{
.tag = .simple_value,
.data = @intFromEnum(simple_value),
});
},
.undef => |ty| {
assert(ty != .none);
ip.items.appendAssumeCapacity(.{
.tag = .undef,
.data = @intFromEnum(ty),
});
},
.struct_type => unreachable, // use getStructType() instead
.anon_struct_type => unreachable, // use getAnonStructType() instead
.union_type => unreachable, // use getUnionType() instead
.opaque_type => unreachable, // use getOpaqueType() instead
.enum_type => unreachable, // use getEnumType() instead
.func_type => unreachable, // use getFuncType() instead
.extern_func => unreachable, // use getExternFunc() instead
.func => unreachable, // use getFuncInstance() or getFuncDecl() instead
.variable => |variable| {
const has_init = variable.init != .none;
if (has_init) assert(variable.ty == ip.typeOf(variable.init));
ip.items.appendAssumeCapacity(.{
.tag = .variable,
.data = try ip.addExtra(gpa, Tag.Variable{
.ty = variable.ty,
.init = variable.init,
.decl = variable.decl,
.lib_name = variable.lib_name,
.flags = .{
.is_extern = variable.is_extern,
.is_const = variable.is_const,
.is_threadlocal = variable.is_threadlocal,
.is_weak_linkage = variable.is_weak_linkage,
},
}),
});
},
.slice => |slice| {
assert(ip.indexToKey(slice.ty).ptr_type.flags.size == .Slice);
assert(ip.indexToKey(ip.typeOf(slice.ptr)).ptr_type.flags.size == .Many);
ip.items.appendAssumeCapacity(.{
.tag = .ptr_slice,
.data = try ip.addExtra(gpa, PtrSlice{
.ty = slice.ty,
.ptr = slice.ptr,
.len = slice.len,
}),
});
},
.ptr => |ptr| {
const ptr_type = ip.indexToKey(ptr.ty).ptr_type;
assert(ptr_type.flags.size != .Slice);
switch (ptr.addr) {
.decl => |decl| ip.items.appendAssumeCapacity(.{
.tag = .ptr_decl,
.data = try ip.addExtra(gpa, PtrDecl{
.ty = ptr.ty,
.decl = decl,
}),
}),
.comptime_alloc => |alloc_index| ip.items.appendAssumeCapacity(.{
.tag = .ptr_comptime_alloc,
.data = try ip.addExtra(gpa, PtrComptimeAlloc{
.ty = ptr.ty,
.index = alloc_index,
}),
}),
.anon_decl => |anon_decl| ip.items.appendAssumeCapacity(
if (ptrsHaveSameAlignment(ip, ptr.ty, ptr_type, anon_decl.orig_ty)) .{
.tag = .ptr_anon_decl,
.data = try ip.addExtra(gpa, PtrAnonDecl{
.ty = ptr.ty,
.val = anon_decl.val,
}),
} else .{
.tag = .ptr_anon_decl_aligned,
.data = try ip.addExtra(gpa, PtrAnonDeclAligned{
.ty = ptr.ty,
.val = anon_decl.val,
.orig_ty = anon_decl.orig_ty,
}),
},
),
.comptime_field => |field_val| {
assert(field_val != .none);
ip.items.appendAssumeCapacity(.{
.tag = .ptr_comptime_field,
.data = try ip.addExtra(gpa, PtrComptimeField{
.ty = ptr.ty,
.field_val = field_val,
}),
});
},
.int, .eu_payload, .opt_payload => |base| {
switch (ptr.addr) {
.int => assert(ip.typeOf(base) == .usize_type),
.eu_payload => assert(ip.indexToKey(
ip.indexToKey(ip.typeOf(base)).ptr_type.child,
) == .error_union_type),
.opt_payload => assert(ip.indexToKey(
ip.indexToKey(ip.typeOf(base)).ptr_type.child,
) == .opt_type),
else => unreachable,
}
ip.items.appendAssumeCapacity(.{
.tag = switch (ptr.addr) {
.int => .ptr_int,
.eu_payload => .ptr_eu_payload,
.opt_payload => .ptr_opt_payload,
else => unreachable,
},
.data = try ip.addExtra(gpa, PtrBase{
.ty = ptr.ty,
.base = base,
}),
});
},
.elem, .field => |base_index| {
const base_ptr_type = ip.indexToKey(ip.typeOf(base_index.base)).ptr_type;
switch (ptr.addr) {
.elem => assert(base_ptr_type.flags.size == .Many),
.field => {
assert(base_ptr_type.flags.size == .One);
switch (ip.indexToKey(base_ptr_type.child)) {
.anon_struct_type => |anon_struct_type| {
assert(ptr.addr == .field);
assert(base_index.index < anon_struct_type.types.len);
},
.struct_type => {
assert(ptr.addr == .field);
assert(base_index.index < ip.loadStructType(base_ptr_type.child).field_types.len);
},
.union_type => {
const union_type = ip.loadUnionType(base_ptr_type.child);
assert(ptr.addr == .field);
assert(base_index.index < union_type.field_types.len);
},
.ptr_type => |slice_type| {
assert(ptr.addr == .field);
assert(slice_type.flags.size == .Slice);
assert(base_index.index < 2);
},
else => unreachable,
}
},
else => unreachable,
}
_ = ip.map.pop();
const index_index = try ip.get(gpa, .{ .int = .{
.ty = .usize_type,
.storage = .{ .u64 = base_index.index },
} });
assert(!(try ip.map.getOrPutAdapted(gpa, key, adapter)).found_existing);
try ip.items.ensureUnusedCapacity(gpa, 1);
ip.items.appendAssumeCapacity(.{
.tag = switch (ptr.addr) {
.elem => .ptr_elem,
.field => .ptr_field,
else => unreachable,
},
.data = try ip.addExtra(gpa, PtrBaseIndex{
.ty = ptr.ty,
.base = base_index.base,
.index = index_index,
}),
});
},
}
},
.opt => |opt| {
assert(ip.isOptionalType(opt.ty));
assert(opt.val == .none or ip.indexToKey(opt.ty).opt_type == ip.typeOf(opt.val));
ip.items.appendAssumeCapacity(if (opt.val == .none) .{
.tag = .opt_null,
.data = @intFromEnum(opt.ty),
} else .{
.tag = .opt_payload,
.data = try ip.addExtra(gpa, Tag.TypeValue{
.ty = opt.ty,
.val = opt.val,
}),
});
},
.int => |int| b: {
assert(ip.isIntegerType(int.ty));
switch (int.storage) {
.u64, .i64, .big_int => {},
.lazy_align, .lazy_size => |lazy_ty| {
ip.items.appendAssumeCapacity(.{
.tag = switch (int.storage) {
else => unreachable,
.lazy_align => .int_lazy_align,
.lazy_size => .int_lazy_size,
},
.data = try ip.addExtra(gpa, IntLazy{
.ty = int.ty,
.lazy_ty = lazy_ty,
}),
});
return @enumFromInt(ip.items.len - 1);
},
}
switch (int.ty) {
.u8_type => switch (int.storage) {
.big_int => |big_int| {
ip.items.appendAssumeCapacity(.{
.tag = .int_u8,
.data = big_int.to(u8) catch unreachable,
});
break :b;
},
inline .u64, .i64 => |x| {
ip.items.appendAssumeCapacity(.{
.tag = .int_u8,
.data = @as(u8, @intCast(x)),
});
break :b;
},
.lazy_align, .lazy_size => unreachable,
},
.u16_type => switch (int.storage) {
.big_int => |big_int| {
ip.items.appendAssumeCapacity(.{
.tag = .int_u16,
.data = big_int.to(u16) catch unreachable,
});
break :b;
},
inline .u64, .i64 => |x| {
ip.items.appendAssumeCapacity(.{
.tag = .int_u16,
.data = @as(u16, @intCast(x)),
});
break :b;
},
.lazy_align, .lazy_size => unreachable,
},
.u32_type => switch (int.storage) {
.big_int => |big_int| {
ip.items.appendAssumeCapacity(.{
.tag = .int_u32,
.data = big_int.to(u32) catch unreachable,
});
break :b;
},
inline .u64, .i64 => |x| {
ip.items.appendAssumeCapacity(.{
.tag = .int_u32,
.data = @as(u32, @intCast(x)),
});
break :b;
},
.lazy_align, .lazy_size => unreachable,
},
.i32_type => switch (int.storage) {
.big_int => |big_int| {
const casted = big_int.to(i32) catch unreachable;
ip.items.appendAssumeCapacity(.{
.tag = .int_i32,
.data = @as(u32, @bitCast(casted)),
});
break :b;
},
inline .u64, .i64 => |x| {
ip.items.appendAssumeCapacity(.{
.tag = .int_i32,
.data = @as(u32, @bitCast(@as(i32, @intCast(x)))),
});
break :b;
},
.lazy_align, .lazy_size => unreachable,
},
.usize_type => switch (int.storage) {
.big_int => |big_int| {
if (big_int.to(u32)) |casted| {
ip.items.appendAssumeCapacity(.{
.tag = .int_usize,
.data = casted,
});
break :b;
} else |_| {}
},
inline .u64, .i64 => |x| {
if (std.math.cast(u32, x)) |casted| {
ip.items.appendAssumeCapacity(.{
.tag = .int_usize,
.data = casted,
});
break :b;
}
},
.lazy_align, .lazy_size => unreachable,
},
.comptime_int_type => switch (int.storage) {
.big_int => |big_int| {
if (big_int.to(u32)) |casted| {
ip.items.appendAssumeCapacity(.{
.tag = .int_comptime_int_u32,
.data = casted,
});
break :b;
} else |_| {}
if (big_int.to(i32)) |casted| {
ip.items.appendAssumeCapacity(.{
.tag = .int_comptime_int_i32,
.data = @as(u32, @bitCast(casted)),
});
break :b;
} else |_| {}
},
inline .u64, .i64 => |x| {
if (std.math.cast(u32, x)) |casted| {
ip.items.appendAssumeCapacity(.{
.tag = .int_comptime_int_u32,
.data = casted,
});
break :b;
}
if (std.math.cast(i32, x)) |casted| {
ip.items.appendAssumeCapacity(.{
.tag = .int_comptime_int_i32,
.data = @as(u32, @bitCast(casted)),
});
break :b;
}
},
.lazy_align, .lazy_size => unreachable,
},
else => {},
}
switch (int.storage) {
.big_int => |big_int| {
if (big_int.to(u32)) |casted| {
ip.items.appendAssumeCapacity(.{
.tag = .int_small,
.data = try ip.addExtra(gpa, IntSmall{
.ty = int.ty,
.value = casted,
}),
});
return @enumFromInt(ip.items.len - 1);
} else |_| {}
const tag: Tag = if (big_int.positive) .int_positive else .int_negative;
try addInt(ip, gpa, int.ty, tag, big_int.limbs);
},
inline .u64, .i64 => |x| {
if (std.math.cast(u32, x)) |casted| {
ip.items.appendAssumeCapacity(.{
.tag = .int_small,
.data = try ip.addExtra(gpa, IntSmall{
.ty = int.ty,
.value = casted,
}),
});
return @enumFromInt(ip.items.len - 1);
}
var buf: [2]Limb = undefined;
const big_int = BigIntMutable.init(&buf, x).toConst();
const tag: Tag = if (big_int.positive) .int_positive else .int_negative;
try addInt(ip, gpa, int.ty, tag, big_int.limbs);
},
.lazy_align, .lazy_size => unreachable,
}
},
.err => |err| {
assert(ip.isErrorSetType(err.ty));
ip.items.appendAssumeCapacity(.{
.tag = .error_set_error,
.data = try ip.addExtra(gpa, err),
});
},
.error_union => |error_union| {
assert(ip.isErrorUnionType(error_union.ty));
ip.items.appendAssumeCapacity(switch (error_union.val) {
.err_name => |err_name| .{
.tag = .error_union_error,
.data = try ip.addExtra(gpa, Key.Error{
.ty = error_union.ty,
.name = err_name,
}),
},
.payload => |payload| .{
.tag = .error_union_payload,
.data = try ip.addExtra(gpa, Tag.TypeValue{
.ty = error_union.ty,
.val = payload,
}),
},
});
},
.enum_literal => |enum_literal| ip.items.appendAssumeCapacity(.{
.tag = .enum_literal,
.data = @intFromEnum(enum_literal),
}),
.enum_tag => |enum_tag| {
assert(ip.isEnumType(enum_tag.ty));
switch (ip.indexToKey(enum_tag.ty)) {
.simple_type => assert(ip.isIntegerType(ip.typeOf(enum_tag.int))),
.enum_type => assert(ip.typeOf(enum_tag.int) == ip.loadEnumType(enum_tag.ty).tag_ty),
else => unreachable,
}
ip.items.appendAssumeCapacity(.{
.tag = .enum_tag,
.data = try ip.addExtra(gpa, enum_tag),
});
},
.empty_enum_value => |enum_or_union_ty| ip.items.appendAssumeCapacity(.{
.tag = .only_possible_value,
.data = @intFromEnum(enum_or_union_ty),
}),
.float => |float| {
switch (float.ty) {
.f16_type => ip.items.appendAssumeCapacity(.{
.tag = .float_f16,
.data = @as(u16, @bitCast(float.storage.f16)),
}),
.f32_type => ip.items.appendAssumeCapacity(.{
.tag = .float_f32,
.data = @as(u32, @bitCast(float.storage.f32)),
}),
.f64_type => ip.items.appendAssumeCapacity(.{
.tag = .float_f64,
.data = try ip.addExtra(gpa, Float64.pack(float.storage.f64)),
}),
.f80_type => ip.items.appendAssumeCapacity(.{
.tag = .float_f80,
.data = try ip.addExtra(gpa, Float80.pack(float.storage.f80)),
}),
.f128_type => ip.items.appendAssumeCapacity(.{
.tag = .float_f128,
.data = try ip.addExtra(gpa, Float128.pack(float.storage.f128)),
}),
.c_longdouble_type => switch (float.storage) {
.f80 => |x| ip.items.appendAssumeCapacity(.{
.tag = .float_c_longdouble_f80,
.data = try ip.addExtra(gpa, Float80.pack(x)),
}),
inline .f16, .f32, .f64, .f128 => |x| ip.items.appendAssumeCapacity(.{
.tag = .float_c_longdouble_f128,
.data = try ip.addExtra(gpa, Float128.pack(x)),
}),
},
.comptime_float_type => ip.items.appendAssumeCapacity(.{
.tag = .float_comptime_float,
.data = try ip.addExtra(gpa, Float128.pack(float.storage.f128)),
}),
else => unreachable,
}
},
.aggregate => |aggregate| {
const ty_key = ip.indexToKey(aggregate.ty);
const len = ip.aggregateTypeLen(aggregate.ty);
const child = switch (ty_key) {
.array_type => |array_type| array_type.child,
.vector_type => |vector_type| vector_type.child,
.anon_struct_type, .struct_type => .none,
else => unreachable,
};
const sentinel = switch (ty_key) {
.array_type => |array_type| array_type.sentinel,
.vector_type, .anon_struct_type, .struct_type => .none,
else => unreachable,
};
const len_including_sentinel = len + @intFromBool(sentinel != .none);
switch (aggregate.storage) {
.bytes => |bytes| {
assert(child == .u8_type);
if (bytes.len != len) {
assert(bytes.len == len_including_sentinel);
assert(bytes[@intCast(len)] == ip.indexToKey(sentinel).int.storage.u64);
}
},
.elems => |elems| {
if (elems.len != len) {
assert(elems.len == len_including_sentinel);
assert(elems[@intCast(len)] == sentinel);
}
},
.repeated_elem => |elem| {
assert(sentinel == .none or elem == sentinel);
},
}
switch (ty_key) {
.array_type, .vector_type => {
for (aggregate.storage.values()) |elem| {
assert(ip.typeOf(elem) == child);
}
},
.struct_type => {
for (aggregate.storage.values(), ip.loadStructType(aggregate.ty).field_types.get(ip)) |elem, field_ty| {
assert(ip.typeOf(elem) == field_ty);
}
},
.anon_struct_type => |anon_struct_type| {
for (aggregate.storage.values(), anon_struct_type.types.get(ip)) |elem, ty| {
assert(ip.typeOf(elem) == ty);
}
},
else => unreachable,
}
if (len == 0) {
ip.items.appendAssumeCapacity(.{
.tag = .only_possible_value,
.data = @intFromEnum(aggregate.ty),
});
return @enumFromInt(ip.items.len - 1);
}
switch (ty_key) {
.anon_struct_type => |anon_struct_type| opv: {
switch (aggregate.storage) {
.bytes => |bytes| for (anon_struct_type.values.get(ip), bytes) |value, byte| {
if (value != ip.getIfExists(.{ .int = .{
.ty = .u8_type,
.storage = .{ .u64 = byte },
} })) break :opv;
},
.elems => |elems| if (!std.mem.eql(
Index,
anon_struct_type.values.get(ip),
elems,
)) break :opv,
.repeated_elem => |elem| for (anon_struct_type.values.get(ip)) |value| {
if (value != elem) break :opv;
},
}
// This encoding works thanks to the fact that, as we just verified,
// the type itself contains a slice of values that can be provided
// in the aggregate fields.
ip.items.appendAssumeCapacity(.{
.tag = .only_possible_value,
.data = @intFromEnum(aggregate.ty),
});
return @enumFromInt(ip.items.len - 1);
},
else => {},
}
repeated: {
switch (aggregate.storage) {
.bytes => |bytes| for (bytes[1..@as(usize, @intCast(len))]) |byte|
if (byte != bytes[0]) break :repeated,
.elems => |elems| for (elems[1..@as(usize, @intCast(len))]) |elem|
if (elem != elems[0]) break :repeated,
.repeated_elem => {},
}
const elem = switch (aggregate.storage) {
.bytes => |bytes| elem: {
_ = ip.map.pop();
const elem = try ip.get(gpa, .{ .int = .{
.ty = .u8_type,
.storage = .{ .u64 = bytes[0] },
} });
assert(!(try ip.map.getOrPutAdapted(gpa, key, adapter)).found_existing);
try ip.items.ensureUnusedCapacity(gpa, 1);
break :elem elem;
},
.elems => |elems| elems[0],
.repeated_elem => |elem| elem,
};
try ip.extra.ensureUnusedCapacity(
gpa,
@typeInfo(Repeated).Struct.fields.len,
);
ip.items.appendAssumeCapacity(.{
.tag = .repeated,
.data = ip.addExtraAssumeCapacity(Repeated{
.ty = aggregate.ty,
.elem_val = elem,
}),
});
return @enumFromInt(ip.items.len - 1);
}
if (child == .u8_type) bytes: {
const string_bytes_index = ip.string_bytes.items.len;
try ip.string_bytes.ensureUnusedCapacity(gpa, @intCast(len_including_sentinel + 1));
try ip.extra.ensureUnusedCapacity(gpa, @typeInfo(Bytes).Struct.fields.len);
switch (aggregate.storage) {
.bytes => |bytes| ip.string_bytes.appendSliceAssumeCapacity(bytes[0..@intCast(len)]),
.elems => |elems| for (elems[0..@intCast(len)]) |elem| switch (ip.indexToKey(elem)) {
.undef => {
ip.string_bytes.shrinkRetainingCapacity(string_bytes_index);
break :bytes;
},
.int => |int| ip.string_bytes.appendAssumeCapacity(
@intCast(int.storage.u64),
),
else => unreachable,
},
.repeated_elem => |elem| switch (ip.indexToKey(elem)) {
.undef => break :bytes,
.int => |int| @memset(
ip.string_bytes.addManyAsSliceAssumeCapacity(@intCast(len)),
@intCast(int.storage.u64),
),
else => unreachable,
},
}
const has_internal_null =
std.mem.indexOfScalar(u8, ip.string_bytes.items[string_bytes_index..], 0) != null;
if (sentinel != .none) ip.string_bytes.appendAssumeCapacity(
@intCast(ip.indexToKey(sentinel).int.storage.u64),
);
const string: String = if (has_internal_null)
@enumFromInt(string_bytes_index)
else
(try ip.getOrPutTrailingString(gpa, @intCast(len_including_sentinel))).toString();
ip.items.appendAssumeCapacity(.{
.tag = .bytes,
.data = ip.addExtraAssumeCapacity(Bytes{
.ty = aggregate.ty,
.bytes = string,
}),
});
return @enumFromInt(ip.items.len - 1);
}
try ip.extra.ensureUnusedCapacity(
gpa,
@typeInfo(Tag.Aggregate).Struct.fields.len + @as(usize, @intCast(len_including_sentinel + 1)),
);
ip.items.appendAssumeCapacity(.{
.tag = .aggregate,
.data = ip.addExtraAssumeCapacity(Tag.Aggregate{
.ty = aggregate.ty,
}),
});
ip.extra.appendSliceAssumeCapacity(@ptrCast(aggregate.storage.elems));
if (sentinel != .none) ip.extra.appendAssumeCapacity(@intFromEnum(sentinel));
},
.un => |un| {
assert(un.ty != .none);
assert(un.val != .none);
ip.items.appendAssumeCapacity(.{
.tag = .union_value,
.data = try ip.addExtra(gpa, un),
});
},
.memoized_call => |memoized_call| {
for (memoized_call.arg_values) |arg| assert(arg != .none);
try ip.extra.ensureUnusedCapacity(gpa, @typeInfo(MemoizedCall).Struct.fields.len +
memoized_call.arg_values.len);
ip.items.appendAssumeCapacity(.{
.tag = .memoized_call,
.data = ip.addExtraAssumeCapacity(MemoizedCall{
.func = memoized_call.func,
.args_len = @as(u32, @intCast(memoized_call.arg_values.len)),
.result = memoized_call.result,
}),
});
ip.extra.appendSliceAssumeCapacity(@ptrCast(memoized_call.arg_values));
},
}
return @enumFromInt(ip.items.len - 1);
}
pub const UnionTypeInit = struct {
flags: packed struct {
runtime_tag: LoadedUnionType.RuntimeTag,
any_aligned_fields: bool,
layout: std.builtin.Type.ContainerLayout,
status: LoadedUnionType.Status,
requires_comptime: RequiresComptime,
assumed_runtime_bits: bool,
assumed_pointer_aligned: bool,
alignment: Alignment,
},
has_namespace: bool,
fields_len: u32,
enum_tag_ty: Index,
/// May have length 0 which leaves the values unset until later.
field_types: []const Index,
/// May have length 0 which leaves the values unset until later.
/// The logic for `any_aligned_fields` is asserted to have been done before
/// calling this function.
field_aligns: []const Alignment,
key: union(enum) {
declared: struct {
zir_index: TrackedInst.Index,
captures: []const CaptureValue,
},
reified: struct {
zir_index: TrackedInst.Index,
type_hash: u64,
},
},
};
pub fn getUnionType(ip: *InternPool, gpa: Allocator, ini: UnionTypeInit) Allocator.Error!WipNamespaceType.Result {
const adapter: KeyAdapter = .{ .intern_pool = ip };
const gop = try ip.map.getOrPutAdapted(gpa, Key{ .union_type = switch (ini.key) {
.declared => |d| .{ .declared = .{
.zir_index = d.zir_index,
.captures = .{ .external = d.captures },
} },
.reified => |r| .{ .reified = .{
.zir_index = r.zir_index,
.type_hash = r.type_hash,
} },
} }, adapter);
if (gop.found_existing) return .{ .existing = @enumFromInt(gop.index) };
errdefer _ = ip.map.pop();
const align_elements_len = if (ini.flags.any_aligned_fields) (ini.fields_len + 3) / 4 else 0;
const align_element: u32 = @bitCast([1]u8{@intFromEnum(Alignment.none)} ** 4);
try ip.extra.ensureUnusedCapacity(gpa, @typeInfo(Tag.TypeUnion).Struct.fields.len +
// TODO: fmt bug
// zig fmt: off
switch (ini.key) {
.declared => |d| @intFromBool(d.captures.len != 0) + d.captures.len,
.reified => 2, // type_hash: PackedU64
} +
// zig fmt: on
ini.fields_len + // field types
align_elements_len);
try ip.items.ensureUnusedCapacity(gpa, 1);
const extra_index = ip.addExtraAssumeCapacity(Tag.TypeUnion{
.flags = .{
.any_captures = ini.key == .declared and ini.key.declared.captures.len != 0,
.runtime_tag = ini.flags.runtime_tag,
.any_aligned_fields = ini.flags.any_aligned_fields,
.layout = ini.flags.layout,
.status = ini.flags.status,
.requires_comptime = ini.flags.requires_comptime,
.assumed_runtime_bits = ini.flags.assumed_runtime_bits,
.assumed_pointer_aligned = ini.flags.assumed_pointer_aligned,
.alignment = ini.flags.alignment,
.is_reified = ini.key == .reified,
},
.fields_len = ini.fields_len,
.size = std.math.maxInt(u32),
.padding = std.math.maxInt(u32),
.decl = undefined, // set by `finish`
.namespace = .none, // set by `finish`
.tag_ty = ini.enum_tag_ty,
.zir_index = switch (ini.key) {
inline else => |x| x.zir_index,
},
});
ip.items.appendAssumeCapacity(.{
.tag = .type_union,
.data = extra_index,
});
switch (ini.key) {
.declared => |d| if (d.captures.len != 0) {
ip.extra.appendAssumeCapacity(@intCast(d.captures.len));
ip.extra.appendSliceAssumeCapacity(@ptrCast(d.captures));
},
.reified => |r| _ = ip.addExtraAssumeCapacity(PackedU64.init(r.type_hash)),
}
// field types
if (ini.field_types.len > 0) {
assert(ini.field_types.len == ini.fields_len);
ip.extra.appendSliceAssumeCapacity(@ptrCast(ini.field_types));
} else {
ip.extra.appendNTimesAssumeCapacity(@intFromEnum(Index.none), ini.fields_len);
}
// field alignments
if (ini.flags.any_aligned_fields) {
ip.extra.appendNTimesAssumeCapacity(align_element, align_elements_len);
if (ini.field_aligns.len > 0) {
assert(ini.field_aligns.len == ini.fields_len);
@memcpy((Alignment.Slice{
.start = @intCast(ip.extra.items.len - align_elements_len),
.len = @intCast(ini.field_aligns.len),
}).get(ip), ini.field_aligns);
}
} else {
assert(ini.field_aligns.len == 0);
}
return .{ .wip = .{
.index = @enumFromInt(ip.items.len - 1),
.decl_extra_index = extra_index + std.meta.fieldIndex(Tag.TypeUnion, "decl").?,
.namespace_extra_index = if (ini.has_namespace)
extra_index + std.meta.fieldIndex(Tag.TypeUnion, "namespace").?
else
null,
} };
}
pub const WipNamespaceType = struct {
index: Index,
decl_extra_index: u32,
namespace_extra_index: ?u32,
pub fn finish(wip: WipNamespaceType, ip: *InternPool, decl: DeclIndex, namespace: OptionalNamespaceIndex) Index {
ip.extra.items[wip.decl_extra_index] = @intFromEnum(decl);
if (wip.namespace_extra_index) |i| {
ip.extra.items[i] = @intFromEnum(namespace.unwrap().?);
} else {
assert(namespace == .none);
}
return wip.index;
}
pub fn cancel(wip: WipNamespaceType, ip: *InternPool) void {
ip.remove(wip.index);
}
pub const Result = union(enum) {
wip: WipNamespaceType,
existing: Index,
};
};
pub const StructTypeInit = struct {
layout: std.builtin.Type.ContainerLayout,
fields_len: u32,
known_non_opv: bool,
requires_comptime: RequiresComptime,
is_tuple: bool,
any_comptime_fields: bool,
any_default_inits: bool,
inits_resolved: bool,
any_aligned_fields: bool,
has_namespace: bool,
key: union(enum) {
declared: struct {
zir_index: TrackedInst.Index,
captures: []const CaptureValue,
},
reified: struct {
zir_index: TrackedInst.Index,
type_hash: u64,
},
},
};
pub fn getStructType(
ip: *InternPool,
gpa: Allocator,
ini: StructTypeInit,
) Allocator.Error!WipNamespaceType.Result {
const adapter: KeyAdapter = .{ .intern_pool = ip };
const key: Key = .{ .struct_type = switch (ini.key) {
.declared => |d| .{ .declared = .{
.zir_index = d.zir_index,
.captures = .{ .external = d.captures },
} },
.reified => |r| .{ .reified = .{
.zir_index = r.zir_index,
.type_hash = r.type_hash,
} },
} };
const gop = try ip.map.getOrPutAdapted(gpa, key, adapter);
if (gop.found_existing) return .{ .existing = @enumFromInt(gop.index) };
errdefer _ = ip.map.pop();
const names_map = try ip.addMap(gpa, ini.fields_len);
errdefer _ = ip.maps.pop();
const zir_index = switch (ini.key) {
inline else => |x| x.zir_index,
};
const is_extern = switch (ini.layout) {
.auto => false,
.@"extern" => true,
.@"packed" => {
try ip.extra.ensureUnusedCapacity(gpa, @typeInfo(Tag.TypeStructPacked).Struct.fields.len +
// TODO: fmt bug
// zig fmt: off
switch (ini.key) {
.declared => |d| @intFromBool(d.captures.len != 0) + d.captures.len,
.reified => 2, // type_hash: PackedU64
} +
// zig fmt: on
ini.fields_len + // types
ini.fields_len + // names
ini.fields_len); // inits
const extra_index = ip.addExtraAssumeCapacity(Tag.TypeStructPacked{
.decl = undefined, // set by `finish`
.zir_index = zir_index,
.fields_len = ini.fields_len,
.namespace = .none,
.backing_int_ty = .none,
.names_map = names_map,
.flags = .{
.any_captures = ini.key == .declared and ini.key.declared.captures.len != 0,
.field_inits_wip = false,
.inits_resolved = ini.inits_resolved,
.is_reified = ini.key == .reified,
},
});
try ip.items.append(gpa, .{
.tag = if (ini.any_default_inits) .type_struct_packed_inits else .type_struct_packed,
.data = extra_index,
});
switch (ini.key) {
.declared => |d| if (d.captures.len != 0) {
ip.extra.appendAssumeCapacity(@intCast(d.captures.len));
ip.extra.appendSliceAssumeCapacity(@ptrCast(d.captures));
},
.reified => |r| {
_ = ip.addExtraAssumeCapacity(PackedU64.init(r.type_hash));
},
}
ip.extra.appendNTimesAssumeCapacity(@intFromEnum(Index.none), ini.fields_len);
ip.extra.appendNTimesAssumeCapacity(@intFromEnum(OptionalNullTerminatedString.none), ini.fields_len);
if (ini.any_default_inits) {
ip.extra.appendNTimesAssumeCapacity(@intFromEnum(Index.none), ini.fields_len);
}
return .{ .wip = .{
.index = @enumFromInt(ip.items.len - 1),
.decl_extra_index = extra_index + std.meta.fieldIndex(Tag.TypeStructPacked, "decl").?,
.namespace_extra_index = if (ini.has_namespace)
extra_index + std.meta.fieldIndex(Tag.TypeStructPacked, "namespace").?
else
null,
} };
},
};
const align_elements_len = if (ini.any_aligned_fields) (ini.fields_len + 3) / 4 else 0;
const align_element: u32 = @bitCast([1]u8{@intFromEnum(Alignment.none)} ** 4);
const comptime_elements_len = if (ini.any_comptime_fields) (ini.fields_len + 31) / 32 else 0;
try ip.extra.ensureUnusedCapacity(gpa, @typeInfo(Tag.TypeStruct).Struct.fields.len +
// TODO: fmt bug
// zig fmt: off
switch (ini.key) {
.declared => |d| @intFromBool(d.captures.len != 0) + d.captures.len,
.reified => 2, // type_hash: PackedU64
} +
// zig fmt: on
(ini.fields_len * 5) + // types, names, inits, runtime order, offsets
align_elements_len + comptime_elements_len +
2); // names_map + namespace
const extra_index = ip.addExtraAssumeCapacity(Tag.TypeStruct{
.decl = undefined, // set by `finish`
.zir_index = zir_index,
.fields_len = ini.fields_len,
.size = std.math.maxInt(u32),
.flags = .{
.any_captures = ini.key == .declared and ini.key.declared.captures.len != 0,
.is_extern = is_extern,
.known_non_opv = ini.known_non_opv,
.requires_comptime = ini.requires_comptime,
.is_tuple = ini.is_tuple,
.assumed_runtime_bits = false,
.assumed_pointer_aligned = false,
.has_namespace = ini.has_namespace,
.any_comptime_fields = ini.any_comptime_fields,
.any_default_inits = ini.any_default_inits,
.any_aligned_fields = ini.any_aligned_fields,
.alignment = .none,
.alignment_wip = false,
.field_types_wip = false,
.layout_wip = false,
.layout_resolved = false,
.field_inits_wip = false,
.inits_resolved = ini.inits_resolved,
.fully_resolved = false,
.is_reified = ini.key == .reified,
},
});
try ip.items.append(gpa, .{
.tag = .type_struct,
.data = extra_index,
});
switch (ini.key) {
.declared => |d| if (d.captures.len != 0) {
ip.extra.appendAssumeCapacity(@intCast(d.captures.len));
ip.extra.appendSliceAssumeCapacity(@ptrCast(d.captures));
},
.reified => |r| {
_ = ip.addExtraAssumeCapacity(PackedU64.init(r.type_hash));
},
}
ip.extra.appendNTimesAssumeCapacity(@intFromEnum(Index.none), ini.fields_len);
if (!ini.is_tuple) {
ip.extra.appendAssumeCapacity(@intFromEnum(names_map));
ip.extra.appendNTimesAssumeCapacity(@intFromEnum(OptionalNullTerminatedString.none), ini.fields_len);
}
if (ini.any_default_inits) {
ip.extra.appendNTimesAssumeCapacity(@intFromEnum(Index.none), ini.fields_len);
}
const namespace_extra_index: ?u32 = if (ini.has_namespace) i: {
ip.extra.appendAssumeCapacity(undefined); // set by `finish`
break :i @intCast(ip.extra.items.len - 1);
} else null;
if (ini.any_aligned_fields) {
ip.extra.appendNTimesAssumeCapacity(align_element, align_elements_len);
}
if (ini.any_comptime_fields) {
ip.extra.appendNTimesAssumeCapacity(0, comptime_elements_len);
}
if (ini.layout == .auto) {
ip.extra.appendNTimesAssumeCapacity(@intFromEnum(LoadedStructType.RuntimeOrder.unresolved), ini.fields_len);
}
ip.extra.appendNTimesAssumeCapacity(std.math.maxInt(u32), ini.fields_len);
return .{ .wip = .{
.index = @enumFromInt(ip.items.len - 1),
.decl_extra_index = extra_index + std.meta.fieldIndex(Tag.TypeStruct, "decl").?,
.namespace_extra_index = namespace_extra_index,
} };
}
pub const AnonStructTypeInit = struct {
types: []const Index,
/// This may be empty, indicating this is a tuple.
names: []const NullTerminatedString,
/// These elements may be `none`, indicating runtime-known.
values: []const Index,
};
pub fn getAnonStructType(ip: *InternPool, gpa: Allocator, ini: AnonStructTypeInit) Allocator.Error!Index {
assert(ini.types.len == ini.values.len);
for (ini.types) |elem| assert(elem != .none);
const prev_extra_len = ip.extra.items.len;
const fields_len: u32 = @intCast(ini.types.len);
try ip.extra.ensureUnusedCapacity(
gpa,
@typeInfo(TypeStructAnon).Struct.fields.len + (fields_len * 3),
);
try ip.items.ensureUnusedCapacity(gpa, 1);
const extra_index = ip.addExtraAssumeCapacity(TypeStructAnon{
.fields_len = fields_len,
});
ip.extra.appendSliceAssumeCapacity(@ptrCast(ini.types));
ip.extra.appendSliceAssumeCapacity(@ptrCast(ini.values));
const adapter: KeyAdapter = .{ .intern_pool = ip };
const key: Key = .{
.anon_struct_type = if (ini.names.len == 0) extraTypeTupleAnon(ip, extra_index) else k: {
assert(ini.names.len == ini.types.len);
ip.extra.appendSliceAssumeCapacity(@ptrCast(ini.names));
break :k extraTypeStructAnon(ip, extra_index);
},
};
const gop = try ip.map.getOrPutAdapted(gpa, key, adapter);
if (gop.found_existing) {
ip.extra.items.len = prev_extra_len;
return @enumFromInt(gop.index);
}
ip.items.appendAssumeCapacity(.{
.tag = if (ini.names.len == 0) .type_tuple_anon else .type_struct_anon,
.data = extra_index,
});
return @enumFromInt(ip.items.len - 1);
}
/// This is equivalent to `Key.FuncType` but adjusted to have a slice for `param_types`.
pub const GetFuncTypeKey = struct {
param_types: []const Index,
return_type: Index,
comptime_bits: u32 = 0,
noalias_bits: u32 = 0,
/// `null` means generic.
cc: ?std.builtin.CallingConvention = .Unspecified,
is_var_args: bool = false,
is_generic: bool = false,
is_noinline: bool = false,
section_is_generic: bool = false,
addrspace_is_generic: bool = false,
};
pub fn getFuncType(ip: *InternPool, gpa: Allocator, key: GetFuncTypeKey) Allocator.Error!Index {
// Validate input parameters.
assert(key.return_type != .none);
for (key.param_types) |param_type| assert(param_type != .none);
// The strategy here is to add the function type unconditionally, then to
// ask if it already exists, and if so, revert the lengths of the mutated
// arrays. This is similar to what `getOrPutTrailingString` does.
const prev_extra_len = ip.extra.items.len;
const params_len: u32 = @intCast(key.param_types.len);
try ip.extra.ensureUnusedCapacity(gpa, @typeInfo(Tag.TypeFunction).Struct.fields.len +
@intFromBool(key.comptime_bits != 0) +
@intFromBool(key.noalias_bits != 0) +
params_len);
try ip.items.ensureUnusedCapacity(gpa, 1);
const func_type_extra_index = ip.addExtraAssumeCapacity(Tag.TypeFunction{
.params_len = params_len,
.return_type = key.return_type,
.flags = .{
.cc = key.cc orelse .Unspecified,
.is_var_args = key.is_var_args,
.has_comptime_bits = key.comptime_bits != 0,
.has_noalias_bits = key.noalias_bits != 0,
.is_generic = key.is_generic,
.is_noinline = key.is_noinline,
.cc_is_generic = key.cc == null,
.section_is_generic = key.section_is_generic,
.addrspace_is_generic = key.addrspace_is_generic,
},
});
if (key.comptime_bits != 0) ip.extra.appendAssumeCapacity(key.comptime_bits);
if (key.noalias_bits != 0) ip.extra.appendAssumeCapacity(key.noalias_bits);
ip.extra.appendSliceAssumeCapacity(@ptrCast(key.param_types));
const adapter: KeyAdapter = .{ .intern_pool = ip };
const gop = try ip.map.getOrPutAdapted(gpa, Key{
.func_type = extraFuncType(ip, func_type_extra_index),
}, adapter);
if (gop.found_existing) {
ip.extra.items.len = prev_extra_len;
return @enumFromInt(gop.index);
}
ip.items.appendAssumeCapacity(.{
.tag = .type_function,
.data = func_type_extra_index,
});
return @enumFromInt(ip.items.len - 1);
}
pub fn getExternFunc(ip: *InternPool, gpa: Allocator, key: Key.ExternFunc) Allocator.Error!Index {
const adapter: KeyAdapter = .{ .intern_pool = ip };
const gop = try ip.map.getOrPutAdapted(gpa, Key{ .extern_func = key }, adapter);
if (gop.found_existing) return @enumFromInt(gop.index);
errdefer _ = ip.map.pop();
const prev_extra_len = ip.extra.items.len;
const extra_index = try ip.addExtra(gpa, @as(Tag.ExternFunc, key));
errdefer ip.extra.items.len = prev_extra_len;
try ip.items.append(gpa, .{
.tag = .extern_func,
.data = extra_index,
});
errdefer ip.items.len -= 1;
return @enumFromInt(ip.items.len - 1);
}
pub const GetFuncDeclKey = struct {
owner_decl: DeclIndex,
ty: Index,
zir_body_inst: TrackedInst.Index,
lbrace_line: u32,
rbrace_line: u32,
lbrace_column: u32,
rbrace_column: u32,
cc: ?std.builtin.CallingConvention,
is_noinline: bool,
};
pub fn getFuncDecl(ip: *InternPool, gpa: Allocator, key: GetFuncDeclKey) Allocator.Error!Index {
// The strategy here is to add the function type unconditionally, then to
// ask if it already exists, and if so, revert the lengths of the mutated
// arrays. This is similar to what `getOrPutTrailingString` does.
const prev_extra_len = ip.extra.items.len;
try ip.extra.ensureUnusedCapacity(gpa, @typeInfo(Tag.FuncDecl).Struct.fields.len);
try ip.items.ensureUnusedCapacity(gpa, 1);
try ip.map.ensureUnusedCapacity(gpa, 1);
const func_decl_extra_index = ip.addExtraAssumeCapacity(Tag.FuncDecl{
.analysis = .{
.state = if (key.cc == .Inline) .inline_only else .none,
.is_cold = false,
.is_noinline = key.is_noinline,
.calls_or_awaits_errorable_fn = false,
.stack_alignment = .none,
.inferred_error_set = false,
},
.owner_decl = key.owner_decl,
.ty = key.ty,
.zir_body_inst = key.zir_body_inst,
.lbrace_line = key.lbrace_line,
.rbrace_line = key.rbrace_line,
.lbrace_column = key.lbrace_column,
.rbrace_column = key.rbrace_column,
});
const adapter: KeyAdapter = .{ .intern_pool = ip };
const gop = ip.map.getOrPutAssumeCapacityAdapted(Key{
.func = extraFuncDecl(ip, func_decl_extra_index),
}, adapter);
if (gop.found_existing) {
ip.extra.items.len = prev_extra_len;
return @enumFromInt(gop.index);
}
ip.items.appendAssumeCapacity(.{
.tag = .func_decl,
.data = func_decl_extra_index,
});
return @enumFromInt(ip.items.len - 1);
}
pub const GetFuncDeclIesKey = struct {
owner_decl: DeclIndex,
param_types: []Index,
noalias_bits: u32,
comptime_bits: u32,
bare_return_type: Index,
/// null means generic.
cc: ?std.builtin.CallingConvention,
/// null means generic.
alignment: ?Alignment,
section_is_generic: bool,
addrspace_is_generic: bool,
is_var_args: bool,
is_generic: bool,
is_noinline: bool,
zir_body_inst: TrackedInst.Index,
lbrace_line: u32,
rbrace_line: u32,
lbrace_column: u32,
rbrace_column: u32,
};
pub fn getFuncDeclIes(ip: *InternPool, gpa: Allocator, key: GetFuncDeclIesKey) Allocator.Error!Index {
// Validate input parameters.
assert(key.bare_return_type != .none);
for (key.param_types) |param_type| assert(param_type != .none);
// The strategy here is to add the function decl unconditionally, then to
// ask if it already exists, and if so, revert the lengths of the mutated
// arrays. This is similar to what `getOrPutTrailingString` does.
const prev_extra_len = ip.extra.items.len;
const params_len: u32 = @intCast(key.param_types.len);
try ip.map.ensureUnusedCapacity(gpa, 4);
try ip.extra.ensureUnusedCapacity(gpa, @typeInfo(Tag.FuncDecl).Struct.fields.len +
1 + // inferred_error_set
@typeInfo(Tag.ErrorUnionType).Struct.fields.len +
@typeInfo(Tag.TypeFunction).Struct.fields.len +
@intFromBool(key.comptime_bits != 0) +
@intFromBool(key.noalias_bits != 0) +
params_len);
try ip.items.ensureUnusedCapacity(gpa, 4);
const func_decl_extra_index = ip.addExtraAssumeCapacity(Tag.FuncDecl{
.analysis = .{
.state = if (key.cc == .Inline) .inline_only else .none,
.is_cold = false,
.is_noinline = key.is_noinline,
.calls_or_awaits_errorable_fn = false,
.stack_alignment = .none,
.inferred_error_set = true,
},
.owner_decl = key.owner_decl,
.ty = @enumFromInt(ip.items.len + 3),
.zir_body_inst = key.zir_body_inst,
.lbrace_line = key.lbrace_line,
.rbrace_line = key.rbrace_line,
.lbrace_column = key.lbrace_column,
.rbrace_column = key.rbrace_column,
});
ip.items.appendAssumeCapacity(.{
.tag = .func_decl,
.data = func_decl_extra_index,
});
ip.extra.appendAssumeCapacity(@intFromEnum(Index.none));
ip.items.appendAssumeCapacity(.{
.tag = .type_error_union,
.data = ip.addExtraAssumeCapacity(Tag.ErrorUnionType{
.error_set_type = @enumFromInt(ip.items.len + 1),
.payload_type = key.bare_return_type,
}),
});
ip.items.appendAssumeCapacity(.{
.tag = .type_inferred_error_set,
.data = @intCast(ip.items.len - 2),
});
const func_type_extra_index = ip.addExtraAssumeCapacity(Tag.TypeFunction{
.params_len = params_len,
.return_type = @enumFromInt(ip.items.len - 2),
.flags = .{
.cc = key.cc orelse .Unspecified,
.is_var_args = key.is_var_args,
.has_comptime_bits = key.comptime_bits != 0,
.has_noalias_bits = key.noalias_bits != 0,
.is_generic = key.is_generic,
.is_noinline = key.is_noinline,
.cc_is_generic = key.cc == null,
.section_is_generic = key.section_is_generic,
.addrspace_is_generic = key.addrspace_is_generic,
},
});
if (key.comptime_bits != 0) ip.extra.appendAssumeCapacity(key.comptime_bits);
if (key.noalias_bits != 0) ip.extra.appendAssumeCapacity(key.noalias_bits);
ip.extra.appendSliceAssumeCapacity(@ptrCast(key.param_types));
ip.items.appendAssumeCapacity(.{
.tag = .type_function,
.data = func_type_extra_index,
});
const adapter: KeyAdapter = .{ .intern_pool = ip };
const gop = ip.map.getOrPutAssumeCapacityAdapted(Key{
.func = extraFuncDecl(ip, func_decl_extra_index),
}, adapter);
if (!gop.found_existing) {
assert(!ip.map.getOrPutAssumeCapacityAdapted(Key{ .error_union_type = .{
.error_set_type = @enumFromInt(ip.items.len - 2),
.payload_type = key.bare_return_type,
} }, adapter).found_existing);
assert(!ip.map.getOrPutAssumeCapacityAdapted(Key{
.inferred_error_set_type = @enumFromInt(ip.items.len - 4),
}, adapter).found_existing);
assert(!ip.map.getOrPutAssumeCapacityAdapted(Key{
.func_type = extraFuncType(ip, func_type_extra_index),
}, adapter).found_existing);
return @enumFromInt(ip.items.len - 4);
}
// An existing function type was found; undo the additions to our two arrays.
ip.items.len -= 4;
ip.extra.items.len = prev_extra_len;
return @enumFromInt(gop.index);
}
pub fn getErrorSetType(
ip: *InternPool,
gpa: Allocator,
names: []const NullTerminatedString,
) Allocator.Error!Index {
assert(std.sort.isSorted(NullTerminatedString, names, {}, NullTerminatedString.indexLessThan));
// The strategy here is to add the type unconditionally, then to ask if it
// already exists, and if so, revert the lengths of the mutated arrays.
// This is similar to what `getOrPutTrailingString` does.
try ip.extra.ensureUnusedCapacity(gpa, @typeInfo(Tag.ErrorSet).Struct.fields.len + names.len);
const prev_extra_len = ip.extra.items.len;
errdefer ip.extra.items.len = prev_extra_len;
const predicted_names_map: MapIndex = @enumFromInt(ip.maps.items.len);
const error_set_extra_index = ip.addExtraAssumeCapacity(Tag.ErrorSet{
.names_len = @intCast(names.len),
.names_map = predicted_names_map,
});
ip.extra.appendSliceAssumeCapacity(@ptrCast(names));
const adapter: KeyAdapter = .{ .intern_pool = ip };
const gop = try ip.map.getOrPutAdapted(gpa, Key{
.error_set_type = extraErrorSet(ip, error_set_extra_index),
}, adapter);
errdefer _ = ip.map.pop();
if (gop.found_existing) {
ip.extra.items.len = prev_extra_len;
return @enumFromInt(gop.index);
}
try ip.items.append(gpa, .{
.tag = .type_error_set,
.data = error_set_extra_index,
});
errdefer ip.items.len -= 1;
const names_map = try ip.addMap(gpa, names.len);
assert(names_map == predicted_names_map);
errdefer _ = ip.maps.pop();
addStringsToMap(ip, names_map, names);
return @enumFromInt(ip.items.len - 1);
}
pub const GetFuncInstanceKey = struct {
/// Has the length of the instance function (may be lesser than
/// comptime_args).
param_types: []Index,
/// Has the length of generic_owner's parameters (may be greater than
/// param_types).
comptime_args: []const Index,
noalias_bits: u32,
bare_return_type: Index,
cc: std.builtin.CallingConvention,
alignment: Alignment,
section: OptionalNullTerminatedString,
is_noinline: bool,
generic_owner: Index,
inferred_error_set: bool,
};
pub fn getFuncInstance(ip: *InternPool, gpa: Allocator, arg: GetFuncInstanceKey) Allocator.Error!Index {
if (arg.inferred_error_set)
return getFuncInstanceIes(ip, gpa, arg);
const func_ty = try ip.getFuncType(gpa, .{
.param_types = arg.param_types,
.return_type = arg.bare_return_type,
.noalias_bits = arg.noalias_bits,
.cc = arg.cc,
.is_noinline = arg.is_noinline,
});
const generic_owner = unwrapCoercedFunc(ip, arg.generic_owner);
assert(arg.comptime_args.len == ip.funcTypeParamsLen(ip.typeOf(generic_owner)));
try ip.extra.ensureUnusedCapacity(gpa, @typeInfo(Tag.FuncInstance).Struct.fields.len +
arg.comptime_args.len);
const prev_extra_len = ip.extra.items.len;
errdefer ip.extra.items.len = prev_extra_len;
const func_extra_index = ip.addExtraAssumeCapacity(Tag.FuncInstance{
.analysis = .{
.state = if (arg.cc == .Inline) .inline_only else .none,
.is_cold = false,
.is_noinline = arg.is_noinline,
.calls_or_awaits_errorable_fn = false,
.stack_alignment = .none,
.inferred_error_set = false,
},
// This is populated after we create the Decl below. It is not read
// by equality or hashing functions.
.owner_decl = undefined,
.ty = func_ty,
.branch_quota = 0,
.generic_owner = generic_owner,
});
ip.extra.appendSliceAssumeCapacity(@ptrCast(arg.comptime_args));
const gop = try ip.map.getOrPutAdapted(gpa, Key{
.func = extraFuncInstance(ip, func_extra_index),
}, KeyAdapter{ .intern_pool = ip });
errdefer _ = ip.map.pop();
if (gop.found_existing) {
ip.extra.items.len = prev_extra_len;
return @enumFromInt(gop.index);
}
const func_index: Index = @enumFromInt(ip.items.len);
try ip.items.append(gpa, .{
.tag = .func_instance,
.data = func_extra_index,
});
errdefer ip.items.len -= 1;
return finishFuncInstance(
ip,
gpa,
generic_owner,
func_index,
func_extra_index,
arg.alignment,
arg.section,
);
}
/// This function exists separately than `getFuncInstance` because it needs to
/// create 4 new items in the InternPool atomically before it can look for an
/// existing item in the map.
pub fn getFuncInstanceIes(
ip: *InternPool,
gpa: Allocator,
arg: GetFuncInstanceKey,
) Allocator.Error!Index {
// Validate input parameters.
assert(arg.inferred_error_set);
assert(arg.bare_return_type != .none);
for (arg.param_types) |param_type| assert(param_type != .none);
const generic_owner = unwrapCoercedFunc(ip, arg.generic_owner);
// The strategy here is to add the function decl unconditionally, then to
// ask if it already exists, and if so, revert the lengths of the mutated
// arrays. This is similar to what `getOrPutTrailingString` does.
const prev_extra_len = ip.extra.items.len;
const params_len: u32 = @intCast(arg.param_types.len);
try ip.map.ensureUnusedCapacity(gpa, 4);
try ip.extra.ensureUnusedCapacity(gpa, @typeInfo(Tag.FuncInstance).Struct.fields.len +
1 + // inferred_error_set
arg.comptime_args.len +
@typeInfo(Tag.ErrorUnionType).Struct.fields.len +
@typeInfo(Tag.TypeFunction).Struct.fields.len +
@intFromBool(arg.noalias_bits != 0) +
params_len);
try ip.items.ensureUnusedCapacity(gpa, 4);
const func_index: Index = @enumFromInt(ip.items.len);
const error_union_type: Index = @enumFromInt(ip.items.len + 1);
const error_set_type: Index = @enumFromInt(ip.items.len + 2);
const func_ty: Index = @enumFromInt(ip.items.len + 3);
const func_extra_index = ip.addExtraAssumeCapacity(Tag.FuncInstance{
.analysis = .{
.state = if (arg.cc == .Inline) .inline_only else .none,
.is_cold = false,
.is_noinline = arg.is_noinline,
.calls_or_awaits_errorable_fn = false,
.stack_alignment = .none,
.inferred_error_set = true,
},
// This is populated after we create the Decl below. It is not read
// by equality or hashing functions.
.owner_decl = undefined,
.ty = func_ty,
.branch_quota = 0,
.generic_owner = generic_owner,
});
ip.extra.appendAssumeCapacity(@intFromEnum(Index.none)); // resolved error set
ip.extra.appendSliceAssumeCapacity(@ptrCast(arg.comptime_args));
const func_type_extra_index = ip.addExtraAssumeCapacity(Tag.TypeFunction{
.params_len = params_len,
.return_type = error_union_type,
.flags = .{
.cc = arg.cc,
.is_var_args = false,
.has_comptime_bits = false,
.has_noalias_bits = arg.noalias_bits != 0,
.is_generic = false,
.is_noinline = arg.is_noinline,
.cc_is_generic = false,
.section_is_generic = false,
.addrspace_is_generic = false,
},
});
// no comptime_bits because has_comptime_bits is false
if (arg.noalias_bits != 0) ip.extra.appendAssumeCapacity(arg.noalias_bits);
ip.extra.appendSliceAssumeCapacity(@ptrCast(arg.param_types));
// TODO: add appendSliceAssumeCapacity to MultiArrayList.
ip.items.appendAssumeCapacity(.{
.tag = .func_instance,
.data = func_extra_index,
});
ip.items.appendAssumeCapacity(.{
.tag = .type_error_union,
.data = ip.addExtraAssumeCapacity(Tag.ErrorUnionType{
.error_set_type = error_set_type,
.payload_type = arg.bare_return_type,
}),
});
ip.items.appendAssumeCapacity(.{
.tag = .type_inferred_error_set,
.data = @intFromEnum(func_index),
});
ip.items.appendAssumeCapacity(.{
.tag = .type_function,
.data = func_type_extra_index,
});
const adapter: KeyAdapter = .{ .intern_pool = ip };
const gop = ip.map.getOrPutAssumeCapacityAdapted(Key{
.func = extraFuncInstance(ip, func_extra_index),
}, adapter);
if (gop.found_existing) {
// Hot path: undo the additions to our two arrays.
ip.items.len -= 4;
ip.extra.items.len = prev_extra_len;
return @enumFromInt(gop.index);
}
// Synchronize the map with items.
assert(!ip.map.getOrPutAssumeCapacityAdapted(Key{ .error_union_type = .{
.error_set_type = error_set_type,
.payload_type = arg.bare_return_type,
} }, adapter).found_existing);
assert(!ip.map.getOrPutAssumeCapacityAdapted(Key{
.inferred_error_set_type = func_index,
}, adapter).found_existing);
assert(!ip.map.getOrPutAssumeCapacityAdapted(Key{
.func_type = extraFuncType(ip, func_type_extra_index),
}, adapter).found_existing);
return finishFuncInstance(
ip,
gpa,
generic_owner,
func_index,
func_extra_index,
arg.alignment,
arg.section,
);
}
fn finishFuncInstance(
ip: *InternPool,
gpa: Allocator,
generic_owner: Index,
func_index: Index,
func_extra_index: u32,
alignment: Alignment,
section: OptionalNullTerminatedString,
) Allocator.Error!Index {
const fn_owner_decl = ip.declPtr(ip.funcDeclOwner(generic_owner));
const decl_index = try ip.createDecl(gpa, .{
.name = undefined,
.src_namespace = fn_owner_decl.src_namespace,
.src_node = fn_owner_decl.src_node,
.src_line = fn_owner_decl.src_line,
.has_tv = true,
.owns_tv = true,
.val = @import("Value.zig").fromInterned(func_index),
.alignment = alignment,
.@"linksection" = section,
.@"addrspace" = fn_owner_decl.@"addrspace",
.analysis = .complete,
.zir_decl_index = fn_owner_decl.zir_decl_index,
.is_pub = fn_owner_decl.is_pub,
.is_exported = fn_owner_decl.is_exported,
.kind = .anon,
});
errdefer ip.destroyDecl(gpa, decl_index);
// Populate the owner_decl field which was left undefined until now.
ip.extra.items[
func_extra_index + std.meta.fieldIndex(Tag.FuncInstance, "owner_decl").?
] = @intFromEnum(decl_index);
// TODO: improve this name
const decl = ip.declPtr(decl_index);
decl.name = try ip.getOrPutStringFmt(gpa, "{}__anon_{d}", .{
fn_owner_decl.name.fmt(ip), @intFromEnum(decl_index),
});
return func_index;
}
pub const EnumTypeInit = struct {
has_namespace: bool,
has_values: bool,
tag_mode: LoadedEnumType.TagMode,
fields_len: u32,
key: union(enum) {
declared: struct {
zir_index: TrackedInst.Index,
captures: []const CaptureValue,
},
reified: struct {
zir_index: TrackedInst.Index,
type_hash: u64,
},
},
};
pub const WipEnumType = struct {
index: Index,
tag_ty_index: u32,
decl_index: u32,
namespace_index: ?u32,
names_map: MapIndex,
names_start: u32,
values_map: OptionalMapIndex,
values_start: u32,
pub fn prepare(
wip: WipEnumType,
ip: *InternPool,
decl: DeclIndex,
namespace: OptionalNamespaceIndex,
) void {
ip.extra.items[wip.decl_index] = @intFromEnum(decl);
if (wip.namespace_index) |i| {
ip.extra.items[i] = @intFromEnum(namespace.unwrap().?);
} else {
assert(namespace == .none);
}
}
pub fn setTagTy(wip: WipEnumType, ip: *InternPool, tag_ty: Index) void {
assert(ip.isIntegerType(tag_ty));
ip.extra.items[wip.tag_ty_index] = @intFromEnum(tag_ty);
}
pub const FieldConflict = struct {
kind: enum { name, value },
prev_field_idx: u32,
};
/// Returns the already-existing field with the same name or value, if any.
/// If the enum is automatially numbered, `value` must be `.none`.
/// Otherwise, the type of `value` must be the integer tag type of the enum.
pub fn nextField(wip: WipEnumType, ip: *InternPool, name: NullTerminatedString, value: Index) ?FieldConflict {
if (ip.addFieldName(wip.names_map, wip.names_start, name)) |conflict| {
return .{ .kind = .name, .prev_field_idx = conflict };
}
if (value == .none) {
assert(wip.values_map == .none);
return null;
}
assert(ip.typeOf(value) == @as(Index, @enumFromInt(ip.extra.items[wip.tag_ty_index])));
const map = &ip.maps.items[@intFromEnum(wip.values_map.unwrap().?)];
const field_index = map.count();
const indexes = ip.extra.items[wip.values_start..][0..field_index];
const adapter: Index.Adapter = .{ .indexes = @ptrCast(indexes) };
const gop = map.getOrPutAssumeCapacityAdapted(value, adapter);
if (gop.found_existing) {
return .{ .kind = .value, .prev_field_idx = @intCast(gop.index) };
}
ip.extra.items[wip.values_start + field_index] = @intFromEnum(value);
return null;
}
pub fn cancel(wip: WipEnumType, ip: *InternPool) void {
ip.remove(wip.index);
}
pub const Result = union(enum) {
wip: WipEnumType,
existing: Index,
};
};
pub fn getEnumType(
ip: *InternPool,
gpa: Allocator,
ini: EnumTypeInit,
) Allocator.Error!WipEnumType.Result {
const adapter: KeyAdapter = .{ .intern_pool = ip };
const gop = try ip.map.getOrPutAdapted(gpa, Key{ .enum_type = switch (ini.key) {
.declared => |d| .{ .declared = .{
.zir_index = d.zir_index,
.captures = .{ .external = d.captures },
} },
.reified => |r| .{ .reified = .{
.zir_index = r.zir_index,
.type_hash = r.type_hash,
} },
} }, adapter);
if (gop.found_existing) return .{ .existing = @enumFromInt(gop.index) };
assert(gop.index == ip.items.len);
errdefer _ = ip.map.pop();
try ip.items.ensureUnusedCapacity(gpa, 1);
const names_map = try ip.addMap(gpa, ini.fields_len);
errdefer _ = ip.maps.pop();
switch (ini.tag_mode) {
.auto => {
assert(!ini.has_values);
try ip.extra.ensureUnusedCapacity(gpa, @typeInfo(EnumAuto).Struct.fields.len +
// TODO: fmt bug
// zig fmt: off
switch (ini.key) {
.declared => |d| d.captures.len,
.reified => 2, // type_hash: PackedU64
} +
// zig fmt: on
ini.fields_len); // field types
const extra_index = ip.addExtraAssumeCapacity(EnumAuto{
.decl = undefined, // set by `prepare`
.captures_len = switch (ini.key) {
.declared => |d| @intCast(d.captures.len),
.reified => std.math.maxInt(u32),
},
.namespace = .none,
.int_tag_type = .none, // set by `prepare`
.fields_len = ini.fields_len,
.names_map = names_map,
.zir_index = switch (ini.key) {
inline else => |x| x.zir_index,
}.toOptional(),
});
ip.items.appendAssumeCapacity(.{
.tag = .type_enum_auto,
.data = extra_index,
});
switch (ini.key) {
.declared => |d| ip.extra.appendSliceAssumeCapacity(@ptrCast(d.captures)),
.reified => |r| _ = ip.addExtraAssumeCapacity(PackedU64.init(r.type_hash)),
}
const names_start = ip.extra.items.len;
ip.extra.appendNTimesAssumeCapacity(undefined, ini.fields_len);
return .{ .wip = .{
.index = @enumFromInt(gop.index),
.tag_ty_index = extra_index + std.meta.fieldIndex(EnumAuto, "int_tag_type").?,
.decl_index = extra_index + std.meta.fieldIndex(EnumAuto, "decl").?,
.namespace_index = if (ini.has_namespace) extra_index + std.meta.fieldIndex(EnumAuto, "namespace").? else null,
.names_map = names_map,
.names_start = @intCast(names_start),
.values_map = .none,
.values_start = undefined,
} };
},
.explicit, .nonexhaustive => {
const values_map: OptionalMapIndex = if (!ini.has_values) .none else m: {
const values_map = try ip.addMap(gpa, ini.fields_len);
break :m values_map.toOptional();
};
errdefer if (ini.has_values) {
_ = ip.map.pop();
};
try ip.extra.ensureUnusedCapacity(gpa, @typeInfo(EnumExplicit).Struct.fields.len +
// TODO: fmt bug
// zig fmt: off
switch (ini.key) {
.declared => |d| d.captures.len,
.reified => 2, // type_hash: PackedU64
} +
// zig fmt: on
ini.fields_len + // field types
ini.fields_len * @intFromBool(ini.has_values)); // field values
const extra_index = ip.addExtraAssumeCapacity(EnumExplicit{
.decl = undefined, // set by `prepare`
.captures_len = switch (ini.key) {
.declared => |d| @intCast(d.captures.len),
.reified => std.math.maxInt(u32),
},
.namespace = .none,
.int_tag_type = .none, // set by `prepare`
.fields_len = ini.fields_len,
.names_map = names_map,
.values_map = values_map,
.zir_index = switch (ini.key) {
inline else => |x| x.zir_index,
}.toOptional(),
});
ip.items.appendAssumeCapacity(.{
.tag = switch (ini.tag_mode) {
.auto => unreachable,
.explicit => .type_enum_explicit,
.nonexhaustive => .type_enum_nonexhaustive,
},
.data = extra_index,
});
switch (ini.key) {
.declared => |d| ip.extra.appendSliceAssumeCapacity(@ptrCast(d.captures)),
.reified => |r| _ = ip.addExtraAssumeCapacity(PackedU64.init(r.type_hash)),
}
const names_start = ip.extra.items.len;
ip.extra.appendNTimesAssumeCapacity(undefined, ini.fields_len);
const values_start = ip.extra.items.len;
if (ini.has_values) {
ip.extra.appendNTimesAssumeCapacity(undefined, ini.fields_len);
}
return .{ .wip = .{
.index = @enumFromInt(gop.index),
.tag_ty_index = extra_index + std.meta.fieldIndex(EnumAuto, "int_tag_type").?,
.decl_index = extra_index + std.meta.fieldIndex(EnumAuto, "decl").?,
.namespace_index = if (ini.has_namespace) extra_index + std.meta.fieldIndex(EnumAuto, "namespace").? else null,
.names_map = names_map,
.names_start = @intCast(names_start),
.values_map = values_map,
.values_start = @intCast(values_start),
} };
},
}
}
const GeneratedTagEnumTypeInit = struct {
decl: DeclIndex,
owner_union_ty: Index,
tag_ty: Index,
names: []const NullTerminatedString,
values: []const Index,
tag_mode: LoadedEnumType.TagMode,
};
/// Creates an enum type which was automatically-generated as the tag type of a
/// `union` with no explicit tag type. Since this is only called once per union
/// type, it asserts that no matching type yet exists.
pub fn getGeneratedTagEnumType(ip: *InternPool, gpa: Allocator, ini: GeneratedTagEnumTypeInit) Allocator.Error!Index {
assert(ip.isUnion(ini.owner_union_ty));
assert(ip.isIntegerType(ini.tag_ty));
for (ini.values) |val| assert(ip.typeOf(val) == ini.tag_ty);
try ip.map.ensureUnusedCapacity(gpa, 1);
try ip.items.ensureUnusedCapacity(gpa, 1);
const names_map = try ip.addMap(gpa, ini.names.len);
errdefer _ = ip.maps.pop();
ip.addStringsToMap(names_map, ini.names);
const fields_len: u32 = @intCast(ini.names.len);
switch (ini.tag_mode) {
.auto => {
try ip.extra.ensureUnusedCapacity(gpa, @typeInfo(EnumAuto).Struct.fields.len +
1 + // owner_union
fields_len); // field names
ip.items.appendAssumeCapacity(.{
.tag = .type_enum_auto,
.data = ip.addExtraAssumeCapacity(EnumAuto{
.decl = ini.decl,
.captures_len = 0,
.namespace = .none,
.int_tag_type = ini.tag_ty,
.fields_len = fields_len,
.names_map = names_map,
.zir_index = .none,
}),
});
ip.extra.appendAssumeCapacity(@intFromEnum(ini.owner_union_ty));
ip.extra.appendSliceAssumeCapacity(@ptrCast(ini.names));
},
.explicit, .nonexhaustive => {
try ip.extra.ensureUnusedCapacity(gpa, @typeInfo(EnumExplicit).Struct.fields.len +
1 + // owner_union
fields_len + // field names
ini.values.len); // field values
const values_map: OptionalMapIndex = if (ini.values.len != 0) m: {
const map = try ip.addMap(gpa, ini.values.len);
addIndexesToMap(ip, map, ini.values);
break :m map.toOptional();
} else .none;
// We don't clean up the values map on error!
errdefer @compileError("error path leaks values_map");
ip.items.appendAssumeCapacity(.{
.tag = switch (ini.tag_mode) {
.explicit => .type_enum_explicit,
.nonexhaustive => .type_enum_nonexhaustive,
.auto => unreachable,
},
.data = ip.addExtraAssumeCapacity(EnumExplicit{
.decl = ini.decl,
.captures_len = 0,
.namespace = .none,
.int_tag_type = ini.tag_ty,
.fields_len = fields_len,
.names_map = names_map,
.values_map = values_map,
.zir_index = .none,
}),
});
ip.extra.appendAssumeCapacity(@intFromEnum(ini.owner_union_ty));
ip.extra.appendSliceAssumeCapacity(@ptrCast(ini.names));
ip.extra.appendSliceAssumeCapacity(@ptrCast(ini.values));
},
}
// Same as above
errdefer @compileError("error path leaks values_map and extra data");
// Capacity for this was ensured earlier
const adapter: KeyAdapter = .{ .intern_pool = ip };
const gop = ip.map.getOrPutAssumeCapacityAdapted(Key{ .enum_type = .{
.generated_tag = .{ .union_type = ini.owner_union_ty },
} }, adapter);
assert(!gop.found_existing);
assert(gop.index == ip.items.len - 1);
return @enumFromInt(gop.index);
}
pub const OpaqueTypeInit = struct {
has_namespace: bool,
key: union(enum) {
declared: struct {
zir_index: TrackedInst.Index,
captures: []const CaptureValue,
},
reified: struct {
zir_index: TrackedInst.Index,
// No type hash since reifid opaques have no data other than the `@Type` location
},
},
};
pub fn getOpaqueType(ip: *InternPool, gpa: Allocator, ini: OpaqueTypeInit) Allocator.Error!WipNamespaceType.Result {
const adapter: KeyAdapter = .{ .intern_pool = ip };
const gop = try ip.map.getOrPutAdapted(gpa, Key{ .opaque_type = switch (ini.key) {
.declared => |d| .{ .declared = .{
.zir_index = d.zir_index,
.captures = .{ .external = d.captures },
} },
.reified => |r| .{ .reified = .{
.zir_index = r.zir_index,
.type_hash = 0,
} },
} }, adapter);
if (gop.found_existing) return .{ .existing = @enumFromInt(gop.index) };
errdefer _ = ip.map.pop();
try ip.items.ensureUnusedCapacity(gpa, 1);
try ip.extra.ensureUnusedCapacity(gpa, @typeInfo(Tag.TypeOpaque).Struct.fields.len + switch (ini.key) {
.declared => |d| d.captures.len,
.reified => 0,
});
const extra_index = ip.addExtraAssumeCapacity(Tag.TypeOpaque{
.decl = undefined, // set by `finish`
.namespace = .none,
.zir_index = switch (ini.key) {
inline else => |x| x.zir_index,
},
.captures_len = switch (ini.key) {
.declared => |d| @intCast(d.captures.len),
.reified => std.math.maxInt(u32),
},
});
ip.items.appendAssumeCapacity(.{
.tag = .type_opaque,
.data = extra_index,
});
switch (ini.key) {
.declared => |d| ip.extra.appendSliceAssumeCapacity(@ptrCast(d.captures)),
.reified => {},
}
return .{ .wip = .{
.index = @enumFromInt(gop.index),
.decl_extra_index = extra_index + std.meta.fieldIndex(Tag.TypeOpaque, "decl").?,
.namespace_extra_index = if (ini.has_namespace)
extra_index + std.meta.fieldIndex(Tag.TypeOpaque, "namespace").?
else
null,
} };
}
pub fn getIfExists(ip: *const InternPool, key: Key) ?Index {
const adapter: KeyAdapter = .{ .intern_pool = ip };
const index = ip.map.getIndexAdapted(key, adapter) orelse return null;
return @enumFromInt(index);
}
pub fn getAssumeExists(ip: *const InternPool, key: Key) Index {
return ip.getIfExists(key).?;
}
fn addStringsToMap(
ip: *InternPool,
map_index: MapIndex,
strings: []const NullTerminatedString,
) void {
const map = &ip.maps.items[@intFromEnum(map_index)];
const adapter: NullTerminatedString.Adapter = .{ .strings = strings };
for (strings) |string| {
const gop = map.getOrPutAssumeCapacityAdapted(string, adapter);
assert(!gop.found_existing);
}
}
fn addIndexesToMap(
ip: *InternPool,
map_index: MapIndex,
indexes: []const Index,
) void {
const map = &ip.maps.items[@intFromEnum(map_index)];
const adapter: Index.Adapter = .{ .indexes = indexes };
for (indexes) |index| {
const gop = map.getOrPutAssumeCapacityAdapted(index, adapter);
assert(!gop.found_existing);
}
}
fn addMap(ip: *InternPool, gpa: Allocator, cap: usize) Allocator.Error!MapIndex {
const ptr = try ip.maps.addOne(gpa);
errdefer _ = ip.maps.pop();
ptr.* = .{};
try ptr.ensureTotalCapacity(gpa, cap);
return @enumFromInt(ip.maps.items.len - 1);
}
/// This operation only happens under compile error conditions.
/// Leak the index until the next garbage collection.
/// Invalidates all references to this index.
pub fn remove(ip: *InternPool, index: Index) void {
if (@intFromEnum(index) < static_keys.len) {
// The item being removed replaced a special index via `InternPool.resolveBuiltinType`.
// Restore the original item at this index.
switch (static_keys[@intFromEnum(index)]) {
.simple_type => |s| {
ip.items.set(@intFromEnum(index), .{
.tag = .simple_type,
.data = @intFromEnum(s),
});
},
else => unreachable,
}
return;
}
if (@intFromEnum(index) == ip.items.len - 1) {
// Happy case - we can just drop the item without affecting any other indices.
ip.items.len -= 1;
_ = ip.map.pop();
} else {
// We must preserve the item so that indices following it remain valid.
// Thus, we will rewrite the tag to `removed`, leaking the item until
// next GC but causing `KeyAdapter` to ignore it.
ip.items.set(@intFromEnum(index), .{ .tag = .removed, .data = undefined });
}
}
fn addInt(ip: *InternPool, gpa: Allocator, ty: Index, tag: Tag, limbs: []const Limb) !void {
const limbs_len = @as(u32, @intCast(limbs.len));
try ip.reserveLimbs(gpa, @typeInfo(Int).Struct.fields.len + limbs_len);
ip.items.appendAssumeCapacity(.{
.tag = tag,
.data = ip.addLimbsExtraAssumeCapacity(Int{
.ty = ty,
.limbs_len = limbs_len,
}),
});
ip.addLimbsAssumeCapacity(limbs);
}
fn addExtra(ip: *InternPool, gpa: Allocator, extra: anytype) Allocator.Error!u32 {
const fields = @typeInfo(@TypeOf(extra)).Struct.fields;
try ip.extra.ensureUnusedCapacity(gpa, fields.len);
return ip.addExtraAssumeCapacity(extra);
}
fn addExtraAssumeCapacity(ip: *InternPool, extra: anytype) u32 {
const result = @as(u32, @intCast(ip.extra.items.len));
inline for (@typeInfo(@TypeOf(extra)).Struct.fields) |field| {
ip.extra.appendAssumeCapacity(switch (field.type) {
Index,
DeclIndex,
NamespaceIndex,
OptionalNamespaceIndex,
MapIndex,
OptionalMapIndex,
RuntimeIndex,
String,
NullTerminatedString,
OptionalNullTerminatedString,
Tag.TypePointer.VectorIndex,
TrackedInst.Index,
TrackedInst.Index.Optional,
ComptimeAllocIndex,
=> @intFromEnum(@field(extra, field.name)),
u32,
i32,
FuncAnalysis,
Tag.TypePointer.Flags,
Tag.TypeFunction.Flags,
Tag.TypePointer.PackedOffset,
Tag.TypeUnion.Flags,
Tag.TypeStruct.Flags,
Tag.TypeStructPacked.Flags,
Tag.Variable.Flags,
=> @bitCast(@field(extra, field.name)),
else => @compileError("bad field type: " ++ @typeName(field.type)),
});
}
return result;
}
fn reserveLimbs(ip: *InternPool, gpa: Allocator, n: usize) !void {
switch (@sizeOf(Limb)) {
@sizeOf(u32) => try ip.extra.ensureUnusedCapacity(gpa, n),
@sizeOf(u64) => try ip.limbs.ensureUnusedCapacity(gpa, n),
else => @compileError("unsupported host"),
}
}
fn addLimbsExtraAssumeCapacity(ip: *InternPool, extra: anytype) u32 {
switch (@sizeOf(Limb)) {
@sizeOf(u32) => return addExtraAssumeCapacity(ip, extra),
@sizeOf(u64) => {},
else => @compileError("unsupported host"),
}
const result = @as(u32, @intCast(ip.limbs.items.len));
inline for (@typeInfo(@TypeOf(extra)).Struct.fields, 0..) |field, i| {
const new: u32 = switch (field.type) {
u32 => @field(extra, field.name),
Index => @intFromEnum(@field(extra, field.name)),
else => @compileError("bad field type: " ++ @typeName(field.type)),
};
if (i % 2 == 0) {
ip.limbs.appendAssumeCapacity(new);
} else {
ip.limbs.items[ip.limbs.items.len - 1] |= @as(u64, new) << 32;
}
}
return result;
}
fn addLimbsAssumeCapacity(ip: *InternPool, limbs: []const Limb) void {
switch (@sizeOf(Limb)) {
@sizeOf(u32) => ip.extra.appendSliceAssumeCapacity(limbs),
@sizeOf(u64) => ip.limbs.appendSliceAssumeCapacity(limbs),
else => @compileError("unsupported host"),
}
}
fn extraDataTrail(ip: *const InternPool, comptime T: type, index: usize) struct { data: T, end: u32 } {
var result: T = undefined;
const fields = @typeInfo(T).Struct.fields;
inline for (fields, 0..) |field, i| {
const int32 = ip.extra.items[i + index];
@field(result, field.name) = switch (field.type) {
Index,
DeclIndex,
NamespaceIndex,
OptionalNamespaceIndex,
MapIndex,
OptionalMapIndex,
RuntimeIndex,
String,
NullTerminatedString,
OptionalNullTerminatedString,
Tag.TypePointer.VectorIndex,
TrackedInst.Index,
TrackedInst.Index.Optional,
ComptimeAllocIndex,
=> @enumFromInt(int32),
u32,
i32,
Tag.TypePointer.Flags,
Tag.TypeFunction.Flags,
Tag.TypePointer.PackedOffset,
Tag.TypeUnion.Flags,
Tag.TypeStruct.Flags,
Tag.TypeStructPacked.Flags,
Tag.Variable.Flags,
FuncAnalysis,
=> @bitCast(int32),
else => @compileError("bad field type: " ++ @typeName(field.type)),
};
}
return .{
.data = result,
.end = @intCast(index + fields.len),
};
}
fn extraData(ip: *const InternPool, comptime T: type, index: usize) T {
return extraDataTrail(ip, T, index).data;
}
/// Asserts the struct has 32-bit fields and the number of fields is evenly divisible by 2.
fn limbData(ip: *const InternPool, comptime T: type, index: usize) T {
switch (@sizeOf(Limb)) {
@sizeOf(u32) => return extraData(ip, T, index),
@sizeOf(u64) => {},
else => @compileError("unsupported host"),
}
var result: T = undefined;
inline for (@typeInfo(T).Struct.fields, 0..) |field, i| {
const host_int = ip.limbs.items[index + i / 2];
const int32 = if (i % 2 == 0)
@as(u32, @truncate(host_int))
else
@as(u32, @truncate(host_int >> 32));
@field(result, field.name) = switch (field.type) {
u32 => int32,
Index => @as(Index, @enumFromInt(int32)),
else => @compileError("bad field type: " ++ @typeName(field.type)),
};
}
return result;
}
/// This function returns the Limb slice that is trailing data after a payload.
fn limbSlice(ip: *const InternPool, comptime S: type, limb_index: u32, len: u32) []const Limb {
const field_count = @typeInfo(S).Struct.fields.len;
switch (@sizeOf(Limb)) {
@sizeOf(u32) => {
const start = limb_index + field_count;
return ip.extra.items[start..][0..len];
},
@sizeOf(u64) => {
const start = limb_index + @divExact(field_count, 2);
return ip.limbs.items[start..][0..len];
},
else => @compileError("unsupported host"),
}
}
const LimbsAsIndexes = struct {
start: u32,
len: u32,
};
fn limbsSliceToIndex(ip: *const InternPool, limbs: []const Limb) LimbsAsIndexes {
const host_slice = switch (@sizeOf(Limb)) {
@sizeOf(u32) => ip.extra.items,
@sizeOf(u64) => ip.limbs.items,
else => @compileError("unsupported host"),
};
// TODO: https://github.com/ziglang/zig/issues/1738
return .{
.start = @as(u32, @intCast(@divExact(@intFromPtr(limbs.ptr) - @intFromPtr(host_slice.ptr), @sizeOf(Limb)))),
.len = @as(u32, @intCast(limbs.len)),
};
}
/// This function converts Limb array indexes to a primitive slice type.
fn limbsIndexToSlice(ip: *const InternPool, limbs: LimbsAsIndexes) []const Limb {
return switch (@sizeOf(Limb)) {
@sizeOf(u32) => ip.extra.items[limbs.start..][0..limbs.len],
@sizeOf(u64) => ip.limbs.items[limbs.start..][0..limbs.len],
else => @compileError("unsupported host"),
};
}
test "basic usage" {
const gpa = std.testing.allocator;
var ip: InternPool = .{};
defer ip.deinit(gpa);
const i32_type = try ip.get(gpa, .{ .int_type = .{
.signedness = .signed,
.bits = 32,
} });
const array_i32 = try ip.get(gpa, .{ .array_type = .{
.len = 10,
.child = i32_type,
.sentinel = .none,
} });
const another_i32_type = try ip.get(gpa, .{ .int_type = .{
.signedness = .signed,
.bits = 32,
} });
try std.testing.expect(another_i32_type == i32_type);
const another_array_i32 = try ip.get(gpa, .{ .array_type = .{
.len = 10,
.child = i32_type,
.sentinel = .none,
} });
try std.testing.expect(another_array_i32 == array_i32);
}
pub fn childType(ip: *const InternPool, i: Index) Index {
return switch (ip.indexToKey(i)) {
.ptr_type => |ptr_type| ptr_type.child,
.vector_type => |vector_type| vector_type.child,
.array_type => |array_type| array_type.child,
.opt_type, .anyframe_type => |child| child,
else => unreachable,
};
}
/// Given a slice type, returns the type of the ptr field.
pub fn slicePtrType(ip: *const InternPool, i: Index) Index {
switch (i) {
.slice_const_u8_type => return .manyptr_const_u8_type,
.slice_const_u8_sentinel_0_type => return .manyptr_const_u8_sentinel_0_type,
else => {},
}
const item = ip.items.get(@intFromEnum(i));
switch (item.tag) {
.type_slice => return @enumFromInt(item.data),
else => unreachable, // not a slice type
}
}
/// Given a slice value, returns the value of the ptr field.
pub fn slicePtr(ip: *const InternPool, i: Index) Index {
const item = ip.items.get(@intFromEnum(i));
switch (item.tag) {
.ptr_slice => return ip.extraData(PtrSlice, item.data).ptr,
else => unreachable, // not a slice value
}
}
/// Given a slice value, returns the value of the len field.
pub fn sliceLen(ip: *const InternPool, i: Index) Index {
const item = ip.items.get(@intFromEnum(i));
switch (item.tag) {
.ptr_slice => return ip.extraData(PtrSlice, item.data).len,
else => unreachable, // not a slice value
}
}
/// Given an existing value, returns the same value but with the supplied type.
/// Only some combinations are allowed:
/// * identity coercion
/// * undef => any
/// * int <=> int
/// * int <=> enum
/// * enum_literal => enum
/// * float <=> float
/// * ptr <=> ptr
/// * opt ptr <=> ptr
/// * opt ptr <=> opt ptr
/// * int <=> ptr
/// * null_value => opt
/// * payload => opt
/// * error set <=> error set
/// * error union <=> error union
/// * error set => error union
/// * payload => error union
/// * fn <=> fn
/// * aggregate <=> aggregate (where children can also be coerced)
pub fn getCoerced(ip: *InternPool, gpa: Allocator, val: Index, new_ty: Index) Allocator.Error!Index {
const old_ty = ip.typeOf(val);
if (old_ty == new_ty) return val;
const tags = ip.items.items(.tag);
switch (val) {
.undef => return ip.get(gpa, .{ .undef = new_ty }),
.null_value => {
if (ip.isOptionalType(new_ty)) return ip.get(gpa, .{ .opt = .{
.ty = new_ty,
.val = .none,
} });
if (ip.isPointerType(new_ty)) switch (ip.indexToKey(new_ty).ptr_type.flags.size) {
.One, .Many, .C => return ip.get(gpa, .{ .ptr = .{
.ty = new_ty,
.addr = .{ .int = .zero_usize },
} }),
.Slice => return ip.get(gpa, .{ .slice = .{
.ty = new_ty,
.ptr = try ip.get(gpa, .{ .ptr = .{
.ty = ip.slicePtrType(new_ty),
.addr = .{ .int = .zero_usize },
} }),
.len = try ip.get(gpa, .{ .undef = .usize_type }),
} }),
};
},
else => switch (tags[@intFromEnum(val)]) {
.func_decl => return getCoercedFuncDecl(ip, gpa, val, new_ty),
.func_instance => return getCoercedFuncInstance(ip, gpa, val, new_ty),
.func_coerced => {
const extra_index = ip.items.items(.data)[@intFromEnum(val)];
const func: Index = @enumFromInt(
ip.extra.items[extra_index + std.meta.fieldIndex(Tag.FuncCoerced, "func").?],
);
switch (tags[@intFromEnum(func)]) {
.func_decl => return getCoercedFuncDecl(ip, gpa, val, new_ty),
.func_instance => return getCoercedFuncInstance(ip, gpa, val, new_ty),
else => unreachable,
}
},
else => {},
},
}
switch (ip.indexToKey(val)) {
.undef => return ip.get(gpa, .{ .undef = new_ty }),
.extern_func => |extern_func| if (ip.isFunctionType(new_ty))
return ip.get(gpa, .{ .extern_func = .{
.ty = new_ty,
.decl = extern_func.decl,
.lib_name = extern_func.lib_name,
} }),
.func => unreachable,
.int => |int| switch (ip.indexToKey(new_ty)) {
.enum_type => return ip.get(gpa, .{ .enum_tag = .{
.ty = new_ty,
.int = try ip.getCoerced(gpa, val, ip.loadEnumType(new_ty).tag_ty),
} }),
.ptr_type => return ip.get(gpa, .{ .ptr = .{
.ty = new_ty,
.addr = .{ .int = try ip.getCoerced(gpa, val, .usize_type) },
} }),
else => if (ip.isIntegerType(new_ty))
return getCoercedInts(ip, gpa, int, new_ty),
},
.float => |float| switch (ip.indexToKey(new_ty)) {
.simple_type => |simple| switch (simple) {
.f16,
.f32,
.f64,
.f80,
.f128,
.c_longdouble,
.comptime_float,
=> return ip.get(gpa, .{ .float = .{
.ty = new_ty,
.storage = float.storage,
} }),
else => {},
},
else => {},
},
.enum_tag => |enum_tag| if (ip.isIntegerType(new_ty))
return getCoercedInts(ip, gpa, ip.indexToKey(enum_tag.int).int, new_ty),
.enum_literal => |enum_literal| switch (ip.indexToKey(new_ty)) {
.enum_type => {
const enum_type = ip.loadEnumType(new_ty);
const index = enum_type.nameIndex(ip, enum_literal).?;
return ip.get(gpa, .{ .enum_tag = .{
.ty = new_ty,
.int = if (enum_type.values.len != 0)
enum_type.values.get(ip)[index]
else
try ip.get(gpa, .{ .int = .{
.ty = enum_type.tag_ty,
.storage = .{ .u64 = index },
} }),
} });
},
else => {},
},
.slice => |slice| if (ip.isPointerType(new_ty) and ip.indexToKey(new_ty).ptr_type.flags.size == .Slice)
return ip.get(gpa, .{ .slice = .{
.ty = new_ty,
.ptr = try ip.getCoerced(gpa, slice.ptr, ip.slicePtrType(new_ty)),
.len = slice.len,
} })
else if (ip.isIntegerType(new_ty))
return ip.getCoerced(gpa, slice.ptr, new_ty),
.ptr => |ptr| if (ip.isPointerType(new_ty) and ip.indexToKey(new_ty).ptr_type.flags.size != .Slice)
return ip.get(gpa, .{ .ptr = .{
.ty = new_ty,
.addr = ptr.addr,
} })
else if (ip.isIntegerType(new_ty))
switch (ptr.addr) {
.int => |int| return ip.getCoerced(gpa, int, new_ty),
else => {},
},
.opt => |opt| switch (ip.indexToKey(new_ty)) {
.ptr_type => |ptr_type| return switch (opt.val) {
.none => switch (ptr_type.flags.size) {
.One, .Many, .C => try ip.get(gpa, .{ .ptr = .{
.ty = new_ty,
.addr = .{ .int = .zero_usize },
} }),
.Slice => try ip.get(gpa, .{ .slice = .{
.ty = new_ty,
.ptr = try ip.get(gpa, .{ .ptr = .{
.ty = ip.slicePtrType(new_ty),
.addr = .{ .int = .zero_usize },
} }),
.len = try ip.get(gpa, .{ .undef = .usize_type }),
} }),
},
else => |payload| try ip.getCoerced(gpa, payload, new_ty),
},
.opt_type => |child_type| return try ip.get(gpa, .{ .opt = .{
.ty = new_ty,
.val = switch (opt.val) {
.none => .none,
else => try ip.getCoerced(gpa, opt.val, child_type),
},
} }),
else => {},
},
.err => |err| if (ip.isErrorSetType(new_ty))
return ip.get(gpa, .{ .err = .{
.ty = new_ty,
.name = err.name,
} })
else if (ip.isErrorUnionType(new_ty))
return ip.get(gpa, .{ .error_union = .{
.ty = new_ty,
.val = .{ .err_name = err.name },
} }),
.error_union => |error_union| if (ip.isErrorUnionType(new_ty))
return ip.get(gpa, .{ .error_union = .{
.ty = new_ty,
.val = error_union.val,
} }),
.aggregate => |aggregate| {
const new_len = @as(usize, @intCast(ip.aggregateTypeLen(new_ty)));
direct: {
const old_ty_child = switch (ip.indexToKey(old_ty)) {
inline .array_type, .vector_type => |seq_type| seq_type.child,
.anon_struct_type, .struct_type => break :direct,
else => unreachable,
};
const new_ty_child = switch (ip.indexToKey(new_ty)) {
inline .array_type, .vector_type => |seq_type| seq_type.child,
.anon_struct_type, .struct_type => break :direct,
else => unreachable,
};
if (old_ty_child != new_ty_child) break :direct;
// TODO: write something like getCoercedInts to avoid needing to dupe here
switch (aggregate.storage) {
.bytes => |bytes| {
const bytes_copy = try gpa.dupe(u8, bytes[0..new_len]);
defer gpa.free(bytes_copy);
return ip.get(gpa, .{ .aggregate = .{
.ty = new_ty,
.storage = .{ .bytes = bytes_copy },
} });
},
.elems => |elems| {
const elems_copy = try gpa.dupe(Index, elems[0..new_len]);
defer gpa.free(elems_copy);
return ip.get(gpa, .{ .aggregate = .{
.ty = new_ty,
.storage = .{ .elems = elems_copy },
} });
},
.repeated_elem => |elem| {
return ip.get(gpa, .{ .aggregate = .{
.ty = new_ty,
.storage = .{ .repeated_elem = elem },
} });
},
}
}
// Direct approach failed - we must recursively coerce elems
const agg_elems = try gpa.alloc(Index, new_len);
defer gpa.free(agg_elems);
// First, fill the vector with the uncoerced elements. We do this to avoid key
// lifetime issues, since it'll allow us to avoid referencing `aggregate` after we
// begin interning elems.
switch (aggregate.storage) {
.bytes => {
// We have to intern each value here, so unfortunately we can't easily avoid
// the repeated indexToKey calls.
for (agg_elems, 0..) |*elem, i| {
const x = ip.indexToKey(val).aggregate.storage.bytes[i];
elem.* = try ip.get(gpa, .{ .int = .{
.ty = .u8_type,
.storage = .{ .u64 = x },
} });
}
},
.elems => |elems| @memcpy(agg_elems, elems[0..new_len]),
.repeated_elem => |elem| @memset(agg_elems, elem),
}
// Now, coerce each element to its new type.
for (agg_elems, 0..) |*elem, i| {
const new_elem_ty = switch (ip.indexToKey(new_ty)) {
inline .array_type, .vector_type => |seq_type| seq_type.child,
.anon_struct_type => |anon_struct_type| anon_struct_type.types.get(ip)[i],
.struct_type => ip.loadStructType(new_ty).field_types.get(ip)[i],
else => unreachable,
};
elem.* = try ip.getCoerced(gpa, elem.*, new_elem_ty);
}
return ip.get(gpa, .{ .aggregate = .{ .ty = new_ty, .storage = .{ .elems = agg_elems } } });
},
else => {},
}
switch (ip.indexToKey(new_ty)) {
.opt_type => |child_type| switch (val) {
.null_value => return ip.get(gpa, .{ .opt = .{
.ty = new_ty,
.val = .none,
} }),
else => return ip.get(gpa, .{ .opt = .{
.ty = new_ty,
.val = try ip.getCoerced(gpa, val, child_type),
} }),
},
.error_union_type => |error_union_type| return ip.get(gpa, .{ .error_union = .{
.ty = new_ty,
.val = .{ .payload = try ip.getCoerced(gpa, val, error_union_type.payload_type) },
} }),
else => {},
}
if (std.debug.runtime_safety) {
std.debug.panic("InternPool.getCoerced of {s} not implemented from {s} to {s}", .{
@tagName(ip.indexToKey(val)),
@tagName(ip.indexToKey(old_ty)),
@tagName(ip.indexToKey(new_ty)),
});
}
unreachable;
}
fn getCoercedFuncDecl(ip: *InternPool, gpa: Allocator, val: Index, new_ty: Index) Allocator.Error!Index {
const datas = ip.items.items(.data);
const extra_index = datas[@intFromEnum(val)];
const prev_ty: Index = @enumFromInt(
ip.extra.items[extra_index + std.meta.fieldIndex(Tag.FuncDecl, "ty").?],
);
if (new_ty == prev_ty) return val;
return getCoercedFunc(ip, gpa, val, new_ty);
}
fn getCoercedFuncInstance(ip: *InternPool, gpa: Allocator, val: Index, new_ty: Index) Allocator.Error!Index {
const datas = ip.items.items(.data);
const extra_index = datas[@intFromEnum(val)];
const prev_ty: Index = @enumFromInt(
ip.extra.items[extra_index + std.meta.fieldIndex(Tag.FuncInstance, "ty").?],
);
if (new_ty == prev_ty) return val;
return getCoercedFunc(ip, gpa, val, new_ty);
}
fn getCoercedFunc(ip: *InternPool, gpa: Allocator, func: Index, ty: Index) Allocator.Error!Index {
const prev_extra_len = ip.extra.items.len;
try ip.extra.ensureUnusedCapacity(gpa, @typeInfo(Tag.FuncCoerced).Struct.fields.len);
try ip.items.ensureUnusedCapacity(gpa, 1);
try ip.map.ensureUnusedCapacity(gpa, 1);
const extra_index = ip.addExtraAssumeCapacity(Tag.FuncCoerced{
.ty = ty,
.func = func,
});
const adapter: KeyAdapter = .{ .intern_pool = ip };
const gop = ip.map.getOrPutAssumeCapacityAdapted(Key{
.func = extraFuncCoerced(ip, extra_index),
}, adapter);
if (gop.found_existing) {
ip.extra.items.len = prev_extra_len;
return @enumFromInt(gop.index);
}
ip.items.appendAssumeCapacity(.{
.tag = .func_coerced,
.data = extra_index,
});
return @enumFromInt(ip.items.len - 1);
}
/// Asserts `val` has an integer type.
/// Assumes `new_ty` is an integer type.
pub fn getCoercedInts(ip: *InternPool, gpa: Allocator, int: Key.Int, new_ty: Index) Allocator.Error!Index {
// The key cannot be passed directly to `get`, otherwise in the case of
// big_int storage, the limbs would be invalidated before they are read.
// Here we pre-reserve the limbs to ensure that the logic in `addInt` will
// not use an invalidated limbs pointer.
const new_storage: Key.Int.Storage = switch (int.storage) {
.u64, .i64, .lazy_align, .lazy_size => int.storage,
.big_int => |big_int| storage: {
const positive = big_int.positive;
const limbs = ip.limbsSliceToIndex(big_int.limbs);
// This line invalidates the limbs slice, but the indexes computed in the
// previous line are still correct.
try reserveLimbs(ip, gpa, @typeInfo(Int).Struct.fields.len + big_int.limbs.len);
break :storage .{ .big_int = .{
.limbs = ip.limbsIndexToSlice(limbs),
.positive = positive,
} };
},
};
return ip.get(gpa, .{ .int = .{
.ty = new_ty,
.storage = new_storage,
} });
}
pub fn indexToFuncType(ip: *const InternPool, val: Index) ?Key.FuncType {
assert(val != .none);
const tags = ip.items.items(.tag);
const datas = ip.items.items(.data);
switch (tags[@intFromEnum(val)]) {
.type_function => return extraFuncType(ip, datas[@intFromEnum(val)]),
else => return null,
}
}
/// includes .comptime_int_type
pub fn isIntegerType(ip: *const InternPool, ty: Index) bool {
return switch (ty) {
.usize_type,
.isize_type,
.c_char_type,
.c_short_type,
.c_ushort_type,
.c_int_type,
.c_uint_type,
.c_long_type,
.c_ulong_type,
.c_longlong_type,
.c_ulonglong_type,
.comptime_int_type,
=> true,
else => switch (ip.items.items(.tag)[@intFromEnum(ty)]) {
.type_int_signed,
.type_int_unsigned,
=> true,
else => false,
},
};
}
/// does not include .enum_literal_type
pub fn isEnumType(ip: *const InternPool, ty: Index) bool {
return switch (ty) {
.atomic_order_type,
.atomic_rmw_op_type,
.calling_convention_type,
.address_space_type,
.float_mode_type,
.reduce_op_type,
.call_modifier_type,
=> true,
else => ip.indexToKey(ty) == .enum_type,
};
}
pub fn isUnion(ip: *const InternPool, ty: Index) bool {
return ip.indexToKey(ty) == .union_type;
}
pub fn isFunctionType(ip: *const InternPool, ty: Index) bool {
return ip.indexToKey(ty) == .func_type;
}
pub fn isPointerType(ip: *const InternPool, ty: Index) bool {
return ip.indexToKey(ty) == .ptr_type;
}
pub fn isOptionalType(ip: *const InternPool, ty: Index) bool {
return ip.indexToKey(ty) == .opt_type;
}
/// includes .inferred_error_set_type
pub fn isErrorSetType(ip: *const InternPool, ty: Index) bool {
return switch (ty) {
.anyerror_type, .adhoc_inferred_error_set_type => true,
else => switch (ip.indexToKey(ty)) {
.error_set_type, .inferred_error_set_type => true,
else => false,
},
};
}
pub fn isInferredErrorSetType(ip: *const InternPool, ty: Index) bool {
return ty == .adhoc_inferred_error_set_type or ip.indexToKey(ty) == .inferred_error_set_type;
}
pub fn isErrorUnionType(ip: *const InternPool, ty: Index) bool {
return ip.indexToKey(ty) == .error_union_type;
}
pub fn isAggregateType(ip: *const InternPool, ty: Index) bool {
return switch (ip.indexToKey(ty)) {
.array_type, .vector_type, .anon_struct_type, .struct_type => true,
else => false,
};
}
pub fn errorUnionSet(ip: *const InternPool, ty: Index) Index {
return ip.indexToKey(ty).error_union_type.error_set_type;
}
pub fn errorUnionPayload(ip: *const InternPool, ty: Index) Index {
return ip.indexToKey(ty).error_union_type.payload_type;
}
/// The is only legal because the initializer is not part of the hash.
pub fn mutateVarInit(ip: *InternPool, index: Index, init_index: Index) void {
const item = ip.items.get(@intFromEnum(index));
assert(item.tag == .variable);
ip.extra.items[item.data + std.meta.fieldIndex(Tag.Variable, "init").?] = @intFromEnum(init_index);
}
pub fn dump(ip: *const InternPool) void {
dumpStatsFallible(ip, std.heap.page_allocator) catch return;
dumpAllFallible(ip) catch return;
}
fn dumpStatsFallible(ip: *const InternPool, arena: Allocator) anyerror!void {
const items_size = (1 + 4) * ip.items.len;
const extra_size = 4 * ip.extra.items.len;
const limbs_size = 8 * ip.limbs.items.len;
const decls_size = ip.allocated_decls.len * @sizeOf(Module.Decl);
// TODO: map overhead size is not taken into account
const total_size = @sizeOf(InternPool) + items_size + extra_size + limbs_size + decls_size;
std.debug.print(
\\InternPool size: {d} bytes
\\ {d} items: {d} bytes
\\ {d} extra: {d} bytes
\\ {d} limbs: {d} bytes
\\ {d} decls: {d} bytes
\\
, .{
total_size,
ip.items.len,
items_size,
ip.extra.items.len,
extra_size,
ip.limbs.items.len,
limbs_size,
ip.allocated_decls.len,
decls_size,
});
const tags = ip.items.items(.tag);
const datas = ip.items.items(.data);
const TagStats = struct {
count: usize = 0,
bytes: usize = 0,
};
var counts = std.AutoArrayHashMap(Tag, TagStats).init(arena);
for (tags, datas) |tag, data| {
const gop = try counts.getOrPut(tag);
if (!gop.found_existing) gop.value_ptr.* = .{};
gop.value_ptr.count += 1;
gop.value_ptr.bytes += 1 + 4 + @as(usize, switch (tag) {
// Note that in this case, we have technically leaked some extra data
// bytes which we do not account for here.
.removed => 0,
.type_int_signed => 0,
.type_int_unsigned => 0,
.type_array_small => @sizeOf(Vector),
.type_array_big => @sizeOf(Array),
.type_vector => @sizeOf(Vector),
.type_pointer => @sizeOf(Tag.TypePointer),
.type_slice => 0,
.type_optional => 0,
.type_anyframe => 0,
.type_error_union => @sizeOf(Key.ErrorUnionType),
.type_anyerror_union => 0,
.type_error_set => b: {
const info = ip.extraData(Tag.ErrorSet, data);
break :b @sizeOf(Tag.ErrorSet) + (@sizeOf(u32) * info.names_len);
},
.type_inferred_error_set => 0,
.type_enum_explicit, .type_enum_nonexhaustive => b: {
const info = ip.extraData(EnumExplicit, data);
var ints = @typeInfo(EnumExplicit).Struct.fields.len + info.captures_len + info.fields_len;
if (info.values_map != .none) ints += info.fields_len;
break :b @sizeOf(u32) * ints;
},
.type_enum_auto => b: {
const info = ip.extraData(EnumAuto, data);
const ints = @typeInfo(EnumAuto).Struct.fields.len + info.captures_len + info.fields_len;
break :b @sizeOf(u32) * ints;
},
.type_opaque => b: {
const info = ip.extraData(Tag.TypeOpaque, data);
const ints = @typeInfo(Tag.TypeOpaque).Struct.fields.len + info.captures_len;
break :b @sizeOf(u32) * ints;
},
.type_struct => b: {
if (data == 0) break :b 0;
const extra = ip.extraDataTrail(Tag.TypeStruct, data);
const info = extra.data;
var ints: usize = @typeInfo(Tag.TypeStruct).Struct.fields.len;
if (info.flags.any_captures) {
const captures_len = ip.extra.items[extra.end];
ints += 1 + captures_len;
}
ints += info.fields_len; // types
if (!info.flags.is_tuple) {
ints += 1; // names_map
ints += info.fields_len; // names
}
if (info.flags.any_default_inits)
ints += info.fields_len; // inits
ints += @intFromBool(info.flags.has_namespace); // namespace
if (info.flags.any_aligned_fields)
ints += (info.fields_len + 3) / 4; // aligns
if (info.flags.any_comptime_fields)
ints += (info.fields_len + 31) / 32; // comptime bits
if (!info.flags.is_extern)
ints += info.fields_len; // runtime order
ints += info.fields_len; // offsets
break :b @sizeOf(u32) * ints;
},
.type_struct_anon => b: {
const info = ip.extraData(TypeStructAnon, data);
break :b @sizeOf(TypeStructAnon) + (@sizeOf(u32) * 3 * info.fields_len);
},
.type_struct_packed => b: {
const extra = ip.extraDataTrail(Tag.TypeStructPacked, data);
const captures_len = if (extra.data.flags.any_captures)
ip.extra.items[extra.end]
else
0;
break :b @sizeOf(u32) * (@typeInfo(Tag.TypeStructPacked).Struct.fields.len +
@intFromBool(extra.data.flags.any_captures) + captures_len +
extra.data.fields_len * 2);
},
.type_struct_packed_inits => b: {
const extra = ip.extraDataTrail(Tag.TypeStructPacked, data);
const captures_len = if (extra.data.flags.any_captures)
ip.extra.items[extra.end]
else
0;
break :b @sizeOf(u32) * (@typeInfo(Tag.TypeStructPacked).Struct.fields.len +
@intFromBool(extra.data.flags.any_captures) + captures_len +
extra.data.fields_len * 3);
},
.type_tuple_anon => b: {
const info = ip.extraData(TypeStructAnon, data);
break :b @sizeOf(TypeStructAnon) + (@sizeOf(u32) * 2 * info.fields_len);
},
.type_union => b: {
const extra = ip.extraDataTrail(Tag.TypeUnion, data);
const captures_len = if (extra.data.flags.any_captures)
ip.extra.items[extra.end]
else
0;
const per_field = @sizeOf(u32); // field type
// 1 byte per field for alignment, rounded up to the nearest 4 bytes
const alignments = if (extra.data.flags.any_aligned_fields)
((extra.data.fields_len + 3) / 4) * 4
else
0;
break :b @sizeOf(Tag.TypeUnion) +
4 * (@intFromBool(extra.data.flags.any_captures) + captures_len) +
(extra.data.fields_len * per_field) + alignments;
},
.type_function => b: {
const info = ip.extraData(Tag.TypeFunction, data);
break :b @sizeOf(Tag.TypeFunction) +
(@sizeOf(Index) * info.params_len) +
(@as(u32, 4) * @intFromBool(info.flags.has_comptime_bits)) +
(@as(u32, 4) * @intFromBool(info.flags.has_noalias_bits));
},
.undef => 0,
.simple_type => 0,
.simple_value => 0,
.ptr_decl => @sizeOf(PtrDecl),
.ptr_comptime_alloc => @sizeOf(PtrComptimeAlloc),
.ptr_anon_decl => @sizeOf(PtrAnonDecl),
.ptr_anon_decl_aligned => @sizeOf(PtrAnonDeclAligned),
.ptr_comptime_field => @sizeOf(PtrComptimeField),
.ptr_int => @sizeOf(PtrBase),
.ptr_eu_payload => @sizeOf(PtrBase),
.ptr_opt_payload => @sizeOf(PtrBase),
.ptr_elem => @sizeOf(PtrBaseIndex),
.ptr_field => @sizeOf(PtrBaseIndex),
.ptr_slice => @sizeOf(PtrSlice),
.opt_null => 0,
.opt_payload => @sizeOf(Tag.TypeValue),
.int_u8 => 0,
.int_u16 => 0,
.int_u32 => 0,
.int_i32 => 0,
.int_usize => 0,
.int_comptime_int_u32 => 0,
.int_comptime_int_i32 => 0,
.int_small => @sizeOf(IntSmall),
.int_positive,
.int_negative,
=> b: {
const int = ip.limbData(Int, data);
break :b @sizeOf(Int) + int.limbs_len * 8;
},
.int_lazy_align, .int_lazy_size => @sizeOf(IntLazy),
.error_set_error, .error_union_error => @sizeOf(Key.Error),
.error_union_payload => @sizeOf(Tag.TypeValue),
.enum_literal => 0,
.enum_tag => @sizeOf(Tag.EnumTag),
.bytes => b: {
const info = ip.extraData(Bytes, data);
const len = @as(u32, @intCast(ip.aggregateTypeLenIncludingSentinel(info.ty)));
break :b @sizeOf(Bytes) + len +
@intFromBool(ip.string_bytes.items[@intFromEnum(info.bytes) + len - 1] != 0);
},
.aggregate => b: {
const info = ip.extraData(Tag.Aggregate, data);
const fields_len: u32 = @intCast(ip.aggregateTypeLenIncludingSentinel(info.ty));
break :b @sizeOf(Tag.Aggregate) + (@sizeOf(Index) * fields_len);
},
.repeated => @sizeOf(Repeated),
.float_f16 => 0,
.float_f32 => 0,
.float_f64 => @sizeOf(Float64),
.float_f80 => @sizeOf(Float80),
.float_f128 => @sizeOf(Float128),
.float_c_longdouble_f80 => @sizeOf(Float80),
.float_c_longdouble_f128 => @sizeOf(Float128),
.float_comptime_float => @sizeOf(Float128),
.variable => @sizeOf(Tag.Variable),
.extern_func => @sizeOf(Tag.ExternFunc),
.func_decl => @sizeOf(Tag.FuncDecl),
.func_instance => b: {
const info = ip.extraData(Tag.FuncInstance, data);
const ty = ip.typeOf(info.generic_owner);
const params_len = ip.indexToKey(ty).func_type.param_types.len;
break :b @sizeOf(Tag.FuncInstance) + @sizeOf(Index) * params_len;
},
.func_coerced => @sizeOf(Tag.FuncCoerced),
.only_possible_value => 0,
.union_value => @sizeOf(Key.Union),
.memoized_call => b: {
const info = ip.extraData(MemoizedCall, data);
break :b @sizeOf(MemoizedCall) + (@sizeOf(Index) * info.args_len);
},
});
}
const SortContext = struct {
map: *std.AutoArrayHashMap(Tag, TagStats),
pub fn lessThan(ctx: @This(), a_index: usize, b_index: usize) bool {
const values = ctx.map.values();
return values[a_index].bytes > values[b_index].bytes;
//return values[a_index].count > values[b_index].count;
}
};
counts.sort(SortContext{ .map = &counts });
const len = @min(50, counts.count());
std.debug.print(" top 50 tags:\n", .{});
for (counts.keys()[0..len], counts.values()[0..len]) |tag, stats| {
std.debug.print(" {s}: {d} occurrences, {d} total bytes\n", .{
@tagName(tag), stats.count, stats.bytes,
});
}
}
fn dumpAllFallible(ip: *const InternPool) anyerror!void {
const tags = ip.items.items(.tag);
const datas = ip.items.items(.data);
var bw = std.io.bufferedWriter(std.io.getStdErr().writer());
const w = bw.writer();
for (tags, datas, 0..) |tag, data, i| {
try w.print("${d} = {s}(", .{ i, @tagName(tag) });
switch (tag) {
.removed => {},
.simple_type => try w.print("{s}", .{@tagName(@as(SimpleType, @enumFromInt(data)))}),
.simple_value => try w.print("{s}", .{@tagName(@as(SimpleValue, @enumFromInt(data)))}),
.type_int_signed,
.type_int_unsigned,
.type_array_small,
.type_array_big,
.type_vector,
.type_pointer,
.type_optional,
.type_anyframe,
.type_error_union,
.type_anyerror_union,
.type_error_set,
.type_inferred_error_set,
.type_enum_explicit,
.type_enum_nonexhaustive,
.type_enum_auto,
.type_opaque,
.type_struct,
.type_struct_anon,
.type_struct_packed,
.type_struct_packed_inits,
.type_tuple_anon,
.type_union,
.type_function,
.undef,
.ptr_decl,
.ptr_comptime_alloc,
.ptr_anon_decl,
.ptr_anon_decl_aligned,
.ptr_comptime_field,
.ptr_int,
.ptr_eu_payload,
.ptr_opt_payload,
.ptr_elem,
.ptr_field,
.ptr_slice,
.opt_payload,
.int_u8,
.int_u16,
.int_u32,
.int_i32,
.int_usize,
.int_comptime_int_u32,
.int_comptime_int_i32,
.int_small,
.int_positive,
.int_negative,
.int_lazy_align,
.int_lazy_size,
.error_set_error,
.error_union_error,
.error_union_payload,
.enum_literal,
.enum_tag,
.bytes,
.aggregate,
.repeated,
.float_f16,
.float_f32,
.float_f64,
.float_f80,
.float_f128,
.float_c_longdouble_f80,
.float_c_longdouble_f128,
.float_comptime_float,
.variable,
.extern_func,
.func_decl,
.func_instance,
.func_coerced,
.union_value,
.memoized_call,
=> try w.print("{d}", .{data}),
.opt_null,
.type_slice,
.only_possible_value,
=> try w.print("${d}", .{data}),
}
try w.writeAll(")\n");
}
try bw.flush();
}
pub fn dumpGenericInstances(ip: *const InternPool, allocator: Allocator) void {
ip.dumpGenericInstancesFallible(allocator) catch return;
}
pub fn dumpGenericInstancesFallible(ip: *const InternPool, allocator: Allocator) anyerror!void {
var arena_allocator = std.heap.ArenaAllocator.init(allocator);
defer arena_allocator.deinit();
const arena = arena_allocator.allocator();
var bw = std.io.bufferedWriter(std.io.getStdErr().writer());
const w = bw.writer();
var instances: std.AutoArrayHashMapUnmanaged(Index, std.ArrayListUnmanaged(Index)) = .{};
const datas = ip.items.items(.data);
for (ip.items.items(.tag), 0..) |tag, i| {
if (tag != .func_instance) continue;
const info = ip.extraData(Tag.FuncInstance, datas[i]);
const gop = try instances.getOrPut(arena, info.generic_owner);
if (!gop.found_existing) gop.value_ptr.* = .{};
try gop.value_ptr.append(arena, @enumFromInt(i));
}
const SortContext = struct {
values: []std.ArrayListUnmanaged(Index),
pub fn lessThan(ctx: @This(), a_index: usize, b_index: usize) bool {
return ctx.values[a_index].items.len > ctx.values[b_index].items.len;
}
};
instances.sort(SortContext{ .values = instances.values() });
var it = instances.iterator();
while (it.next()) |entry| {
const generic_fn_owner_decl = ip.declPtrConst(ip.funcDeclOwner(entry.key_ptr.*));
try w.print("{} ({}): \n", .{ generic_fn_owner_decl.name.fmt(ip), entry.value_ptr.items.len });
for (entry.value_ptr.items) |index| {
const func = ip.extraFuncInstance(datas[@intFromEnum(index)]);
const owner_decl = ip.declPtrConst(func.owner_decl);
try w.print(" {}: (", .{owner_decl.name.fmt(ip)});
for (func.comptime_args.get(ip)) |arg| {
if (arg != .none) {
const key = ip.indexToKey(arg);
try w.print(" {} ", .{key});
}
}
try w.writeAll(")\n");
}
}
try bw.flush();
}
pub fn declPtr(ip: *InternPool, index: DeclIndex) *Module.Decl {
return ip.allocated_decls.at(@intFromEnum(index));
}
pub fn declPtrConst(ip: *const InternPool, index: DeclIndex) *const Module.Decl {
return ip.allocated_decls.at(@intFromEnum(index));
}
pub fn namespacePtr(ip: *InternPool, index: NamespaceIndex) *Module.Namespace {
return ip.allocated_namespaces.at(@intFromEnum(index));
}
pub fn createDecl(
ip: *InternPool,
gpa: Allocator,
initialization: Module.Decl,
) Allocator.Error!DeclIndex {
if (ip.decls_free_list.popOrNull()) |index| {
ip.allocated_decls.at(@intFromEnum(index)).* = initialization;
return index;
}
const ptr = try ip.allocated_decls.addOne(gpa);
ptr.* = initialization;
return @enumFromInt(ip.allocated_decls.len - 1);
}
pub fn destroyDecl(ip: *InternPool, gpa: Allocator, index: DeclIndex) void {
ip.declPtr(index).* = undefined;
ip.decls_free_list.append(gpa, index) catch {
// In order to keep `destroyDecl` a non-fallible function, we ignore memory
// allocation failures here, instead leaking the Decl until garbage collection.
};
}
pub fn createNamespace(
ip: *InternPool,
gpa: Allocator,
initialization: Module.Namespace,
) Allocator.Error!NamespaceIndex {
if (ip.namespaces_free_list.popOrNull()) |index| {
ip.allocated_namespaces.at(@intFromEnum(index)).* = initialization;
return index;
}
const ptr = try ip.allocated_namespaces.addOne(gpa);
ptr.* = initialization;
return @enumFromInt(ip.allocated_namespaces.len - 1);
}
pub fn destroyNamespace(ip: *InternPool, gpa: Allocator, index: NamespaceIndex) void {
ip.namespacePtr(index).* = .{
.parent = undefined,
.file_scope = undefined,
.decl_index = undefined,
};
ip.namespaces_free_list.append(gpa, index) catch {
// In order to keep `destroyNamespace` a non-fallible function, we ignore memory
// allocation failures here, instead leaking the Namespace until garbage collection.
};
}
pub fn getOrPutString(
ip: *InternPool,
gpa: Allocator,
s: []const u8,
) Allocator.Error!NullTerminatedString {
try ip.string_bytes.ensureUnusedCapacity(gpa, s.len + 1);
ip.string_bytes.appendSliceAssumeCapacity(s);
ip.string_bytes.appendAssumeCapacity(0);
return ip.getOrPutTrailingString(gpa, s.len + 1);
}
pub fn getOrPutStringFmt(
ip: *InternPool,
gpa: Allocator,
comptime format: []const u8,
args: anytype,
) Allocator.Error!NullTerminatedString {
// ensure that references to string_bytes in args do not get invalidated
const len: usize = @intCast(std.fmt.count(format, args) + 1);
try ip.string_bytes.ensureUnusedCapacity(gpa, len);
ip.string_bytes.writer(undefined).print(format, args) catch unreachable;
ip.string_bytes.appendAssumeCapacity(0);
return ip.getOrPutTrailingString(gpa, len);
}
pub fn getOrPutStringOpt(
ip: *InternPool,
gpa: Allocator,
optional_string: ?[]const u8,
) Allocator.Error!OptionalNullTerminatedString {
const s = optional_string orelse return .none;
const interned = try getOrPutString(ip, gpa, s);
return interned.toOptional();
}
/// Uses the last len bytes of ip.string_bytes as the key.
pub fn getOrPutTrailingString(
ip: *InternPool,
gpa: Allocator,
len: usize,
) Allocator.Error!NullTerminatedString {
const string_bytes = &ip.string_bytes;
const str_index: u32 = @intCast(string_bytes.items.len - len);
if (len > 0 and string_bytes.getLast() == 0) {
_ = string_bytes.pop();
} else {
try string_bytes.ensureUnusedCapacity(gpa, 1);
}
const key: []const u8 = string_bytes.items[str_index..];
const gop = try ip.string_table.getOrPutContextAdapted(gpa, key, std.hash_map.StringIndexAdapter{
.bytes = string_bytes,
}, std.hash_map.StringIndexContext{
.bytes = string_bytes,
});
if (gop.found_existing) {
string_bytes.shrinkRetainingCapacity(str_index);
return @enumFromInt(gop.key_ptr.*);
} else {
gop.key_ptr.* = str_index;
string_bytes.appendAssumeCapacity(0);
return @enumFromInt(str_index);
}
}
/// Uses the last len bytes of ip.string_bytes as the key.
pub fn getTrailingAggregate(
ip: *InternPool,
gpa: Allocator,
ty: Index,
len: usize,
) Allocator.Error!Index {
try ip.items.ensureUnusedCapacity(gpa, 1);
try ip.extra.ensureUnusedCapacity(gpa, @typeInfo(Bytes).Struct.fields.len);
const str: String = @enumFromInt(ip.string_bytes.items.len - len);
const adapter: KeyAdapter = .{ .intern_pool = ip };
const gop = try ip.map.getOrPutAdapted(gpa, Key{ .aggregate = .{
.ty = ty,
.storage = .{ .bytes = ip.string_bytes.items[@intFromEnum(str)..] },
} }, adapter);
if (gop.found_existing) return @enumFromInt(gop.index);
ip.items.appendAssumeCapacity(.{
.tag = .bytes,
.data = ip.addExtraAssumeCapacity(Bytes{
.ty = ty,
.bytes = str,
}),
});
return @enumFromInt(ip.items.len - 1);
}
pub fn getString(ip: *InternPool, s: []const u8) OptionalNullTerminatedString {
if (ip.string_table.getKeyAdapted(s, std.hash_map.StringIndexAdapter{
.bytes = &ip.string_bytes,
})) |index| {
return @as(NullTerminatedString, @enumFromInt(index)).toOptional();
} else {
return .none;
}
}
pub fn stringToSlice(ip: *const InternPool, s: NullTerminatedString) [:0]const u8 {
const string_bytes = ip.string_bytes.items;
const start = @intFromEnum(s);
var end: usize = start;
while (string_bytes[end] != 0) end += 1;
return string_bytes[start..end :0];
}
pub fn stringToSliceUnwrap(ip: *const InternPool, s: OptionalNullTerminatedString) ?[:0]const u8 {
return ip.stringToSlice(s.unwrap() orelse return null);
}
pub fn stringEqlSlice(ip: *const InternPool, a: NullTerminatedString, b: []const u8) bool {
return std.mem.eql(u8, stringToSlice(ip, a), b);
}
pub fn typeOf(ip: *const InternPool, index: Index) Index {
// This optimization of static keys is required so that typeOf can be called
// on static keys that haven't been added yet during static key initialization.
// An alternative would be to topological sort the static keys, but this would
// mean that the range of type indices would not be dense.
return switch (index) {
.u0_type,
.i0_type,
.u1_type,
.u8_type,
.i8_type,
.u16_type,
.i16_type,
.u29_type,
.u32_type,
.i32_type,
.u64_type,
.i64_type,
.u80_type,
.u128_type,
.i128_type,
.usize_type,
.isize_type,
.c_char_type,
.c_short_type,
.c_ushort_type,
.c_int_type,
.c_uint_type,
.c_long_type,
.c_ulong_type,
.c_longlong_type,
.c_ulonglong_type,
.c_longdouble_type,
.f16_type,
.f32_type,
.f64_type,
.f80_type,
.f128_type,
.anyopaque_type,
.bool_type,
.void_type,
.type_type,
.anyerror_type,
.comptime_int_type,
.comptime_float_type,
.noreturn_type,
.anyframe_type,
.null_type,
.undefined_type,
.enum_literal_type,
.atomic_order_type,
.atomic_rmw_op_type,
.calling_convention_type,
.address_space_type,
.float_mode_type,
.reduce_op_type,
.call_modifier_type,
.prefetch_options_type,
.export_options_type,
.extern_options_type,
.type_info_type,
.manyptr_u8_type,
.manyptr_const_u8_type,
.manyptr_const_u8_sentinel_0_type,
.single_const_pointer_to_comptime_int_type,
.slice_const_u8_type,
.slice_const_u8_sentinel_0_type,
.optional_noreturn_type,
.anyerror_void_error_union_type,
.adhoc_inferred_error_set_type,
.generic_poison_type,
.empty_struct_type,
=> .type_type,
.undef => .undefined_type,
.zero, .one, .negative_one => .comptime_int_type,
.zero_usize, .one_usize => .usize_type,
.zero_u8, .one_u8, .four_u8 => .u8_type,
.calling_convention_c, .calling_convention_inline => .calling_convention_type,
.void_value => .void_type,
.unreachable_value => .noreturn_type,
.null_value => .null_type,
.bool_true, .bool_false => .bool_type,
.empty_struct => .empty_struct_type,
.generic_poison => .generic_poison_type,
// This optimization on tags is needed so that indexToKey can call
// typeOf without being recursive.
_ => switch (ip.items.items(.tag)[@intFromEnum(index)]) {
.removed => unreachable,
.type_int_signed,
.type_int_unsigned,
.type_array_big,
.type_array_small,
.type_vector,
.type_pointer,
.type_slice,
.type_optional,
.type_anyframe,
.type_error_union,
.type_anyerror_union,
.type_error_set,
.type_inferred_error_set,
.type_enum_auto,
.type_enum_explicit,
.type_enum_nonexhaustive,
.simple_type,
.type_opaque,
.type_struct,
.type_struct_anon,
.type_struct_packed,
.type_struct_packed_inits,
.type_tuple_anon,
.type_union,
.type_function,
=> .type_type,
.undef,
.opt_null,
.only_possible_value,
=> @enumFromInt(ip.items.items(.data)[@intFromEnum(index)]),
.simple_value => unreachable, // handled via Index above
inline .ptr_decl,
.ptr_comptime_alloc,
.ptr_anon_decl,
.ptr_anon_decl_aligned,
.ptr_comptime_field,
.ptr_int,
.ptr_eu_payload,
.ptr_opt_payload,
.ptr_elem,
.ptr_field,
.ptr_slice,
.opt_payload,
.error_union_payload,
.int_small,
.int_lazy_align,
.int_lazy_size,
.error_set_error,
.error_union_error,
.enum_tag,
.variable,
.extern_func,
.func_decl,
.func_instance,
.func_coerced,
.union_value,
.bytes,
.aggregate,
.repeated,
=> |t| {
const extra_index = ip.items.items(.data)[@intFromEnum(index)];
const field_index = std.meta.fieldIndex(t.Payload(), "ty").?;
return @enumFromInt(ip.extra.items[extra_index + field_index]);
},
.int_u8 => .u8_type,
.int_u16 => .u16_type,
.int_u32 => .u32_type,
.int_i32 => .i32_type,
.int_usize => .usize_type,
.int_comptime_int_u32,
.int_comptime_int_i32,
=> .comptime_int_type,
// Note these are stored in limbs data, not extra data.
.int_positive,
.int_negative,
=> ip.limbData(Int, ip.items.items(.data)[@intFromEnum(index)]).ty,
.enum_literal => .enum_literal_type,
.float_f16 => .f16_type,
.float_f32 => .f32_type,
.float_f64 => .f64_type,
.float_f80 => .f80_type,
.float_f128 => .f128_type,
.float_c_longdouble_f80,
.float_c_longdouble_f128,
=> .c_longdouble_type,
.float_comptime_float => .comptime_float_type,
.memoized_call => unreachable,
},
.var_args_param_type => unreachable,
.none => unreachable,
};
}
/// Assumes that the enum's field indexes equal its value tags.
pub fn toEnum(ip: *const InternPool, comptime E: type, i: Index) E {
const int = ip.indexToKey(i).enum_tag.int;
return @enumFromInt(ip.indexToKey(int).int.storage.u64);
}
pub fn aggregateTypeLen(ip: *const InternPool, ty: Index) u64 {
return switch (ip.indexToKey(ty)) {
.struct_type => ip.loadStructType(ty).field_types.len,
.anon_struct_type => |anon_struct_type| anon_struct_type.types.len,
.array_type => |array_type| array_type.len,
.vector_type => |vector_type| vector_type.len,
else => unreachable,
};
}
pub fn aggregateTypeLenIncludingSentinel(ip: *const InternPool, ty: Index) u64 {
return switch (ip.indexToKey(ty)) {
.struct_type => ip.loadStructType(ty).field_types.len,
.anon_struct_type => |anon_struct_type| anon_struct_type.types.len,
.array_type => |array_type| array_type.len + @intFromBool(array_type.sentinel != .none),
.vector_type => |vector_type| vector_type.len,
else => unreachable,
};
}
pub fn funcTypeReturnType(ip: *const InternPool, ty: Index) Index {
const item = ip.items.get(@intFromEnum(ty));
const child_item = switch (item.tag) {
.type_pointer => ip.items.get(ip.extra.items[
item.data + std.meta.fieldIndex(Tag.TypePointer, "child").?
]),
.type_function => item,
else => unreachable,
};
assert(child_item.tag == .type_function);
return @enumFromInt(ip.extra.items[
child_item.data + std.meta.fieldIndex(Tag.TypeFunction, "return_type").?
]);
}
pub fn isNoReturn(ip: *const InternPool, ty: Index) bool {
return switch (ty) {
.noreturn_type => true,
else => switch (ip.items.items(.tag)[@intFromEnum(ty)]) {
.type_error_set => ip.extra.items[ip.items.items(.data)[@intFromEnum(ty)] + std.meta.fieldIndex(Tag.ErrorSet, "names_len").?] == 0,
else => false,
},
};
}
pub fn isUndef(ip: *const InternPool, val: Index) bool {
return val == .undef or ip.items.items(.tag)[@intFromEnum(val)] == .undef;
}
pub fn isVariable(ip: *const InternPool, val: Index) bool {
return ip.items.items(.tag)[@intFromEnum(val)] == .variable;
}
pub fn getBackingDecl(ip: *const InternPool, val: Index) OptionalDeclIndex {
var base = @intFromEnum(val);
while (true) {
switch (ip.items.items(.tag)[base]) {
.ptr_decl => return @enumFromInt(ip.extra.items[
ip.items.items(.data)[base] + std.meta.fieldIndex(PtrDecl, "decl").?
]),
inline .ptr_eu_payload,
.ptr_opt_payload,
.ptr_elem,
.ptr_field,
=> |tag| base = ip.extra.items[
ip.items.items(.data)[base] + std.meta.fieldIndex(tag.Payload(), "base").?
],
.ptr_slice => base = ip.extra.items[
ip.items.items(.data)[base] + std.meta.fieldIndex(PtrSlice, "ptr").?
],
else => return .none,
}
}
}
pub fn getBackingAddrTag(ip: *const InternPool, val: Index) ?Key.Ptr.Addr.Tag {
var base = @intFromEnum(val);
while (true) {
switch (ip.items.items(.tag)[base]) {
.ptr_decl => return .decl,
.ptr_comptime_alloc => return .comptime_alloc,
.ptr_anon_decl, .ptr_anon_decl_aligned => return .anon_decl,
.ptr_comptime_field => return .comptime_field,
.ptr_int => return .int,
inline .ptr_eu_payload,
.ptr_opt_payload,
.ptr_elem,
.ptr_field,
=> |tag| base = ip.extra.items[
ip.items.items(.data)[base] + std.meta.fieldIndex(tag.Payload(), "base").?
],
inline .ptr_slice => |tag| base = ip.extra.items[
ip.items.items(.data)[base] + std.meta.fieldIndex(tag.Payload(), "ptr").?
],
else => return null,
}
}
}
/// This is a particularly hot function, so we operate directly on encodings
/// rather than the more straightforward implementation of calling `indexToKey`.
pub fn zigTypeTagOrPoison(ip: *const InternPool, index: Index) error{GenericPoison}!std.builtin.TypeId {
return switch (index) {
.u0_type,
.i0_type,
.u1_type,
.u8_type,
.i8_type,
.u16_type,
.i16_type,
.u29_type,
.u32_type,
.i32_type,
.u64_type,
.i64_type,
.u80_type,
.u128_type,
.i128_type,
.usize_type,
.isize_type,
.c_char_type,
.c_short_type,
.c_ushort_type,
.c_int_type,
.c_uint_type,
.c_long_type,
.c_ulong_type,
.c_longlong_type,
.c_ulonglong_type,
=> .Int,
.c_longdouble_type,
.f16_type,
.f32_type,
.f64_type,
.f80_type,
.f128_type,
=> .Float,
.anyopaque_type => .Opaque,
.bool_type => .Bool,
.void_type => .Void,
.type_type => .Type,
.anyerror_type, .adhoc_inferred_error_set_type => .ErrorSet,
.comptime_int_type => .ComptimeInt,
.comptime_float_type => .ComptimeFloat,
.noreturn_type => .NoReturn,
.anyframe_type => .AnyFrame,
.null_type => .Null,
.undefined_type => .Undefined,
.enum_literal_type => .EnumLiteral,
.atomic_order_type,
.atomic_rmw_op_type,
.calling_convention_type,
.address_space_type,
.float_mode_type,
.reduce_op_type,
.call_modifier_type,
=> .Enum,
.prefetch_options_type,
.export_options_type,
.extern_options_type,
=> .Struct,
.type_info_type => .Union,
.manyptr_u8_type,
.manyptr_const_u8_type,
.manyptr_const_u8_sentinel_0_type,
.single_const_pointer_to_comptime_int_type,
.slice_const_u8_type,
.slice_const_u8_sentinel_0_type,
=> .Pointer,
.optional_noreturn_type => .Optional,
.anyerror_void_error_union_type => .ErrorUnion,
.empty_struct_type => .Struct,
.generic_poison_type => return error.GenericPoison,
// values, not types
.undef => unreachable,
.zero => unreachable,
.zero_usize => unreachable,
.zero_u8 => unreachable,
.one => unreachable,
.one_usize => unreachable,
.one_u8 => unreachable,
.four_u8 => unreachable,
.negative_one => unreachable,
.calling_convention_c => unreachable,
.calling_convention_inline => unreachable,
.void_value => unreachable,
.unreachable_value => unreachable,
.null_value => unreachable,
.bool_true => unreachable,
.bool_false => unreachable,
.empty_struct => unreachable,
.generic_poison => unreachable,
.var_args_param_type => unreachable, // special tag
_ => switch (ip.items.items(.tag)[@intFromEnum(index)]) {
.removed => unreachable,
.type_int_signed,
.type_int_unsigned,
=> .Int,
.type_array_big,
.type_array_small,
=> .Array,
.type_vector => .Vector,
.type_pointer,
.type_slice,
=> .Pointer,
.type_optional => .Optional,
.type_anyframe => .AnyFrame,
.type_error_union,
.type_anyerror_union,
=> .ErrorUnion,
.type_error_set,
.type_inferred_error_set,
=> .ErrorSet,
.type_enum_auto,
.type_enum_explicit,
.type_enum_nonexhaustive,
=> .Enum,
.simple_type => unreachable, // handled via Index tag above
.type_opaque => .Opaque,
.type_struct,
.type_struct_anon,
.type_struct_packed,
.type_struct_packed_inits,
.type_tuple_anon,
=> .Struct,
.type_union => .Union,
.type_function => .Fn,
// values, not types
.undef,
.simple_value,
.ptr_decl,
.ptr_comptime_alloc,
.ptr_anon_decl,
.ptr_anon_decl_aligned,
.ptr_comptime_field,
.ptr_int,
.ptr_eu_payload,
.ptr_opt_payload,
.ptr_elem,
.ptr_field,
.ptr_slice,
.opt_payload,
.opt_null,
.int_u8,
.int_u16,
.int_u32,
.int_i32,
.int_usize,
.int_comptime_int_u32,
.int_comptime_int_i32,
.int_small,
.int_positive,
.int_negative,
.int_lazy_align,
.int_lazy_size,
.error_set_error,
.error_union_error,
.error_union_payload,
.enum_literal,
.enum_tag,
.float_f16,
.float_f32,
.float_f64,
.float_f80,
.float_f128,
.float_c_longdouble_f80,
.float_c_longdouble_f128,
.float_comptime_float,
.variable,
.extern_func,
.func_decl,
.func_instance,
.func_coerced,
.only_possible_value,
.union_value,
.bytes,
.aggregate,
.repeated,
// memoization, not types
.memoized_call,
=> unreachable,
},
.none => unreachable, // special tag
};
}
pub fn isFuncBody(ip: *const InternPool, i: Index) bool {
assert(i != .none);
return switch (ip.items.items(.tag)[@intFromEnum(i)]) {
.func_decl, .func_instance, .func_coerced => true,
else => false,
};
}
pub fn funcAnalysis(ip: *const InternPool, i: Index) *FuncAnalysis {
assert(i != .none);
const item = ip.items.get(@intFromEnum(i));
const extra_index = switch (item.tag) {
.func_decl => item.data + std.meta.fieldIndex(Tag.FuncDecl, "analysis").?,
.func_instance => item.data + std.meta.fieldIndex(Tag.FuncInstance, "analysis").?,
.func_coerced => i: {
const extra_index = item.data + std.meta.fieldIndex(Tag.FuncCoerced, "func").?;
const func_index: Index = @enumFromInt(ip.extra.items[extra_index]);
const sub_item = ip.items.get(@intFromEnum(func_index));
break :i switch (sub_item.tag) {
.func_decl => sub_item.data + std.meta.fieldIndex(Tag.FuncDecl, "analysis").?,
.func_instance => sub_item.data + std.meta.fieldIndex(Tag.FuncInstance, "analysis").?,
else => unreachable,
};
},
else => unreachable,
};
return @ptrCast(&ip.extra.items[extra_index]);
}
pub fn funcHasInferredErrorSet(ip: *const InternPool, i: Index) bool {
return funcAnalysis(ip, i).inferred_error_set;
}
pub fn funcZirBodyInst(ip: *const InternPool, i: Index) TrackedInst.Index {
assert(i != .none);
const item = ip.items.get(@intFromEnum(i));
const zir_body_inst_field_index = std.meta.fieldIndex(Tag.FuncDecl, "zir_body_inst").?;
const extra_index = switch (item.tag) {
.func_decl => item.data + zir_body_inst_field_index,
.func_instance => b: {
const generic_owner_field_index = std.meta.fieldIndex(Tag.FuncInstance, "generic_owner").?;
const func_decl_index = ip.extra.items[item.data + generic_owner_field_index];
assert(ip.items.items(.tag)[func_decl_index] == .func_decl);
break :b ip.items.items(.data)[func_decl_index] + zir_body_inst_field_index;
},
.func_coerced => {
const datas = ip.items.items(.data);
const uncoerced_func_index: Index = @enumFromInt(ip.extra.items[
datas[@intFromEnum(i)] + std.meta.fieldIndex(Tag.FuncCoerced, "func").?
]);
return ip.funcZirBodyInst(uncoerced_func_index);
},
else => unreachable,
};
return @enumFromInt(ip.extra.items[extra_index]);
}
pub fn iesFuncIndex(ip: *const InternPool, ies_index: Index) Index {
assert(ies_index != .none);
const tags = ip.items.items(.tag);
assert(tags[@intFromEnum(ies_index)] == .type_inferred_error_set);
const func_index = ip.items.items(.data)[@intFromEnum(ies_index)];
switch (tags[func_index]) {
.func_decl, .func_instance => {},
else => unreachable, // assertion failed
}
return @enumFromInt(func_index);
}
/// Returns a mutable pointer to the resolved error set type of an inferred
/// error set function. The returned pointer is invalidated when anything is
/// added to `ip`.
pub fn iesResolved(ip: *const InternPool, ies_index: Index) *Index {
assert(ies_index != .none);
const tags = ip.items.items(.tag);
const datas = ip.items.items(.data);
assert(tags[@intFromEnum(ies_index)] == .type_inferred_error_set);
const func_index = datas[@intFromEnum(ies_index)];
return funcIesResolved(ip, func_index);
}
/// Returns a mutable pointer to the resolved error set type of an inferred
/// error set function. The returned pointer is invalidated when anything is
/// added to `ip`.
pub fn funcIesResolved(ip: *const InternPool, func_index: Index) *Index {
const tags = ip.items.items(.tag);
const datas = ip.items.items(.data);
assert(funcHasInferredErrorSet(ip, func_index));
const func_start = datas[@intFromEnum(func_index)];
const extra_index = switch (tags[@intFromEnum(func_index)]) {
.func_decl => func_start + @typeInfo(Tag.FuncDecl).Struct.fields.len,
.func_instance => func_start + @typeInfo(Tag.FuncInstance).Struct.fields.len,
.func_coerced => i: {
const uncoerced_func_index: Index = @enumFromInt(ip.extra.items[
func_start + std.meta.fieldIndex(Tag.FuncCoerced, "func").?
]);
const uncoerced_func_start = datas[@intFromEnum(uncoerced_func_index)];
break :i switch (tags[@intFromEnum(uncoerced_func_index)]) {
.func_decl => uncoerced_func_start + @typeInfo(Tag.FuncDecl).Struct.fields.len,
.func_instance => uncoerced_func_start + @typeInfo(Tag.FuncInstance).Struct.fields.len,
else => unreachable,
};
},
else => unreachable,
};
return @ptrCast(&ip.extra.items[extra_index]);
}
pub fn funcDeclInfo(ip: *const InternPool, i: Index) Key.Func {
const tags = ip.items.items(.tag);
const datas = ip.items.items(.data);
assert(tags[@intFromEnum(i)] == .func_decl);
return extraFuncDecl(ip, datas[@intFromEnum(i)]);
}
pub fn funcDeclOwner(ip: *const InternPool, i: Index) DeclIndex {
return funcDeclInfo(ip, i).owner_decl;
}
pub fn funcTypeParamsLen(ip: *const InternPool, i: Index) u32 {
const tags = ip.items.items(.tag);
const datas = ip.items.items(.data);
assert(tags[@intFromEnum(i)] == .type_function);
const start = datas[@intFromEnum(i)];
return ip.extra.items[start + std.meta.fieldIndex(Tag.TypeFunction, "params_len").?];
}
pub fn unwrapCoercedFunc(ip: *const InternPool, i: Index) Index {
const tags = ip.items.items(.tag);
return switch (tags[@intFromEnum(i)]) {
.func_coerced => {
const datas = ip.items.items(.data);
return @enumFromInt(ip.extra.items[
datas[@intFromEnum(i)] + std.meta.fieldIndex(Tag.FuncCoerced, "func").?
]);
},
.func_instance, .func_decl => i,
else => unreachable,
};
}
/// Having resolved a builtin type to a real struct/union/enum (which is now at `resolverd_index`),
/// make `want_index` refer to this type instead. This invalidates `resolved_index`, so must be
/// called only when it is guaranteed that no reference to `resolved_index` exists.
pub fn resolveBuiltinType(ip: *InternPool, want_index: Index, resolved_index: Index) void {
assert(@intFromEnum(want_index) >= @intFromEnum(Index.first_type));
assert(@intFromEnum(want_index) <= @intFromEnum(Index.last_type));
// Make sure the type isn't already resolved!
assert(ip.indexToKey(want_index) == .simple_type);
// Make sure it's the same kind of type
assert((ip.zigTypeTagOrPoison(want_index) catch unreachable) ==
(ip.zigTypeTagOrPoison(resolved_index) catch unreachable));
// Copy the data
const item = ip.items.get(@intFromEnum(resolved_index));
ip.items.set(@intFromEnum(want_index), item);
if (std.debug.runtime_safety) {
// Make the value unreachable - this is a weird value which will make (incorrect) existing
// references easier to spot
ip.items.set(@intFromEnum(resolved_index), .{
.tag = .simple_value,
.data = @intFromEnum(SimpleValue.@"unreachable"),
});
} else {
// Here we could add the index to a free-list for reuse, but since
// there is so little garbage created this way it's not worth it.
}
}
pub fn anonStructFieldTypes(ip: *const InternPool, i: Index) []const Index {
return ip.indexToKey(i).anon_struct_type.types;
}
pub fn anonStructFieldsLen(ip: *const InternPool, i: Index) u32 {
return @intCast(ip.indexToKey(i).anon_struct_type.types.len);
}
/// Asserts the type is a struct.
pub fn structDecl(ip: *const InternPool, i: Index) OptionalDeclIndex {
return switch (ip.indexToKey(i)) {
.struct_type => |t| t.decl,
else => unreachable,
};
}
/// Returns the already-existing field with the same name, if any.
pub fn addFieldName(
ip: *InternPool,
names_map: MapIndex,
names_start: u32,
name: NullTerminatedString,
) ?u32 {
const map = &ip.maps.items[@intFromEnum(names_map)];
const field_index = map.count();
const strings = ip.extra.items[names_start..][0..field_index];
const adapter: NullTerminatedString.Adapter = .{ .strings = @ptrCast(strings) };
const gop = map.getOrPutAssumeCapacityAdapted(name, adapter);
if (gop.found_existing) return @intCast(gop.index);
ip.extra.items[names_start + field_index] = @intFromEnum(name);
return null;
}
/// Used only by `get` for pointer values, and mainly intended to use `Tag.ptr_anon_decl`
/// encoding instead of `Tag.ptr_anon_decl_aligned` when possible.
fn ptrsHaveSameAlignment(ip: *InternPool, a_ty: Index, a_info: Key.PtrType, b_ty: Index) bool {
if (a_ty == b_ty) return true;
const b_info = ip.indexToKey(b_ty).ptr_type;
return a_info.flags.alignment == b_info.flags.alignment and
(a_info.child == b_info.child or a_info.flags.alignment != .none);
}