zig/src/InternPool.zig
2023-06-10 20:47:56 -07:00

5347 lines
193 KiB
Zig

//! All interned objects have both a value and a type.
//! This data structure is self-contained, with the following exceptions:
//! * type_struct via Module.Struct.Index
//! * type_opaque via Module.Namespace.Index and Module.Decl.Index
/// 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) = .{},
/// Struct objects are stored in this data structure because:
/// * They contain pointers such as the field maps.
/// * They need to be mutated after creation.
allocated_structs: std.SegmentedList(Module.Struct, 0) = .{},
/// When a Struct object is freed from `allocated_structs`, it is pushed into this stack.
structs_free_list: std.ArrayListUnmanaged(Module.Struct.Index) = .{},
/// Union objects are stored in this data structure because:
/// * They contain pointers such as the field maps.
/// * They need to be mutated after creation.
allocated_unions: std.SegmentedList(Module.Union, 0) = .{},
/// When a Union object is freed from `allocated_unions`, it is pushed into this stack.
unions_free_list: std.ArrayListUnmanaged(Module.Union.Index) = .{},
/// Fn objects are stored in this data structure because:
/// * They need to be mutated after creation.
allocated_funcs: std.SegmentedList(Module.Fn, 0) = .{},
/// When a Fn object is freed from `allocated_funcs`, it is pushed into this stack.
funcs_free_list: std.ArrayListUnmanaged(Module.Fn.Index) = .{},
/// InferredErrorSet objects are stored in this data structure because:
/// * They contain pointers such as the errors map and the set of other inferred error sets.
/// * They need to be mutated after creation.
allocated_inferred_error_sets: std.SegmentedList(Module.Fn.InferredErrorSet, 0) = .{},
/// When a Struct object is freed from `allocated_inferred_error_sets`, it is
/// pushed into this stack.
inferred_error_sets_free_list: std.ArrayListUnmanaged(Module.Fn.InferredErrorSet.Index) = .{},
/// 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.
maps: std.ArrayListUnmanaged(std.AutoArrayHashMapUnmanaged(void, void)) = .{},
/// 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,
) = .{},
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 Limb = std.math.big.Limb;
const InternPool = @This();
const Module = @import("Module.zig");
const Sema = @import("Sema.zig");
const KeyAdapter = struct {
intern_pool: *const InternPool,
pub fn eql(ctx: @This(), a: Key, b_void: void, b_map_index: usize) bool {
_ = b_void;
return ctx.intern_pool.indexToKey(@intToEnum(Index, 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 @intToEnum(MapIndex, @enumToInt(oi));
}
};
/// An index into `maps`.
pub const MapIndex = enum(u32) {
_,
pub fn toOptional(i: MapIndex) OptionalMapIndex {
return @intToEnum(OptionalMapIndex, @enumToInt(i));
}
};
pub const RuntimeIndex = enum(u32) {
zero = 0,
comptime_field_ptr = std.math.maxInt(u32),
_,
pub fn increment(ri: *RuntimeIndex) void {
ri.* = @intToEnum(RuntimeIndex, @enumToInt(ri.*) + 1);
}
};
/// An index into `string_bytes`.
pub const String = enum(u32) {
_,
};
/// An index into `string_bytes`.
pub const NullTerminatedString = enum(u32) {
_,
pub fn toString(self: NullTerminatedString) String {
return @intToEnum(String, @enumToInt(self));
}
pub fn toOptional(self: NullTerminatedString) OptionalNullTerminatedString {
return @intToEnum(OptionalNullTerminatedString, @enumToInt(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(@enumToInt(a));
}
};
/// Compare based on integer value alone, ignoring the string contents.
pub fn indexLessThan(ctx: void, a: NullTerminatedString, b: NullTerminatedString) bool {
_ = ctx;
return @enumToInt(a) < @enumToInt(b);
}
};
/// An index into `string_bytes` which might be `none`.
pub const OptionalNullTerminatedString = enum(u32) {
none = std.math.maxInt(u32),
_,
pub fn unwrap(oi: OptionalNullTerminatedString) ?NullTerminatedString {
if (oi == .none) return null;
return @intToEnum(NullTerminatedString, @enumToInt(oi));
}
};
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: StructType,
/// 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: UnionType,
opaque_type: OpaqueType,
enum_type: EnumType,
func_type: FuncType,
error_set_type: ErrorSetType,
inferred_error_set_type: Module.Fn.InferredErrorSet.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,
runtime_value: TypeValue,
simple_value: SimpleValue,
variable: Key.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: Key.EnumTag,
float: Key.Float,
ptr: Ptr,
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: Key.Aggregate,
/// An instance of a union.
un: Union,
/// A declaration with a memoized value.
memoized_decl: MemoizedDecl,
/// A comptime function call with a memoized result.
memoized_call: Key.MemoizedCall,
pub const IntType = std.builtin.Type.Int;
pub const ErrorUnionType = struct {
error_set_type: Index,
payload_type: Index,
};
pub const ErrorSetType = struct {
/// Set of error names, sorted by null terminated string index.
names: []const NullTerminatedString,
/// 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[@enumToInt(self.names_map.unwrap().?)];
const adapter: NullTerminatedString.Adapter = .{ .strings = self.names };
const field_index = map.getIndexAdapted(name, adapter) orelse return null;
return @intCast(u32, field_index);
}
};
pub const PtrType = struct {
elem_type: Index,
sentinel: Index = .none,
/// `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,
/// 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 = 0,
bit_offset: u16 = 0,
vector_index: VectorIndex = .none,
size: std.builtin.Type.Pointer.Size = .One,
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: std.builtin.AddressSpace = .generic,
pub const VectorIndex = enum(u16) {
none = std.math.maxInt(u16),
runtime = std.math.maxInt(u16) - 1,
_,
};
};
pub const ArrayType = struct {
len: u64,
child: Index,
sentinel: Index = .none,
};
pub const VectorType = struct {
len: u32,
child: Index,
};
pub const OpaqueType = struct {
/// The Decl that corresponds to the opaque itself.
decl: Module.Decl.Index,
/// Represents the declarations inside this opaque.
namespace: Module.Namespace.Index,
};
pub const StructType = struct {
/// The `none` tag is used to represent a struct with no fields.
index: Module.Struct.OptionalIndex,
/// May be `none` if the struct has no declarations.
namespace: Module.Namespace.OptionalIndex,
};
pub const AnonStructType = 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 isTuple(self: AnonStructType) bool {
return self.names.len == 0;
}
};
pub const UnionType = struct {
index: Module.Union.Index,
runtime_tag: RuntimeTag,
pub const RuntimeTag = enum { none, safety, tagged };
pub fn hasTag(self: UnionType) bool {
return switch (self.runtime_tag) {
.none => false,
.tagged, .safety => true,
};
}
};
pub const EnumType = struct {
/// The Decl that corresponds to the enum itself.
decl: Module.Decl.Index,
/// Represents the declarations inside this enum.
namespace: Module.Namespace.OptionalIndex,
/// 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: []const NullTerminatedString,
/// 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: []const Index,
tag_mode: TagMode,
/// This is ignored by `get` but will always be provided by `indexToKey`.
names_map: OptionalMapIndex = .none,
/// This is ignored by `get` but will be provided by `indexToKey` when
/// a value map exists.
values_map: OptionalMapIndex = .none,
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: EnumType, ip: *const InternPool, name: NullTerminatedString) ?u32 {
const map = &ip.maps.items[@enumToInt(self.names_map.unwrap().?)];
const adapter: NullTerminatedString.Adapter = .{ .strings = self.names };
const field_index = map.getIndexAdapted(name, adapter) orelse return null;
return @intCast(u32, 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: EnumType, ip: *const InternPool, tag_val: Index) ?u32 {
assert(tag_val != .none);
if (self.values_map.unwrap()) |values_map| {
const map = &ip.maps.items[@enumToInt(values_map)];
const adapter: Index.Adapter = .{ .indexes = self.values };
const field_index = map.getIndexAdapted(tag_val, adapter) orelse return null;
return @intCast(u32, field_index);
}
// Auto-numbered enum. Convert `tag_val` to field index.
switch (ip.indexToKey(tag_val).int.storage) {
.u64 => |x| {
if (x >= self.names.len) return null;
return @intCast(u32, x);
},
.i64, .big_int => return null, // out of range
.lazy_align, .lazy_size => unreachable,
}
}
};
pub const IncompleteEnumType = struct {
/// Same as corresponding `EnumType` field.
decl: Module.Decl.Index,
/// Same as corresponding `EnumType` field.
namespace: Module.Namespace.OptionalIndex,
/// The field names and field values are not known yet, but
/// the number of fields must be known ahead of time.
fields_len: u32,
/// This information is needed so that the size does not change
/// later when populating field values.
has_values: bool,
/// Same as corresponding `EnumType` field.
tag_mode: EnumType.TagMode,
/// This may be updated via `setTagType` later.
tag_ty: Index = .none,
pub fn toEnumType(self: @This()) EnumType {
return .{
.decl = self.decl,
.namespace = self.namespace,
.tag_ty = self.tag_ty,
.tag_mode = self.tag_mode,
.names = &.{},
.values = &.{},
};
}
/// Only the decl is used for hashing and equality, so we can construct
/// this minimal key for use with `map`.
pub fn toKey(self: @This()) Key {
return .{ .enum_type = self.toEnumType() };
}
};
pub const FuncType = struct {
param_types: []Index,
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,
/// `none` indicates the function has the default alignment for
/// function code on the target. In this case, this field *must* be set
/// to `none`, otherwise the `InternPool` equality and hashing
/// functions will return incorrect results.
alignment: Alignment,
cc: std.builtin.CallingConvention,
is_var_args: bool,
is_generic: bool,
is_noinline: bool,
align_is_generic: 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 @truncate(u1, self.comptime_bits >> i) != 0;
}
pub fn paramIsNoalias(self: @This(), i: u5) bool {
assert(i < self.param_types.len);
return @truncate(u1, self.noalias_bits >> i) != 0;
}
};
pub const Variable = struct {
ty: Index,
init: Index,
decl: Module.Decl.Index,
lib_name: OptionalNullTerminatedString = .none,
is_extern: bool = false,
is_const: bool = false,
is_threadlocal: bool = false,
is_weak_linkage: bool = false,
};
pub const ExternFunc = struct {
ty: Index,
/// The Decl that corresponds to the function itself.
decl: Module.Decl.Index,
/// 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 {
ty: Index,
index: Module.Fn.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 = 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 = 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,
/// This could be `none` if size is not a slice.
len: Index = .none,
pub const Addr = union(enum) {
decl: Module.Decl.Index,
mut_decl: MutDecl,
comptime_field: Index,
int: Index,
eu_payload: Index,
opt_payload: Index,
elem: BaseIndex,
field: BaseIndex,
pub const MutDecl = struct {
decl: Module.Decl.Index,
runtime_index: RuntimeIndex,
};
pub const BaseIndex = struct {
base: Index,
index: u64,
};
};
};
/// `null` is represented by the `val` field being `none`.
pub const Opt = 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 = struct {
/// This is the union type; not the field type.
ty: Index,
/// Indicates the active field.
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 MemoizedDecl = struct {
val: Index,
decl: Module.Decl.Index,
};
pub const MemoizedCall = struct {
func: Module.Fn.Index,
arg_values: []const Index,
result: Index,
};
pub fn hash32(key: Key, ip: *const InternPool) u32 {
return @truncate(u32, key.hash64(ip));
}
pub fn hash64(key: Key, ip: *const InternPool) u64 {
var hasher = std.hash.Wyhash.init(0);
key.hashWithHasher(&hasher, ip);
return hasher.final();
}
pub fn hashWithHasher(key: Key, hasher: *std.hash.Wyhash, ip: *const InternPool) void {
const KeyTag = @typeInfo(Key).Union.tag_type.?;
std.hash.autoHash(hasher, @as(KeyTag, key));
switch (key) {
inline .int_type,
.ptr_type,
.array_type,
.vector_type,
.opt_type,
.anyframe_type,
.error_union_type,
.simple_type,
.simple_value,
.opt,
.struct_type,
.union_type,
.un,
.undef,
.err,
.error_union,
.enum_literal,
.enum_tag,
.inferred_error_set_type,
=> |info| std.hash.autoHash(hasher, info),
.runtime_value => |runtime_value| std.hash.autoHash(hasher, runtime_value.val),
.opaque_type => |opaque_type| std.hash.autoHash(hasher, opaque_type.decl),
.enum_type => |enum_type| std.hash.autoHash(hasher, enum_type.decl),
.variable => |variable| std.hash.autoHash(hasher, variable.decl),
.extern_func => |extern_func| {
std.hash.autoHash(hasher, extern_func.ty);
std.hash.autoHash(hasher, extern_func.decl);
},
.func => |func| {
std.hash.autoHash(hasher, func.ty);
std.hash.autoHash(hasher, func.index);
},
.int => |int| {
// 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);
},
}
},
.float => |float| {
std.hash.autoHash(hasher, float.ty);
switch (float.storage) {
inline else => |val| std.hash.autoHash(
hasher,
@bitCast(std.meta.Int(.unsigned, @bitSizeOf(@TypeOf(val))), val),
),
}
},
.ptr => |ptr| {
std.hash.autoHash(hasher, ptr.ty);
std.hash.autoHash(hasher, ptr.len);
// Int-to-ptr pointers are hashed separately than decl-referencing pointers.
// This is sound due to pointer provenance rules.
std.hash.autoHash(hasher, @as(@typeInfo(Key.Ptr.Addr).Union.tag_type.?, ptr.addr));
switch (ptr.addr) {
.decl => |decl| std.hash.autoHash(hasher, decl),
.mut_decl => |mut_decl| std.hash.autoHash(hasher, mut_decl),
.int => |int| std.hash.autoHash(hasher, int),
.eu_payload => |eu_payload| std.hash.autoHash(hasher, eu_payload),
.opt_payload => |opt_payload| std.hash.autoHash(hasher, opt_payload),
.comptime_field => |comptime_field| std.hash.autoHash(hasher, comptime_field),
.elem => |elem| std.hash.autoHash(hasher, elem),
.field => |field| std.hash.autoHash(hasher, field),
}
},
.aggregate => |aggregate| {
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(usize, len)]) |byte| {
std.hash.autoHash(hasher, KeyTag.int);
std.hash.autoHash(hasher, byte);
},
.elems => |elems| for (elems[0..@intCast(usize, 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,
@intCast(u8, 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,
@intCast(u8, int.storage.u64),
),
else => unreachable,
}
}
},
}
return;
}
switch (aggregate.storage) {
.bytes => unreachable,
.elems => |elems| for (elems[0..@intCast(usize, len)]) |elem|
std.hash.autoHash(hasher, elem),
.repeated_elem => |elem| {
var remaining = len;
while (remaining > 0) : (remaining -= 1) std.hash.autoHash(hasher, elem);
},
}
},
.error_set_type => |error_set_type| {
for (error_set_type.names) |elem| std.hash.autoHash(hasher, elem);
},
.anon_struct_type => |anon_struct_type| {
for (anon_struct_type.types) |elem| std.hash.autoHash(hasher, elem);
for (anon_struct_type.values) |elem| std.hash.autoHash(hasher, elem);
for (anon_struct_type.names) |elem| std.hash.autoHash(hasher, elem);
},
.func_type => |func_type| {
for (func_type.param_types) |param_type| std.hash.autoHash(hasher, param_type);
std.hash.autoHash(hasher, func_type.return_type);
std.hash.autoHash(hasher, func_type.comptime_bits);
std.hash.autoHash(hasher, func_type.noalias_bits);
std.hash.autoHash(hasher, func_type.alignment);
std.hash.autoHash(hasher, func_type.cc);
std.hash.autoHash(hasher, func_type.is_var_args);
std.hash.autoHash(hasher, func_type.is_generic);
std.hash.autoHash(hasher, func_type.is_noinline);
},
.memoized_decl => |memoized_decl| std.hash.autoHash(hasher, memoized_decl.val),
.memoized_call => |memoized_call| {
std.hash.autoHash(hasher, memoized_call.func);
for (memoized_call.arg_values) |arg| std.hash.autoHash(hasher, arg);
},
}
}
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;
},
.runtime_value => |a_info| {
const b_info = b.runtime_value;
return a_info.val == b_info.val;
},
.opt => |a_info| {
const b_info = b.opt;
return std.meta.eql(a_info, b_info);
},
.struct_type => |a_info| {
const b_info = b.struct_type;
return std.meta.eql(a_info, b_info);
},
.union_type => |a_info| {
const b_info = b.union_type;
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);
},
.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;
return a_info.ty == b_info.ty and a_info.index == b_info.index;
},
.ptr => |a_info| {
const b_info = b.ptr;
if (a_info.ty != b_info.ty or a_info.len != b_info.len) 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,
.mut_decl => |a_mut_decl| std.meta.eql(a_mut_decl, b_info.addr.mut_decl),
.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.eq(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| @floatCast(f128, val),
};
const b_val = switch (b_info.storage) {
inline else => |val| @floatCast(f128, 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));
return switch (a_info.storage) {
inline else => |val, tag| val == @field(b_info.storage, @tagName(tag)),
};
},
.opaque_type => |a_info| {
const b_info = b.opaque_type;
return a_info.decl == b_info.decl;
},
.enum_type => |a_info| {
const b_info = b.enum_type;
return a_info.decl == b_info.decl;
},
.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..@intCast(usize, 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..@intCast(usize, len)],
b_bytes[0..@intCast(usize, len)],
);
},
.elems => |a_elems| {
const b_elems = b_info.storage.elems;
return std.mem.eql(
Index,
a_elems[0..@intCast(usize, len)],
b_elems[0..@intCast(usize, 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, b_info.types) and
std.mem.eql(Index, a_info.values, b_info.values) and
std.mem.eql(NullTerminatedString, a_info.names, b_info.names);
},
.error_set_type => |a_info| {
const b_info = b.error_set_type;
return std.mem.eql(NullTerminatedString, a_info.names, b_info.names);
},
.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 std.mem.eql(Index, a_info.param_types, b_info.param_types) and
a_info.return_type == b_info.return_type and
a_info.comptime_bits == b_info.comptime_bits and
a_info.noalias_bits == b_info.noalias_bits and
a_info.alignment == b_info.alignment and
a_info.cc == b_info.cc and
a_info.is_var_args == b_info.is_var_args and
a_info.is_generic == b_info.is_generic and
a_info.is_noinline == b_info.is_noinline;
},
.memoized_decl => |a_info| {
const b_info = b.memoized_decl;
return a_info.val == b_info.val;
},
.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 .runtime_value,
.ptr,
.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,
.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_decl,
.memoized_call,
=> unreachable,
};
}
};
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 = .u1_type;
pub const last_type: Index = .empty_struct_type;
pub const first_value: Index = .undef;
pub const last_value: Index = .empty_struct;
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,
anyerror_void_error_union_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),
_,
pub fn toType(i: Index) @import("type.zig").Type {
assert(i != .none);
return .{ .ip_index = i };
}
pub fn toValue(i: Index) @import("value.zig").Value {
assert(i != .none);
return .{
.ip_index = i,
.legacy = undefined,
};
}
/// 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(@enumToInt(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.
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,
},
};
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: *Pointer },
type_slice: DataIsIndex,
type_optional: DataIsIndex,
type_anyframe: DataIsIndex,
type_error_union: struct { data: *Key.ErrorUnionType },
type_error_set: struct {
const @"data.names_len" = opaque {};
data: *ErrorSet,
@"trailing.names.len": *@"data.names_len",
trailing: struct { names: []NullTerminatedString },
},
type_inferred_error_set: struct { data: Module.Fn.InferredErrorSet.Index },
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: *Key.OpaqueType },
type_struct: struct { data: Module.Struct.OptionalIndex },
type_struct_ns: struct { data: Module.Namespace.Index },
type_struct_anon: DataIsExtraIndexOfTypeStructAnon,
type_tuple_anon: DataIsExtraIndexOfTypeStructAnon,
type_union_tagged: struct { data: Module.Union.Index },
type_union_untagged: struct { data: Module.Union.Index },
type_union_safety: struct { data: Module.Union.Index },
type_function: struct {
const @"data.params_len" = opaque {};
data: *TypeFunction,
@"trailing.param_types.len": *@"data.params_len",
trailing: struct { param_types: []Index },
},
undef: DataIsIndex,
runtime_value: DataIsIndex,
simple_value: struct { data: SimpleValue },
ptr_decl: struct { data: *PtrDecl },
ptr_mut_decl: struct { data: *PtrMutDecl },
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: DataIsIndex,
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: *TypeValue },
enum_literal: struct { data: NullTerminatedString },
enum_tag: struct { data: *Key.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: *Variable },
extern_func: struct { data: *Key.ExternFunc },
func: struct { data: *Key.Func },
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: *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_decl: struct { data: *Key.MemoizedDecl },
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.mode == .Debug) {
_ = dbHelper;
}
}
};
pub const static_keys = [_]Key{
.{ .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 },
.{ .ptr_type = .{
.elem_type = .u8_type,
.size = .Many,
} },
// manyptr_const_u8_type
.{ .ptr_type = .{
.elem_type = .u8_type,
.size = .Many,
.is_const = true,
} },
// manyptr_const_u8_sentinel_0_type
.{ .ptr_type = .{
.elem_type = .u8_type,
.sentinel = .zero_u8,
.size = .Many,
.is_const = true,
} },
.{ .ptr_type = .{
.elem_type = .comptime_int_type,
.size = .One,
.is_const = true,
} },
// slice_const_u8_type
.{ .ptr_type = .{
.elem_type = .u8_type,
.size = .Slice,
.is_const = true,
} },
// slice_const_u8_sentinel_0_type
.{ .ptr_type = .{
.elem_type = .u8_type,
.sentinel = .zero_u8,
.size = .Slice,
.is_const = true,
} },
// anyerror_void_error_union_type
.{ .error_union_type = .{
.error_set_type = .anyerror_type,
.payload_type = .void_type,
} },
// generic_poison_type
.{ .simple_type = .generic_poison },
// empty_struct_type
.{ .anon_struct_type = .{
.types = &.{},
.names = &.{},
.values = &.{},
} },
.{ .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.
pub const static_len: u32 = static_keys.len;
pub const Tag = enum(u8) {
/// 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.
/// data is payload to Pointer.
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 set type.
/// data is payload to `ErrorSet`.
type_error_set,
/// The inferred error set type of a function.
/// data is `Module.Fn.InferredErrorSet.Index`.
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 Key.OpaqueType in extra.
type_opaque,
/// A struct type.
/// data is Module.Struct.OptionalIndex
/// The `none` tag is used to represent `@TypeOf(.{})`.
type_struct,
/// A struct type that has only a namespace; no fields, and there is no
/// Module.Struct object allocated for it.
/// data is Module.Namespace.Index.
type_struct_ns,
/// An AnonStructType which stores types, names, and values for fields.
/// data is extra index of `TypeStructAnon`.
type_struct_anon,
/// An AnonStructType which has only types and values for fields.
/// data is extra index of `TypeStructAnon`.
type_tuple_anon,
/// A tagged union type.
/// `data` is `Module.Union.Index`.
type_union_tagged,
/// An untagged union type. It also has no safety tag.
/// `data` is `Module.Union.Index`.
type_union_untagged,
/// An untagged union type which has a safety tag.
/// `data` is `Module.Union.Index`.
type_union_safety,
/// 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 wrapper for values which are comptime-known but should
/// semantically be runtime-known.
/// `data` is `Index` of the value.
runtime_value,
/// 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 `PtrMutDecl`, which contains the type and address.
ptr_mut_decl,
/// 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
/// In order to use this encoding, one must ensure that the `InternPool`
/// already contains the slice type corresponding to this payload.
ptr_slice,
/// An optional value that is non-null.
/// data is Index of the payload value.
/// In order to use this encoding, one must ensure that the `InternPool`
/// already contains the optional type corresponding to this payload.
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 `Key.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 Key.ExternFunc.
extern_func,
/// A regular function.
/// data is extra index to Key.Func.
func,
/// 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.
/// 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 declaration value.
/// data is extra index to `Key.MemoizedDecl`
memoized_decl,
/// A memoized comptime function call result.
/// data is extra index to `MemoizedFunc`
memoized_call,
};
/// 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. param_type: Index for each params_len
pub const TypeFunction = struct {
params_len: u32,
return_type: Index,
comptime_bits: u32,
noalias_bits: u32,
flags: Flags,
pub const Flags = packed struct(u32) {
alignment: Alignment,
cc: std.builtin.CallingConvention,
is_var_args: bool,
is_generic: bool,
is_noinline: bool,
align_is_generic: bool,
cc_is_generic: bool,
section_is_generic: bool,
addrspace_is_generic: bool,
_: u11 = 0,
};
};
pub const Bytes = struct {
/// The type of the aggregate
ty: Index,
/// Index into string_bytes, of len ip.aggregateTypeLen(ty)
bytes: String,
};
/// 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,
};
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,
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,
};
pub const Pointer = struct {
child: Index,
sentinel: Index,
flags: Flags,
packed_offset: PackedOffset,
pub const Flags = packed struct(u32) {
size: Size,
alignment: Alignment,
is_const: bool,
is_volatile: bool,
is_allowzero: bool,
address_space: AddressSpace,
vector_index: VectorIndex,
};
pub const PackedOffset = packed struct(u32) {
host_size: u16,
bit_offset: u16,
};
pub const Size = std.builtin.Type.Pointer.Size;
pub const AddressSpace = std.builtin.AddressSpace;
pub const VectorIndex = Key.PtrType.VectorIndex;
};
/// Stored as a power-of-two, with one special value to indicate none.
pub const Alignment = enum(u6) {
none = std.math.maxInt(u6),
_,
pub fn toByteUnitsOptional(a: Alignment) ?u64 {
return switch (a) {
.none => null,
_ => @as(u64, 1) << @enumToInt(a),
};
}
pub fn toByteUnits(a: Alignment, default: u64) u64 {
return switch (a) {
.none => default,
_ => @as(u64, 1) << @enumToInt(a),
};
}
pub fn fromByteUnits(n: u64) Alignment {
if (n == 0) return .none;
assert(std.math.isPowerOfTwo(n));
return @intToEnum(Alignment, @ctz(n));
}
pub fn fromNonzeroByteUnits(n: u64) Alignment {
assert(n != 0);
return fromByteUnits(n);
}
pub fn min(a: Alignment, b: Alignment) Alignment {
return @intToEnum(Alignment, @min(@enumToInt(a), @enumToInt(b)));
}
};
/// 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();
}
};
pub const TypeValue = struct {
ty: Index,
val: Index,
};
/// Trailing:
/// 0. field name: NullTerminatedString for each fields_len; declaration order
/// 1. tag value: Index for each fields_len; declaration order
pub const EnumExplicit = struct {
/// The Decl that corresponds to the enum itself.
decl: Module.Decl.Index,
/// This may be `none` if there are no declarations.
namespace: Module.Namespace.OptionalIndex,
/// 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,
};
/// Trailing:
/// 0. field name: NullTerminatedString for each fields_len; declaration order
pub const EnumAuto = struct {
/// The Decl that corresponds to the enum itself.
decl: Module.Decl.Index,
/// This may be `none` if there are no declarations.
namespace: Module.Namespace.OptionalIndex,
fields_len: u32,
/// Maps field names to declaration index.
names_map: MapIndex,
};
pub const PackedU64 = packed struct(u64) {
a: u32,
b: u32,
pub fn get(x: PackedU64) u64 {
return @bitCast(u64, x);
}
pub fn init(x: u64) PackedU64 {
return @bitCast(PackedU64, x);
}
};
pub const Variable = struct {
/// This is a value if has_init is true, otherwise a type.
init: Index,
decl: Module.Decl.Index,
/// 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) {
has_init: bool,
is_extern: bool,
is_const: bool,
is_threadlocal: bool,
is_weak_linkage: bool,
_: u27 = 0,
};
};
pub const PtrDecl = struct {
ty: Index,
decl: Module.Decl.Index,
};
pub const PtrMutDecl = struct {
ty: Index,
decl: Module.Decl.Index,
runtime_index: RuntimeIndex,
};
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 {
ptr: Index,
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(f64, int_bits);
}
fn pack(val: f64) Float64 {
const bits = @bitCast(u64, val);
return .{
.piece0 = @truncate(u32, bits),
.piece1 = @truncate(u32, 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(f80, int_bits);
}
fn pack(val: f80) Float80 {
const bits = @bitCast(u80, val);
return .{
.piece0 = @truncate(u32, bits),
.piece1 = @truncate(u32, bits >> 32),
.piece2 = @truncate(u16, 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(f128, int_bits);
}
fn pack(val: f128) Float128 {
const bits = @bitCast(u128, val);
return .{
.piece0 = @truncate(u32, bits),
.piece1 = @truncate(u32, bits >> 32),
.piece2 = @truncate(u32, bits >> 64),
.piece3 = @truncate(u32, bits >> 96),
};
}
};
/// Trailing:
/// 0. arg value: Index for each args_len
pub const MemoizedCall = struct {
func: Module.Fn.Index,
args_len: u32,
result: Index,
};
pub fn init(ip: *InternPool, gpa: Allocator) !void {
assert(ip.items.len == 0);
// 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) |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 ==
@enumToInt(std.builtin.CallingConvention.Inline));
assert(ip.indexToKey(cc_c).int.storage.u64 ==
@enumToInt(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.structs_free_list.deinit(gpa);
ip.allocated_structs.deinit(gpa);
ip.unions_free_list.deinit(gpa);
ip.allocated_unions.deinit(gpa);
ip.funcs_free_list.deinit(gpa);
ip.allocated_funcs.deinit(gpa);
ip.inferred_error_sets_free_list.deinit(gpa);
ip.allocated_inferred_error_sets.deinit(gpa);
for (ip.maps.items) |*map| map.deinit(gpa);
ip.maps.deinit(gpa);
ip.string_table.deinit(gpa);
ip.* = undefined;
}
pub fn indexToKey(ip: *const InternPool, index: Index) Key {
assert(index != .none);
const item = ip.items.get(@enumToInt(index));
const data = item.data;
return switch (item.tag) {
.type_int_signed => .{
.int_type = .{
.signedness = .signed,
.bits = @intCast(u16, data),
},
},
.type_int_unsigned => .{
.int_type = .{
.signedness = .unsigned,
.bits = @intCast(u16, 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 = @intToEnum(SimpleType, data) },
.simple_value => .{ .simple_value = @intToEnum(SimpleValue, data) },
.type_vector => {
const vector_info = ip.extraData(Vector, data);
return .{ .vector_type = .{
.len = vector_info.len,
.child = vector_info.child,
} };
},
.type_pointer => {
const ptr_info = ip.extraData(Pointer, data);
return .{ .ptr_type = .{
.elem_type = ptr_info.child,
.sentinel = ptr_info.sentinel,
.alignment = ptr_info.flags.alignment,
.size = ptr_info.flags.size,
.is_const = ptr_info.flags.is_const,
.is_volatile = ptr_info.flags.is_volatile,
.is_allowzero = ptr_info.flags.is_allowzero,
.address_space = ptr_info.flags.address_space,
.vector_index = ptr_info.flags.vector_index,
.host_size = ptr_info.packed_offset.host_size,
.bit_offset = ptr_info.packed_offset.bit_offset,
} };
},
.type_slice => {
const ptr_type_index = @intToEnum(Index, data);
var result = ip.indexToKey(ptr_type_index).ptr_type;
result.size = .Slice;
return .{ .ptr_type = result };
},
.type_optional => .{ .opt_type = @intToEnum(Index, data) },
.type_anyframe => .{ .anyframe_type = @intToEnum(Index, data) },
.type_error_union => .{ .error_union_type = ip.extraData(Key.ErrorUnionType, data) },
.type_error_set => {
const error_set = ip.extraDataTrail(ErrorSet, data);
const names_len = error_set.data.names_len;
const names = ip.extra.items[error_set.end..][0..names_len];
return .{ .error_set_type = .{
.names = @ptrCast([]const NullTerminatedString, names),
.names_map = error_set.data.names_map.toOptional(),
} };
},
.type_inferred_error_set => .{
.inferred_error_set_type = @intToEnum(Module.Fn.InferredErrorSet.Index, data),
},
.type_opaque => .{ .opaque_type = ip.extraData(Key.OpaqueType, data) },
.type_struct => {
const struct_index = @intToEnum(Module.Struct.OptionalIndex, data);
const namespace = if (struct_index.unwrap()) |i|
ip.structPtrConst(i).namespace.toOptional()
else
.none;
return .{ .struct_type = .{
.index = struct_index,
.namespace = namespace,
} };
},
.type_struct_ns => .{ .struct_type = .{
.index = .none,
.namespace = @intToEnum(Module.Namespace.Index, data).toOptional(),
} },
.type_struct_anon => {
const type_struct_anon = ip.extraDataTrail(TypeStructAnon, data);
const fields_len = type_struct_anon.data.fields_len;
const types = ip.extra.items[type_struct_anon.end..][0..fields_len];
const values = ip.extra.items[type_struct_anon.end + fields_len ..][0..fields_len];
const names = ip.extra.items[type_struct_anon.end + 2 * fields_len ..][0..fields_len];
return .{ .anon_struct_type = .{
.types = @ptrCast([]const Index, types),
.values = @ptrCast([]const Index, values),
.names = @ptrCast([]const NullTerminatedString, names),
} };
},
.type_tuple_anon => {
const type_struct_anon = ip.extraDataTrail(TypeStructAnon, data);
const fields_len = type_struct_anon.data.fields_len;
const types = ip.extra.items[type_struct_anon.end..][0..fields_len];
const values = ip.extra.items[type_struct_anon.end + fields_len ..][0..fields_len];
return .{ .anon_struct_type = .{
.types = @ptrCast([]const Index, types),
.values = @ptrCast([]const Index, values),
.names = &.{},
} };
},
.type_union_untagged => .{ .union_type = .{
.index = @intToEnum(Module.Union.Index, data),
.runtime_tag = .none,
} },
.type_union_tagged => .{ .union_type = .{
.index = @intToEnum(Module.Union.Index, data),
.runtime_tag = .tagged,
} },
.type_union_safety => .{ .union_type = .{
.index = @intToEnum(Module.Union.Index, data),
.runtime_tag = .safety,
} },
.type_enum_auto => {
const enum_auto = ip.extraDataTrail(EnumAuto, data);
const names = @ptrCast(
[]const NullTerminatedString,
ip.extra.items[enum_auto.end..][0..enum_auto.data.fields_len],
);
return .{ .enum_type = .{
.decl = enum_auto.data.decl,
.namespace = enum_auto.data.namespace,
.tag_ty = ip.getEnumIntTagType(enum_auto.data.fields_len),
.names = names,
.values = &.{},
.tag_mode = .auto,
.names_map = enum_auto.data.names_map.toOptional(),
.values_map = .none,
} };
},
.type_enum_explicit => ip.indexToKeyEnum(data, .explicit),
.type_enum_nonexhaustive => ip.indexToKeyEnum(data, .nonexhaustive),
.type_function => .{ .func_type = ip.indexToKeyFuncType(data) },
.undef => .{ .undef = @intToEnum(Index, data) },
.runtime_value => {
const val = @intToEnum(Index, data);
return .{ .runtime_value = .{
.ty = ip.typeOf(val),
.val = val,
} };
},
.opt_null => .{ .opt = .{
.ty = @intToEnum(Index, data),
.val = .none,
} },
.opt_payload => {
const payload_val = @intToEnum(Index, data);
// The existence of `opt_payload` guarantees that the optional type will be
// stored in the `InternPool`.
const opt_ty = ip.getAssumeExists(.{
.opt_type = ip.typeOf(payload_val),
});
return .{ .opt = .{
.ty = opt_ty,
.val = payload_val,
} };
},
.ptr_decl => {
const info = ip.extraData(PtrDecl, data);
return .{ .ptr = .{
.ty = info.ty,
.addr = .{ .decl = info.decl },
} };
},
.ptr_mut_decl => {
const info = ip.extraData(PtrMutDecl, data);
return .{ .ptr = .{
.ty = info.ty,
.addr = .{ .mut_decl = .{
.decl = info.decl,
.runtime_index = info.runtime_index,
} },
} };
},
.ptr_comptime_field => {
const info = ip.extraData(PtrComptimeField, data);
return .{ .ptr = .{
.ty = info.ty,
.addr = .{ .comptime_field = info.field_val },
} };
},
.ptr_int, .ptr_eu_payload, .ptr_opt_payload => {
const info = ip.extraData(PtrBase, data);
return .{ .ptr = .{
.ty = info.ty,
.addr = switch (item.tag) {
.ptr_int => .{ .int = info.base },
.ptr_eu_payload => .{ .eu_payload = info.base },
.ptr_opt_payload => .{ .opt_payload = info.base },
else => unreachable,
},
} };
},
.ptr_elem => {
const info = ip.extraData(PtrBaseIndex, data);
return .{ .ptr = .{
.ty = info.ty,
.addr = .{ .elem = .{
.base = info.base,
.index = ip.indexToKey(info.index).int.storage.u64,
} },
} };
},
.ptr_field => {
const info = ip.extraData(PtrBaseIndex, data);
return .{ .ptr = .{
.ty = info.ty,
.addr = .{ .field = .{
.base = info.base,
.index = ip.indexToKey(info.index).int.storage.u64,
} },
} };
},
.ptr_slice => {
const info = ip.extraData(PtrSlice, data);
const ptr = ip.indexToKey(info.ptr).ptr;
var ptr_type = ip.indexToKey(ptr.ty).ptr_type;
assert(ptr_type.size == .Many);
ptr_type.size = .Slice;
return .{ .ptr = .{
.ty = ip.getAssumeExists(.{ .ptr_type = ptr_type }),
.addr = ptr.addr,
.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 = @bitCast(i32, 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 = @bitCast(i32, 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 = @bitCast(f16, @intCast(u16, data)) },
} },
.float_f32 => .{ .float = .{
.ty = .f32_type,
.storage = .{ .f32 = @bitCast(f32, 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(Variable, data);
return .{ .variable = .{
.ty = if (extra.flags.has_init) ip.typeOf(extra.init) else extra.init,
.init = if (extra.flags.has_init) extra.init else .none,
.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(Key.ExternFunc, data) },
.func => .{ .func = ip.extraData(Key.Func, data) },
.only_possible_value => {
const ty = @intToEnum(Index, data);
return switch (ip.indexToKey(ty)) {
.array_type, .vector_type => .{ .aggregate = .{
.ty = ty,
.storage = .{ .elems = &.{} },
} },
// TODO: migrate structs to properly use the InternPool rather
// than using the SegmentedList trick, then the struct type will
// have a slice of comptime values that can be used here for when
// the struct has one possible value due to all fields comptime (same
// as the tuple case below).
.struct_type => .{ .aggregate = .{
.ty = ty,
.storage = .{ .elems = &.{} },
} },
// There is only one possible value precisely due to the
// fact that this values slice is fully populated!
.anon_struct_type => |anon_struct_type| .{ .aggregate = .{
.ty = ty,
.storage = .{ .elems = anon_struct_type.values },
} },
else => unreachable,
};
},
.bytes => {
const extra = ip.extraData(Bytes, data);
const len = @intCast(u32, ip.aggregateTypeLenIncludingSentinel(extra.ty));
return .{ .aggregate = .{
.ty = extra.ty,
.storage = .{ .bytes = ip.string_bytes.items[@enumToInt(extra.bytes)..][0..len] },
} };
},
.aggregate => {
const extra = ip.extraDataTrail(Aggregate, data);
const len = @intCast(u32, ip.aggregateTypeLenIncludingSentinel(extra.data.ty));
const fields = @ptrCast([]const Index, 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(TypeValue, data);
return .{ .error_union = .{
.ty = extra.ty,
.val = .{ .payload = extra.val },
} };
},
.enum_literal => .{ .enum_literal = @intToEnum(NullTerminatedString, data) },
.enum_tag => .{ .enum_tag = ip.extraData(Key.EnumTag, data) },
.memoized_decl => .{ .memoized_decl = ip.extraData(Key.MemoizedDecl, data) },
.memoized_call => {
const extra = ip.extraDataTrail(MemoizedCall, data);
return .{ .memoized_call = .{
.func = extra.data.func,
.arg_values = @ptrCast([]const Index, ip.extra.items[extra.end..][0..extra.data.args_len]),
.result = extra.data.result,
} };
},
};
}
fn indexToKeyFuncType(ip: InternPool, data: u32) Key.FuncType {
const type_function = ip.extraDataTrail(TypeFunction, data);
const param_types = @ptrCast(
[]Index,
ip.extra.items[type_function.end..][0..type_function.data.params_len],
);
return .{
.param_types = param_types,
.return_type = type_function.data.return_type,
.comptime_bits = type_function.data.comptime_bits,
.noalias_bits = type_function.data.noalias_bits,
.alignment = type_function.data.flags.alignment,
.cc = type_function.data.flags.cc,
.is_var_args = type_function.data.flags.is_var_args,
.is_generic = type_function.data.flags.is_generic,
.is_noinline = type_function.data.flags.is_noinline,
.align_is_generic = type_function.data.flags.align_is_generic,
.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,
};
}
/// Asserts the integer tag type is already present in the InternPool.
fn getEnumIntTagType(ip: InternPool, fields_len: u32) Index {
return ip.getAssumeExists(.{ .int_type = .{
.bits = if (fields_len == 0) 0 else std.math.log2_int_ceil(u32, fields_len),
.signedness = .unsigned,
} });
}
fn indexToKeyEnum(ip: InternPool, data: u32, tag_mode: Key.EnumType.TagMode) Key {
const enum_explicit = ip.extraDataTrail(EnumExplicit, data);
const names = @ptrCast(
[]const NullTerminatedString,
ip.extra.items[enum_explicit.end..][0..enum_explicit.data.fields_len],
);
const values = if (enum_explicit.data.values_map != .none) @ptrCast(
[]const Index,
ip.extra.items[enum_explicit.end + names.len ..][0..enum_explicit.data.fields_len],
) else &[0]Index{};
return .{ .enum_type = .{
.decl = enum_explicit.data.decl,
.namespace = enum_explicit.data.namespace,
.tag_ty = enum_explicit.data.int_tag_type,
.names = names,
.values = values,
.tag_mode = tag_mode,
.names_map = enum_explicit.data.names_map.toOptional(),
.values_map = enum_explicit.data.values_map,
} };
}
fn indexToKeyBigInt(ip: 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 @intToEnum(Index, 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.elem_type != .none);
assert(ptr_type.sentinel == .none or ip.typeOf(ptr_type.sentinel) == ptr_type.elem_type);
if (ptr_type.size == .Slice) {
_ = ip.map.pop();
var new_key = key;
new_key.ptr_type.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 = @enumToInt(ptr_type_index),
});
return @intToEnum(Index, ip.items.len - 1);
}
const is_allowzero = ptr_type.is_allowzero or ptr_type.size == .C;
ip.items.appendAssumeCapacity(.{
.tag = .type_pointer,
.data = try ip.addExtra(gpa, Pointer{
.child = ptr_type.elem_type,
.sentinel = ptr_type.sentinel,
.flags = .{
.alignment = ptr_type.alignment,
.is_const = ptr_type.is_const,
.is_volatile = ptr_type.is_volatile,
.is_allowzero = is_allowzero,
.size = ptr_type.size,
.address_space = ptr_type.address_space,
.vector_index = ptr_type.vector_index,
},
.packed_offset = .{
.host_size = ptr_type.host_size,
.bit_offset = ptr_type.bit_offset,
},
}),
});
},
.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 @intToEnum(Index, 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 = @enumToInt(payload_type),
});
},
.anyframe_type => |payload_type| {
// payload_type might be none, indicating the type is `anyframe`.
ip.items.appendAssumeCapacity(.{
.tag = .type_anyframe,
.data = @enumToInt(payload_type),
});
},
.error_union_type => |error_union_type| {
ip.items.appendAssumeCapacity(.{
.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, {}, NullTerminatedString.indexLessThan));
const names_map = try ip.addMap(gpa);
try addStringsToMap(ip, gpa, names_map, error_set_type.names);
const names_len = @intCast(u32, error_set_type.names.len);
try ip.extra.ensureUnusedCapacity(gpa, @typeInfo(ErrorSet).Struct.fields.len + names_len);
ip.items.appendAssumeCapacity(.{
.tag = .type_error_set,
.data = ip.addExtraAssumeCapacity(ErrorSet{
.names_len = names_len,
.names_map = names_map,
}),
});
ip.extra.appendSliceAssumeCapacity(@ptrCast([]const u32, error_set_type.names));
},
.inferred_error_set_type => |ies_index| {
ip.items.appendAssumeCapacity(.{
.tag = .type_inferred_error_set,
.data = @enumToInt(ies_index),
});
},
.simple_type => |simple_type| {
ip.items.appendAssumeCapacity(.{
.tag = .simple_type,
.data = @enumToInt(simple_type),
});
},
.simple_value => |simple_value| {
ip.items.appendAssumeCapacity(.{
.tag = .simple_value,
.data = @enumToInt(simple_value),
});
},
.undef => |ty| {
assert(ty != .none);
ip.items.appendAssumeCapacity(.{
.tag = .undef,
.data = @enumToInt(ty),
});
},
.runtime_value => |runtime_value| {
assert(runtime_value.ty == ip.typeOf(runtime_value.val));
ip.items.appendAssumeCapacity(.{
.tag = .runtime_value,
.data = @enumToInt(runtime_value.val),
});
},
.struct_type => |struct_type| {
ip.items.appendAssumeCapacity(if (struct_type.index.unwrap()) |i| .{
.tag = .type_struct,
.data = @enumToInt(i),
} else if (struct_type.namespace.unwrap()) |i| .{
.tag = .type_struct_ns,
.data = @enumToInt(i),
} else .{
.tag = .type_struct,
.data = @enumToInt(Module.Struct.OptionalIndex.none),
});
},
.anon_struct_type => |anon_struct_type| {
assert(anon_struct_type.types.len == anon_struct_type.values.len);
for (anon_struct_type.types) |elem| assert(elem != .none);
const fields_len = @intCast(u32, anon_struct_type.types.len);
if (anon_struct_type.names.len == 0) {
try ip.extra.ensureUnusedCapacity(
gpa,
@typeInfo(TypeStructAnon).Struct.fields.len + (fields_len * 2),
);
ip.items.appendAssumeCapacity(.{
.tag = .type_tuple_anon,
.data = ip.addExtraAssumeCapacity(TypeStructAnon{
.fields_len = fields_len,
}),
});
ip.extra.appendSliceAssumeCapacity(@ptrCast([]const u32, anon_struct_type.types));
ip.extra.appendSliceAssumeCapacity(@ptrCast([]const u32, anon_struct_type.values));
return @intToEnum(Index, ip.items.len - 1);
}
assert(anon_struct_type.names.len == anon_struct_type.types.len);
try ip.extra.ensureUnusedCapacity(
gpa,
@typeInfo(TypeStructAnon).Struct.fields.len + (fields_len * 3),
);
ip.items.appendAssumeCapacity(.{
.tag = .type_struct_anon,
.data = ip.addExtraAssumeCapacity(TypeStructAnon{
.fields_len = fields_len,
}),
});
ip.extra.appendSliceAssumeCapacity(@ptrCast([]const u32, anon_struct_type.types));
ip.extra.appendSliceAssumeCapacity(@ptrCast([]const u32, anon_struct_type.values));
ip.extra.appendSliceAssumeCapacity(@ptrCast([]const u32, anon_struct_type.names));
return @intToEnum(Index, ip.items.len - 1);
},
.union_type => |union_type| {
ip.items.appendAssumeCapacity(.{
.tag = switch (union_type.runtime_tag) {
.none => .type_union_untagged,
.safety => .type_union_safety,
.tagged => .type_union_tagged,
},
.data = @enumToInt(union_type.index),
});
},
.opaque_type => |opaque_type| {
ip.items.appendAssumeCapacity(.{
.tag = .type_opaque,
.data = try ip.addExtra(gpa, opaque_type),
});
},
.enum_type => |enum_type| {
assert(enum_type.tag_ty == .noreturn_type or ip.isIntegerType(enum_type.tag_ty));
for (enum_type.values) |value| assert(ip.typeOf(value) == enum_type.tag_ty);
assert(enum_type.names_map == .none);
assert(enum_type.values_map == .none);
switch (enum_type.tag_mode) {
.auto => {
const names_map = try ip.addMap(gpa);
try addStringsToMap(ip, gpa, names_map, enum_type.names);
const fields_len = @intCast(u32, enum_type.names.len);
try ip.extra.ensureUnusedCapacity(gpa, @typeInfo(EnumAuto).Struct.fields.len +
fields_len);
ip.items.appendAssumeCapacity(.{
.tag = .type_enum_auto,
.data = ip.addExtraAssumeCapacity(EnumAuto{
.decl = enum_type.decl,
.namespace = enum_type.namespace,
.names_map = names_map,
.fields_len = fields_len,
}),
});
ip.extra.appendSliceAssumeCapacity(@ptrCast([]const u32, enum_type.names));
return @intToEnum(Index, ip.items.len - 1);
},
.explicit => return finishGetEnum(ip, gpa, enum_type, .type_enum_explicit),
.nonexhaustive => return finishGetEnum(ip, gpa, enum_type, .type_enum_nonexhaustive),
}
},
.func_type => |func_type| {
assert(func_type.return_type != .none);
for (func_type.param_types) |param_type| assert(param_type != .none);
const params_len = @intCast(u32, func_type.param_types.len);
try ip.extra.ensureUnusedCapacity(gpa, @typeInfo(TypeFunction).Struct.fields.len +
params_len);
ip.items.appendAssumeCapacity(.{
.tag = .type_function,
.data = ip.addExtraAssumeCapacity(TypeFunction{
.params_len = params_len,
.return_type = func_type.return_type,
.comptime_bits = func_type.comptime_bits,
.noalias_bits = func_type.noalias_bits,
.flags = .{
.alignment = func_type.alignment,
.cc = func_type.cc,
.is_var_args = func_type.is_var_args,
.is_generic = func_type.is_generic,
.is_noinline = func_type.is_noinline,
.align_is_generic = func_type.align_is_generic,
.cc_is_generic = func_type.cc_is_generic,
.section_is_generic = func_type.section_is_generic,
.addrspace_is_generic = func_type.addrspace_is_generic,
},
}),
});
ip.extra.appendSliceAssumeCapacity(@ptrCast([]const u32, func_type.param_types));
},
.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, Variable{
.init = if (has_init) variable.init else variable.ty,
.decl = variable.decl,
.lib_name = variable.lib_name,
.flags = .{
.has_init = has_init,
.is_extern = variable.is_extern,
.is_const = variable.is_const,
.is_threadlocal = variable.is_threadlocal,
.is_weak_linkage = variable.is_weak_linkage,
},
}),
});
},
.extern_func => |extern_func| ip.items.appendAssumeCapacity(.{
.tag = .extern_func,
.data = try ip.addExtra(gpa, extern_func),
}),
.func => |func| ip.items.appendAssumeCapacity(.{
.tag = .func,
.data = try ip.addExtra(gpa, func),
}),
.ptr => |ptr| {
const ptr_type = ip.indexToKey(ptr.ty).ptr_type;
switch (ptr.len) {
.none => {
assert(ptr_type.size != .Slice);
switch (ptr.addr) {
.decl => |decl| ip.items.appendAssumeCapacity(.{
.tag = .ptr_decl,
.data = try ip.addExtra(gpa, PtrDecl{
.ty = ptr.ty,
.decl = decl,
}),
}),
.mut_decl => |mut_decl| ip.items.appendAssumeCapacity(.{
.tag = .ptr_mut_decl,
.data = try ip.addExtra(gpa, PtrMutDecl{
.ty = ptr.ty,
.decl = mut_decl.decl,
.runtime_index = mut_decl.runtime_index,
}),
}),
.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.elem_type,
) == .error_union_type),
.opt_payload => assert(ip.indexToKey(
ip.indexToKey(ip.typeOf(base)).ptr_type.elem_type,
) == .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 (base_ptr_type.size) {
.One => switch (ip.indexToKey(base_ptr_type.elem_type)) {
.array_type, .vector_type => assert(ptr.addr == .elem),
.anon_struct_type => |anon_struct_type| {
assert(ptr.addr == .field);
assert(base_index.index < anon_struct_type.types.len);
},
.struct_type => |struct_type| {
assert(ptr.addr == .field);
assert(base_index.index < ip.structPtrUnwrapConst(struct_type.index).?.fields.count());
},
.union_type => |union_type| {
assert(ptr.addr == .field);
assert(base_index.index < ip.unionPtrConst(union_type.index).fields.count());
},
.ptr_type => |slice_type| {
assert(ptr.addr == .field);
assert(slice_type.size == .Slice);
assert(base_index.index < 2);
},
else => unreachable,
},
.Many => assert(ptr.addr == .elem),
.Slice, .C => 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,
}),
});
},
}
},
else => {
assert(ptr_type.size == .Slice);
_ = ip.map.pop();
var new_key = key;
new_key.ptr.ty = ip.slicePtrType(ptr.ty);
new_key.ptr.len = .none;
assert(ip.indexToKey(new_key.ptr.ty).ptr_type.size == .Many);
const ptr_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 = .ptr_slice,
.data = try ip.addExtra(gpa, PtrSlice{
.ptr = ptr_index,
.len = ptr.len,
}),
});
},
}
assert(ptr.ty == ip.indexToKey(@intToEnum(Index, ip.items.len - 1)).ptr.ty);
},
.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 = @enumToInt(opt.ty),
} else .{
.tag = .opt_payload,
.data = @enumToInt(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 @intToEnum(Index, 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 = @intCast(u8, 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 = @intCast(u16, 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 = @intCast(u32, 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 = @bitCast(u32, casted),
});
break :b;
},
inline .u64, .i64 => |x| {
ip.items.appendAssumeCapacity(.{
.tag = .int_i32,
.data = @bitCast(u32, @intCast(i32, 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 = @bitCast(u32, 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 = @bitCast(u32, 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 @intToEnum(Index, 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 @intToEnum(Index, 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, TypeValue{
.ty = error_union.ty,
.val = payload,
}),
},
});
},
.enum_literal => |enum_literal| ip.items.appendAssumeCapacity(.{
.tag = .enum_literal,
.data = @enumToInt(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 => |enum_type| assert(ip.typeOf(enum_tag.int) == enum_type.tag_ty),
else => unreachable,
}
ip.items.appendAssumeCapacity(.{
.tag = .enum_tag,
.data = try ip.addExtra(gpa, enum_tag),
});
},
.float => |float| {
switch (float.ty) {
.f16_type => ip.items.appendAssumeCapacity(.{
.tag = .float_f16,
.data = @bitCast(u16, float.storage.f16),
}),
.f32_type => ip.items.appendAssumeCapacity(.{
.tag = .float_f32,
.data = @bitCast(u32, 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 + @boolToInt(sentinel != .none);
switch (aggregate.storage) {
.bytes => |bytes| {
assert(child == .u8_type);
if (bytes.len != len) {
assert(bytes.len == len_including_sentinel);
assert(bytes[len] == ip.indexToKey(sentinel).int.storage.u64);
}
},
.elems => |elems| {
if (elems.len != len) {
assert(elems.len == len_including_sentinel);
assert(elems[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 => |struct_type| {
for (
aggregate.storage.values(),
ip.structPtrUnwrapConst(struct_type.index).?.fields.values(),
) |elem, field| {
assert(ip.typeOf(elem) == field.ty.toIntern());
}
},
.anon_struct_type => |anon_struct_type| {
for (aggregate.storage.values(), anon_struct_type.types) |elem, ty| {
assert(ip.typeOf(elem) == ty);
}
},
else => unreachable,
}
if (len == 0) {
ip.items.appendAssumeCapacity(.{
.tag = .only_possible_value,
.data = @enumToInt(aggregate.ty),
});
return @intToEnum(Index, ip.items.len - 1);
}
switch (ty_key) {
.anon_struct_type => |anon_struct_type| opv: {
switch (aggregate.storage) {
.bytes => |bytes| for (anon_struct_type.values, 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,
elems,
)) break :opv,
.repeated_elem => |elem| for (anon_struct_type.values) |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 = @enumToInt(aggregate.ty),
});
return @intToEnum(Index, ip.items.len - 1);
},
else => {},
}
repeated: {
switch (aggregate.storage) {
.bytes => |bytes| for (bytes[1..@intCast(usize, len)]) |byte|
if (byte != bytes[0]) break :repeated,
.elems => |elems| for (elems[1..@intCast(usize, 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 @intToEnum(Index, ip.items.len - 1);
}
if (child == .u8_type) bytes: {
const string_bytes_index = ip.string_bytes.items.len;
try ip.string_bytes.ensureUnusedCapacity(gpa, len_including_sentinel + 1);
try ip.extra.ensureUnusedCapacity(gpa, @typeInfo(Bytes).Struct.fields.len);
switch (aggregate.storage) {
.bytes => |bytes| ip.string_bytes.appendSliceAssumeCapacity(bytes),
.elems => |elems| for (elems) |elem| switch (ip.indexToKey(elem)) {
.undef => {
ip.string_bytes.shrinkRetainingCapacity(string_bytes_index);
break :bytes;
},
.int => |int| ip.string_bytes.appendAssumeCapacity(
@intCast(u8, int.storage.u64),
),
else => unreachable,
},
.repeated_elem => |elem| switch (ip.indexToKey(elem)) {
.undef => break :bytes,
.int => |int| @memset(
ip.string_bytes.addManyAsSliceAssumeCapacity(len),
@intCast(u8, int.storage.u64),
),
else => unreachable,
},
}
if (sentinel != .none) ip.string_bytes.appendAssumeCapacity(
@intCast(u8, ip.indexToKey(sentinel).int.storage.u64),
);
const bytes = try ip.getOrPutTrailingString(gpa, len_including_sentinel);
ip.items.appendAssumeCapacity(.{
.tag = .bytes,
.data = ip.addExtraAssumeCapacity(Bytes{
.ty = aggregate.ty,
.bytes = bytes.toString(),
}),
});
return @intToEnum(Index, ip.items.len - 1);
}
try ip.extra.ensureUnusedCapacity(
gpa,
@typeInfo(Aggregate).Struct.fields.len + len_including_sentinel,
);
ip.items.appendAssumeCapacity(.{
.tag = .aggregate,
.data = ip.addExtraAssumeCapacity(Aggregate{
.ty = aggregate.ty,
}),
});
ip.extra.appendSliceAssumeCapacity(@ptrCast([]const u32, aggregate.storage.elems));
if (sentinel != .none) ip.extra.appendAssumeCapacity(@enumToInt(sentinel));
},
.un => |un| {
assert(un.ty != .none);
assert(un.tag != .none);
assert(un.val != .none);
ip.items.appendAssumeCapacity(.{
.tag = .union_value,
.data = try ip.addExtra(gpa, un),
});
},
.memoized_decl => |memoized_decl| {
assert(memoized_decl.val != .none);
ip.items.appendAssumeCapacity(.{
.tag = .memoized_decl,
.data = try ip.addExtra(gpa, memoized_decl),
});
},
.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 = @intCast(u32, memoized_call.arg_values.len),
.result = memoized_call.result,
}),
});
ip.extra.appendSliceAssumeCapacity(@ptrCast([]const u32, memoized_call.arg_values));
},
}
return @intToEnum(Index, ip.items.len - 1);
}
/// Provides API for completing an enum type after calling `getIncompleteEnum`.
pub const IncompleteEnumType = struct {
index: Index,
tag_ty_index: u32,
names_map: MapIndex,
names_start: u32,
values_map: OptionalMapIndex,
values_start: u32,
pub fn setTagType(self: @This(), ip: *InternPool, tag_ty: Index) void {
assert(tag_ty == .noreturn_type or ip.isIntegerType(tag_ty));
ip.extra.items[self.tag_ty_index] = @enumToInt(tag_ty);
}
/// Returns the already-existing field with the same name, if any.
pub fn addFieldName(
self: @This(),
ip: *InternPool,
gpa: Allocator,
name: NullTerminatedString,
) Allocator.Error!?u32 {
const map = &ip.maps.items[@enumToInt(self.names_map)];
const field_index = map.count();
const strings = ip.extra.items[self.names_start..][0..field_index];
const adapter: NullTerminatedString.Adapter = .{
.strings = @ptrCast([]const NullTerminatedString, strings),
};
const gop = try map.getOrPutAdapted(gpa, name, adapter);
if (gop.found_existing) return @intCast(u32, gop.index);
ip.extra.items[self.names_start + field_index] = @enumToInt(name);
return null;
}
/// Returns the already-existing field with the same value, if any.
/// Make sure the type of the value has the integer tag type of the enum.
pub fn addFieldValue(
self: @This(),
ip: *InternPool,
gpa: Allocator,
value: Index,
) Allocator.Error!?u32 {
assert(ip.typeOf(value) == @intToEnum(Index, ip.extra.items[self.tag_ty_index]));
const map = &ip.maps.items[@enumToInt(self.values_map.unwrap().?)];
const field_index = map.count();
const indexes = ip.extra.items[self.values_start..][0..field_index];
const adapter: Index.Adapter = .{
.indexes = @ptrCast([]const Index, indexes),
};
const gop = try map.getOrPutAdapted(gpa, value, adapter);
if (gop.found_existing) return @intCast(u32, gop.index);
ip.extra.items[self.values_start + field_index] = @enumToInt(value);
return null;
}
};
/// This is used to create an enum type in the `InternPool`, with the ability
/// to update the tag type, field names, and field values later.
pub fn getIncompleteEnum(
ip: *InternPool,
gpa: Allocator,
enum_type: Key.IncompleteEnumType,
) Allocator.Error!IncompleteEnumType {
switch (enum_type.tag_mode) {
.auto => return getIncompleteEnumAuto(ip, gpa, enum_type),
.explicit => return getIncompleteEnumExplicit(ip, gpa, enum_type, .type_enum_explicit),
.nonexhaustive => return getIncompleteEnumExplicit(ip, gpa, enum_type, .type_enum_nonexhaustive),
}
}
pub fn getIncompleteEnumAuto(
ip: *InternPool,
gpa: Allocator,
enum_type: Key.IncompleteEnumType,
) Allocator.Error!IncompleteEnumType {
// Although the integer tag type will not be stored in the `EnumAuto` struct,
// `InternPool` logic depends on it being present so that `typeOf` can be infallible.
// Ensure it is present here:
_ = try ip.get(gpa, .{ .int_type = .{
.bits = if (enum_type.fields_len == 0) 0 else std.math.log2_int_ceil(u32, enum_type.fields_len),
.signedness = .unsigned,
} });
// We must keep the map in sync with `items`. The hash and equality functions
// for enum types only look at the decl field, which is present even in
// an `IncompleteEnumType`.
const adapter: KeyAdapter = .{ .intern_pool = ip };
const gop = try ip.map.getOrPutAdapted(gpa, enum_type.toKey(), adapter);
assert(!gop.found_existing);
const names_map = try ip.addMap(gpa);
const extra_fields_len: u32 = @typeInfo(EnumAuto).Struct.fields.len;
try ip.extra.ensureUnusedCapacity(gpa, extra_fields_len + enum_type.fields_len);
try ip.items.ensureUnusedCapacity(gpa, 1);
const extra_index = ip.addExtraAssumeCapacity(EnumAuto{
.decl = enum_type.decl,
.namespace = enum_type.namespace,
.names_map = names_map,
.fields_len = enum_type.fields_len,
});
ip.items.appendAssumeCapacity(.{
.tag = .type_enum_auto,
.data = extra_index,
});
ip.extra.appendNTimesAssumeCapacity(@enumToInt(Index.none), enum_type.fields_len);
return .{
.index = @intToEnum(Index, ip.items.len - 1),
.tag_ty_index = undefined,
.names_map = names_map,
.names_start = extra_index + extra_fields_len,
.values_map = .none,
.values_start = undefined,
};
}
fn getIncompleteEnumExplicit(
ip: *InternPool,
gpa: Allocator,
enum_type: Key.IncompleteEnumType,
tag: Tag,
) Allocator.Error!IncompleteEnumType {
// We must keep the map in sync with `items`. The hash and equality functions
// for enum types only look at the decl field, which is present even in
// an `IncompleteEnumType`.
const adapter: KeyAdapter = .{ .intern_pool = ip };
const gop = try ip.map.getOrPutAdapted(gpa, enum_type.toKey(), adapter);
assert(!gop.found_existing);
const names_map = try ip.addMap(gpa);
const values_map: OptionalMapIndex = if (!enum_type.has_values) .none else m: {
const values_map = try ip.addMap(gpa);
break :m values_map.toOptional();
};
const reserved_len = enum_type.fields_len +
if (enum_type.has_values) enum_type.fields_len else 0;
const extra_fields_len: u32 = @typeInfo(EnumExplicit).Struct.fields.len;
try ip.extra.ensureUnusedCapacity(gpa, extra_fields_len + reserved_len);
try ip.items.ensureUnusedCapacity(gpa, 1);
const extra_index = ip.addExtraAssumeCapacity(EnumExplicit{
.decl = enum_type.decl,
.namespace = enum_type.namespace,
.int_tag_type = enum_type.tag_ty,
.fields_len = enum_type.fields_len,
.names_map = names_map,
.values_map = values_map,
});
ip.items.appendAssumeCapacity(.{
.tag = tag,
.data = extra_index,
});
// This is both fields and values (if present).
ip.extra.appendNTimesAssumeCapacity(@enumToInt(Index.none), reserved_len);
return .{
.index = @intToEnum(Index, ip.items.len - 1),
.tag_ty_index = extra_index + std.meta.fieldIndex(EnumExplicit, "int_tag_type").?,
.names_map = names_map,
.names_start = extra_index + extra_fields_len,
.values_map = values_map,
.values_start = extra_index + extra_fields_len + enum_type.fields_len,
};
}
pub fn finishGetEnum(
ip: *InternPool,
gpa: Allocator,
enum_type: Key.EnumType,
tag: Tag,
) Allocator.Error!Index {
const names_map = try ip.addMap(gpa);
try addStringsToMap(ip, gpa, names_map, enum_type.names);
const values_map: OptionalMapIndex = if (enum_type.values.len == 0) .none else m: {
const values_map = try ip.addMap(gpa);
try addIndexesToMap(ip, gpa, values_map, enum_type.values);
break :m values_map.toOptional();
};
const fields_len = @intCast(u32, enum_type.names.len);
try ip.extra.ensureUnusedCapacity(gpa, @typeInfo(EnumExplicit).Struct.fields.len +
fields_len);
ip.items.appendAssumeCapacity(.{
.tag = tag,
.data = ip.addExtraAssumeCapacity(EnumExplicit{
.decl = enum_type.decl,
.namespace = enum_type.namespace,
.int_tag_type = enum_type.tag_ty,
.fields_len = fields_len,
.names_map = names_map,
.values_map = values_map,
}),
});
ip.extra.appendSliceAssumeCapacity(@ptrCast([]const u32, enum_type.names));
ip.extra.appendSliceAssumeCapacity(@ptrCast([]const u32, enum_type.values));
return @intToEnum(Index, ip.items.len - 1);
}
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 @intToEnum(Index, index);
}
pub fn getAssumeExists(ip: *const InternPool, key: Key) Index {
return ip.getIfExists(key).?;
}
fn addStringsToMap(
ip: *InternPool,
gpa: Allocator,
map_index: MapIndex,
strings: []const NullTerminatedString,
) Allocator.Error!void {
const map = &ip.maps.items[@enumToInt(map_index)];
const adapter: NullTerminatedString.Adapter = .{ .strings = strings };
for (strings) |string| {
const gop = try map.getOrPutAdapted(gpa, string, adapter);
assert(!gop.found_existing);
}
}
fn addIndexesToMap(
ip: *InternPool,
gpa: Allocator,
map_index: MapIndex,
indexes: []const Index,
) Allocator.Error!void {
const map = &ip.maps.items[@enumToInt(map_index)];
const adapter: Index.Adapter = .{ .indexes = indexes };
for (indexes) |index| {
const gop = try map.getOrPutAdapted(gpa, index, adapter);
assert(!gop.found_existing);
}
}
fn addMap(ip: *InternPool, gpa: Allocator) Allocator.Error!MapIndex {
const ptr = try ip.maps.addOne(gpa);
ptr.* = .{};
return @intToEnum(MapIndex, ip.maps.items.len - 1);
}
/// This operation only happens under compile error conditions.
/// Leak the index until the next garbage collection.
pub fn remove(ip: *InternPool, index: Index) void {
_ = ip;
_ = index;
@setCold(true);
@panic("TODO this is a bit problematic to implement, could we maybe just never support a remove() operation on InternPool?");
}
fn addInt(ip: *InternPool, gpa: Allocator, ty: Index, tag: Tag, limbs: []const Limb) !void {
const limbs_len = @intCast(u32, 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 = @intCast(u32, ip.extra.items.len);
inline for (@typeInfo(@TypeOf(extra)).Struct.fields) |field| {
ip.extra.appendAssumeCapacity(switch (field.type) {
u32 => @field(extra, field.name),
Index => @enumToInt(@field(extra, field.name)),
Module.Decl.Index => @enumToInt(@field(extra, field.name)),
Module.Namespace.Index => @enumToInt(@field(extra, field.name)),
Module.Namespace.OptionalIndex => @enumToInt(@field(extra, field.name)),
Module.Fn.Index => @enumToInt(@field(extra, field.name)),
MapIndex => @enumToInt(@field(extra, field.name)),
OptionalMapIndex => @enumToInt(@field(extra, field.name)),
RuntimeIndex => @enumToInt(@field(extra, field.name)),
String => @enumToInt(@field(extra, field.name)),
NullTerminatedString => @enumToInt(@field(extra, field.name)),
OptionalNullTerminatedString => @enumToInt(@field(extra, field.name)),
i32 => @bitCast(u32, @field(extra, field.name)),
Pointer.Flags => @bitCast(u32, @field(extra, field.name)),
TypeFunction.Flags => @bitCast(u32, @field(extra, field.name)),
Pointer.PackedOffset => @bitCast(u32, @field(extra, field.name)),
Pointer.VectorIndex => @enumToInt(@field(extra, field.name)),
Variable.Flags => @bitCast(u32, @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 = @intCast(u32, 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 => @enumToInt(@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: InternPool, comptime T: type, index: usize) struct { data: T, end: usize } {
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) {
u32 => int32,
Index => @intToEnum(Index, int32),
Module.Decl.Index => @intToEnum(Module.Decl.Index, int32),
Module.Namespace.Index => @intToEnum(Module.Namespace.Index, int32),
Module.Namespace.OptionalIndex => @intToEnum(Module.Namespace.OptionalIndex, int32),
Module.Fn.Index => @intToEnum(Module.Fn.Index, int32),
MapIndex => @intToEnum(MapIndex, int32),
OptionalMapIndex => @intToEnum(OptionalMapIndex, int32),
RuntimeIndex => @intToEnum(RuntimeIndex, int32),
String => @intToEnum(String, int32),
NullTerminatedString => @intToEnum(NullTerminatedString, int32),
OptionalNullTerminatedString => @intToEnum(OptionalNullTerminatedString, int32),
i32 => @bitCast(i32, int32),
Pointer.Flags => @bitCast(Pointer.Flags, int32),
TypeFunction.Flags => @bitCast(TypeFunction.Flags, int32),
Pointer.PackedOffset => @bitCast(Pointer.PackedOffset, int32),
Pointer.VectorIndex => @intToEnum(Pointer.VectorIndex, int32),
Variable.Flags => @bitCast(Variable.Flags, int32),
else => @compileError("bad field type: " ++ @typeName(field.type)),
};
}
return .{
.data = result,
.end = index + fields.len,
};
}
fn extraData(ip: 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: 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)
@truncate(u32, host_int)
else
@truncate(u32, host_int >> 32);
@field(result, field.name) = switch (field.type) {
u32 => int32,
Index => @intToEnum(Index, 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: 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: 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 = @intCast(u32, @divExact(@ptrToInt(limbs.ptr) - @ptrToInt(host_slice.ptr), @sizeOf(Limb))),
.len = @intCast(u32, limbs.len),
};
}
/// This function converts Limb array indexes to a primitive slice type.
fn limbsIndexToSlice(ip: 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: InternPool, i: Index) Index {
return switch (ip.indexToKey(i)) {
.ptr_type => |ptr_type| ptr_type.elem_type,
.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: 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(@enumToInt(i));
switch (item.tag) {
.type_slice => return @intToEnum(Index, item.data),
else => unreachable, // not a slice type
}
}
/// Given a slice value, returns the value of the ptr field.
pub fn slicePtr(ip: InternPool, i: Index) Index {
const item = ip.items.get(@enumToInt(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: InternPool, i: Index) Index {
const item = ip.items.get(@enumToInt(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
/// * 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
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;
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,
} })
else if (ip.isPointerType(new_ty))
return ip.get(gpa, .{ .ptr = .{
.ty = new_ty,
.addr = .{ .int = .zero_usize },
.len = switch (ip.indexToKey(new_ty).ptr_type.size) {
.One, .Many, .C => .none,
.Slice => try ip.get(gpa, .{ .undef = .usize_type }),
},
} }),
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 => |func| if (ip.isFunctionType(new_ty))
return ip.get(gpa, .{ .func = .{
.ty = new_ty,
.index = func.index,
} }),
.int => |int| switch (ip.indexToKey(new_ty)) {
.enum_type => |enum_type| return ip.get(gpa, .{ .enum_tag = .{
.ty = new_ty,
.int = try ip.getCoerced(gpa, val, enum_type.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),
},
.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 => |enum_type| {
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[index]
else
try ip.get(gpa, .{ .int = .{
.ty = enum_type.tag_ty,
.storage = .{ .u64 = index },
} }),
} });
},
else => {},
},
.ptr => |ptr| if (ip.isPointerType(new_ty))
return ip.get(gpa, .{ .ptr = .{
.ty = new_ty,
.addr = ptr.addr,
.len = ptr.len,
} })
else if (ip.isIntegerType(new_ty))
switch (ptr.addr) {
.int => |int| return ip.getCoerced(gpa, int, new_ty),
else => {},
},
.opt => |opt| if (ip.isPointerType(new_ty))
return switch (opt.val) {
.none => try ip.get(gpa, .{ .ptr = .{
.ty = new_ty,
.addr = .{ .int = .zero_usize },
.len = switch (ip.indexToKey(new_ty).ptr_type.size) {
.One, .Many, .C => .none,
.Slice => try ip.get(gpa, .{ .undef = .usize_type }),
},
} }),
else => |payload| try ip.getCoerced(gpa, payload, new_ty),
},
.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,
} }),
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;
}
/// 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 indexToStructType(ip: InternPool, val: Index) Module.Struct.OptionalIndex {
assert(val != .none);
const tags = ip.items.items(.tag);
if (tags[@enumToInt(val)] != .type_struct) return .none;
const datas = ip.items.items(.data);
return @intToEnum(Module.Struct.Index, datas[@enumToInt(val)]).toOptional();
}
pub fn indexToUnionType(ip: InternPool, val: Index) Module.Union.OptionalIndex {
assert(val != .none);
const tags = ip.items.items(.tag);
switch (tags[@enumToInt(val)]) {
.type_union_tagged, .type_union_untagged, .type_union_safety => {},
else => return .none,
}
const datas = ip.items.items(.data);
return @intToEnum(Module.Union.Index, datas[@enumToInt(val)]).toOptional();
}
pub fn indexToFuncType(ip: InternPool, val: Index) ?Key.FuncType {
assert(val != .none);
const tags = ip.items.items(.tag);
const datas = ip.items.items(.data);
switch (tags[@enumToInt(val)]) {
.type_function => return indexToKeyFuncType(ip, datas[@enumToInt(val)]),
else => return null,
}
}
pub fn indexToFunc(ip: InternPool, val: Index) Module.Fn.OptionalIndex {
assert(val != .none);
const tags = ip.items.items(.tag);
if (tags[@enumToInt(val)] != .func) return .none;
const datas = ip.items.items(.data);
return ip.extraData(Key.Func, datas[@enumToInt(val)]).index.toOptional();
}
pub fn indexToInferredErrorSetType(ip: InternPool, val: Index) Module.Fn.InferredErrorSet.OptionalIndex {
assert(val != .none);
const tags = ip.items.items(.tag);
if (tags[@enumToInt(val)] != .type_inferred_error_set) return .none;
const datas = ip.items.items(.data);
return @intToEnum(Module.Fn.InferredErrorSet.Index, datas[@enumToInt(val)]).toOptional();
}
/// includes .comptime_int_type
pub fn isIntegerType(ip: 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,
.c_longdouble_type,
.comptime_int_type,
=> true,
else => ip.indexToKey(ty) == .int_type,
};
}
/// does not include .enum_literal_type
pub fn isEnumType(ip: 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 isFunctionType(ip: InternPool, ty: Index) bool {
return ip.indexToKey(ty) == .func_type;
}
pub fn isPointerType(ip: InternPool, ty: Index) bool {
return ip.indexToKey(ty) == .ptr_type;
}
pub fn isOptionalType(ip: InternPool, ty: Index) bool {
return ip.indexToKey(ty) == .opt_type;
}
/// includes .inferred_error_set_type
pub fn isErrorSetType(ip: InternPool, ty: Index) bool {
return ty == .anyerror_type or switch (ip.indexToKey(ty)) {
.error_set_type, .inferred_error_set_type => true,
else => false,
};
}
pub fn isInferredErrorSetType(ip: InternPool, ty: Index) bool {
return ip.indexToKey(ty) == .inferred_error_set_type;
}
pub fn isErrorUnionType(ip: InternPool, ty: Index) bool {
return ip.indexToKey(ty) == .error_union_type;
}
pub fn isAggregateType(ip: InternPool, ty: Index) bool {
return switch (ip.indexToKey(ty)) {
.array_type, .vector_type, .anon_struct_type, .struct_type => true,
else => false,
};
}
/// The is only legal because the initializer is not part of the hash.
pub fn mutateVarInit(ip: *InternPool, index: Index, init_index: Index) void {
assert(ip.items.items(.tag)[@enumToInt(index)] == .variable);
const field_index = inline for (@typeInfo(Variable).Struct.fields, 0..) |field, field_index| {
if (comptime std.mem.eql(u8, field.name, "init")) break field_index;
} else unreachable;
ip.extra.items[ip.items.items(.data)[@enumToInt(index)] + field_index] = @enumToInt(init_index);
}
pub fn dump(ip: InternPool) void {
dumpFallible(ip, std.heap.page_allocator) catch return;
}
fn dumpFallible(ip: 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;
// TODO: fields size is not taken into account
const structs_size = ip.allocated_structs.len *
(@sizeOf(Module.Struct) + @sizeOf(Module.Namespace) + @sizeOf(Module.Decl));
const unions_size = ip.allocated_unions.len *
(@sizeOf(Module.Union) + @sizeOf(Module.Namespace) + @sizeOf(Module.Decl));
const funcs_size = ip.allocated_funcs.len *
(@sizeOf(Module.Fn) + @sizeOf(Module.Decl));
// TODO: map overhead size is not taken into account
const total_size = @sizeOf(InternPool) + items_size + extra_size + limbs_size +
structs_size + unions_size + funcs_size;
std.debug.print(
\\InternPool size: {d} bytes
\\ {d} items: {d} bytes
\\ {d} extra: {d} bytes
\\ {d} limbs: {d} bytes
\\ {d} structs: {d} bytes
\\ {d} unions: {d} bytes
\\ {d} funcs: {d} bytes
\\
, .{
total_size,
ip.items.len,
items_size,
ip.extra.items.len,
extra_size,
ip.limbs.items.len,
limbs_size,
ip.allocated_structs.len,
structs_size,
ip.allocated_unions.len,
unions_size,
ip.allocated_funcs.len,
funcs_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) {
.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(Pointer),
.type_slice => 0,
.type_optional => 0,
.type_anyframe => 0,
.type_error_union => @sizeOf(Key.ErrorUnionType),
.type_error_set => b: {
const info = ip.extraData(ErrorSet, data);
break :b @sizeOf(ErrorSet) + (@sizeOf(u32) * info.names_len);
},
.type_inferred_error_set => @sizeOf(Module.Fn.InferredErrorSet),
.type_enum_explicit, .type_enum_nonexhaustive => @sizeOf(EnumExplicit),
.type_enum_auto => @sizeOf(EnumAuto),
.type_opaque => @sizeOf(Key.OpaqueType),
.type_struct => b: {
const struct_index = @intToEnum(Module.Struct.Index, data);
const struct_obj = ip.structPtrConst(struct_index);
break :b @sizeOf(Module.Struct) +
@sizeOf(Module.Namespace) +
@sizeOf(Module.Decl) +
(struct_obj.fields.count() * @sizeOf(Module.Struct.Field));
},
.type_struct_ns => @sizeOf(Module.Namespace),
.type_struct_anon => b: {
const info = ip.extraData(TypeStructAnon, data);
break :b @sizeOf(TypeStructAnon) + (@sizeOf(u32) * 3 * info.fields_len);
},
.type_tuple_anon => b: {
const info = ip.extraData(TypeStructAnon, data);
break :b @sizeOf(TypeStructAnon) + (@sizeOf(u32) * 2 * info.fields_len);
},
.type_union_tagged,
.type_union_untagged,
.type_union_safety,
=> @sizeOf(Module.Union) + @sizeOf(Module.Namespace) + @sizeOf(Module.Decl),
.type_function => b: {
const info = ip.extraData(TypeFunction, data);
break :b @sizeOf(TypeFunction) + (@sizeOf(Index) * info.params_len);
},
.undef => 0,
.runtime_value => 0,
.simple_type => 0,
.simple_value => 0,
.ptr_decl => @sizeOf(PtrDecl),
.ptr_mut_decl => @sizeOf(PtrMutDecl),
.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 => 0,
.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(TypeValue),
.enum_literal => 0,
.enum_tag => @sizeOf(Key.EnumTag),
.bytes => b: {
const info = ip.extraData(Bytes, data);
const len = @intCast(u32, ip.aggregateTypeLenIncludingSentinel(info.ty));
break :b @sizeOf(Bytes) + len +
@boolToInt(ip.string_bytes.items[@enumToInt(info.bytes) + len - 1] != 0);
},
.aggregate => b: {
const info = ip.extraData(Aggregate, data);
const fields_len = @intCast(u32, ip.aggregateTypeLenIncludingSentinel(info.ty));
break :b @sizeOf(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(Variable) + @sizeOf(Module.Decl),
.extern_func => @sizeOf(Key.ExternFunc) + @sizeOf(Module.Decl),
.func => @sizeOf(Key.Func) + @sizeOf(Module.Fn) + @sizeOf(Module.Decl),
.only_possible_value => 0,
.union_value => @sizeOf(Key.Union),
.memoized_decl => @sizeOf(Key.MemoizedDecl),
.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;
}
};
counts.sort(SortContext{ .map = &counts });
const len = @min(25, counts.count());
std.debug.print(" top 25 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,
});
}
}
pub fn structPtr(ip: *InternPool, index: Module.Struct.Index) *Module.Struct {
return ip.allocated_structs.at(@enumToInt(index));
}
pub fn structPtrConst(ip: InternPool, index: Module.Struct.Index) *const Module.Struct {
return ip.allocated_structs.at(@enumToInt(index));
}
pub fn structPtrUnwrapConst(ip: InternPool, index: Module.Struct.OptionalIndex) ?*const Module.Struct {
return structPtrConst(ip, index.unwrap() orelse return null);
}
pub fn unionPtr(ip: *InternPool, index: Module.Union.Index) *Module.Union {
return ip.allocated_unions.at(@enumToInt(index));
}
pub fn unionPtrConst(ip: InternPool, index: Module.Union.Index) *const Module.Union {
return ip.allocated_unions.at(@enumToInt(index));
}
pub fn funcPtr(ip: *InternPool, index: Module.Fn.Index) *Module.Fn {
return ip.allocated_funcs.at(@enumToInt(index));
}
pub fn funcPtrConst(ip: InternPool, index: Module.Fn.Index) *const Module.Fn {
return ip.allocated_funcs.at(@enumToInt(index));
}
pub fn inferredErrorSetPtr(ip: *InternPool, index: Module.Fn.InferredErrorSet.Index) *Module.Fn.InferredErrorSet {
return ip.allocated_inferred_error_sets.at(@enumToInt(index));
}
pub fn inferredErrorSetPtrConst(ip: InternPool, index: Module.Fn.InferredErrorSet.Index) *const Module.Fn.InferredErrorSet {
return ip.allocated_inferred_error_sets.at(@enumToInt(index));
}
pub fn createStruct(
ip: *InternPool,
gpa: Allocator,
initialization: Module.Struct,
) Allocator.Error!Module.Struct.Index {
if (ip.structs_free_list.popOrNull()) |index| return index;
const ptr = try ip.allocated_structs.addOne(gpa);
ptr.* = initialization;
return @intToEnum(Module.Struct.Index, ip.allocated_structs.len - 1);
}
pub fn destroyStruct(ip: *InternPool, gpa: Allocator, index: Module.Struct.Index) void {
ip.structPtr(index).* = undefined;
ip.structs_free_list.append(gpa, index) catch {
// In order to keep `destroyStruct` a non-fallible function, we ignore memory
// allocation failures here, instead leaking the Struct until garbage collection.
};
}
pub fn createUnion(
ip: *InternPool,
gpa: Allocator,
initialization: Module.Union,
) Allocator.Error!Module.Union.Index {
if (ip.unions_free_list.popOrNull()) |index| return index;
const ptr = try ip.allocated_unions.addOne(gpa);
ptr.* = initialization;
return @intToEnum(Module.Union.Index, ip.allocated_unions.len - 1);
}
pub fn destroyUnion(ip: *InternPool, gpa: Allocator, index: Module.Union.Index) void {
ip.unionPtr(index).* = undefined;
ip.unions_free_list.append(gpa, index) catch {
// In order to keep `destroyUnion` a non-fallible function, we ignore memory
// allocation failures here, instead leaking the Union until garbage collection.
};
}
pub fn createFunc(
ip: *InternPool,
gpa: Allocator,
initialization: Module.Fn,
) Allocator.Error!Module.Fn.Index {
if (ip.funcs_free_list.popOrNull()) |index| return index;
const ptr = try ip.allocated_funcs.addOne(gpa);
ptr.* = initialization;
return @intToEnum(Module.Fn.Index, ip.allocated_funcs.len - 1);
}
pub fn destroyFunc(ip: *InternPool, gpa: Allocator, index: Module.Fn.Index) void {
ip.funcPtr(index).* = undefined;
ip.funcs_free_list.append(gpa, index) catch {
// In order to keep `destroyFunc` a non-fallible function, we ignore memory
// allocation failures here, instead leaking the Union until garbage collection.
};
}
pub fn createInferredErrorSet(
ip: *InternPool,
gpa: Allocator,
initialization: Module.Fn.InferredErrorSet,
) Allocator.Error!Module.Fn.InferredErrorSet.Index {
if (ip.inferred_error_sets_free_list.popOrNull()) |index| return index;
const ptr = try ip.allocated_inferred_error_sets.addOne(gpa);
ptr.* = initialization;
return @intToEnum(Module.Fn.InferredErrorSet.Index, ip.allocated_inferred_error_sets.len - 1);
}
pub fn destroyInferredErrorSet(ip: *InternPool, gpa: Allocator, index: Module.Fn.InferredErrorSet.Index) void {
ip.inferredErrorSetPtr(index).* = undefined;
ip.inferred_error_sets_free_list.append(gpa, index) catch {
// In order to keep `destroyInferredErrorSet` a non-fallible function, we ignore memory
// allocation failures here, instead leaking the InferredErrorSet until garbage collection.
};
}
pub fn getOrPutString(
ip: *InternPool,
gpa: Allocator,
s: []const u8,
) Allocator.Error!NullTerminatedString {
const string_bytes = &ip.string_bytes;
try string_bytes.ensureUnusedCapacity(gpa, s.len + 1);
string_bytes.appendSliceAssumeCapacity(s);
string_bytes.appendAssumeCapacity(0);
return ip.getOrPutTrailingString(gpa, s.len + 1);
}
/// 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 = @intCast(u32, 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 @intToEnum(NullTerminatedString, gop.key_ptr.*);
} else {
gop.key_ptr.* = str_index;
string_bytes.appendAssumeCapacity(0);
return @intToEnum(NullTerminatedString, str_index);
}
}
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 @intToEnum(NullTerminatedString, index).toOptional();
} else {
return .none;
}
}
pub fn stringToSlice(ip: InternPool, s: NullTerminatedString) [:0]const u8 {
const string_bytes = ip.string_bytes.items;
const start = @enumToInt(s);
var end: usize = start;
while (string_bytes[end] != 0) end += 1;
return string_bytes[start..end :0];
}
pub fn stringToSliceUnwrap(ip: InternPool, s: OptionalNullTerminatedString) ?[:0]const u8 {
return ip.stringToSlice(s.unwrap() orelse return null);
}
pub fn typeOf(ip: 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) {
.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,
.anyerror_void_error_union_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,
.var_args_param_type, .none => unreachable,
_ => ip.indexToKey(index).typeOf(),
};
}
/// Assumes that the enum's field indexes equal its value tags.
pub fn toEnum(ip: InternPool, comptime E: type, i: Index) E {
const int = ip.indexToKey(i).enum_tag.int;
return @intToEnum(E, ip.indexToKey(int).int.storage.u64);
}
pub fn aggregateTypeLen(ip: InternPool, ty: Index) u64 {
return switch (ip.indexToKey(ty)) {
.struct_type => |struct_type| ip.structPtrConst(struct_type.index.unwrap() orelse return 0).fields.count(),
.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: InternPool, ty: Index) u64 {
return switch (ip.indexToKey(ty)) {
.struct_type => |struct_type| ip.structPtrConst(struct_type.index.unwrap() orelse return 0).fields.count(),
.anon_struct_type => |anon_struct_type| anon_struct_type.types.len,
.array_type => |array_type| array_type.len + @boolToInt(array_type.sentinel != .none),
.vector_type => |vector_type| vector_type.len,
else => unreachable,
};
}
pub fn isNoReturn(ip: InternPool, ty: Index) bool {
return switch (ty) {
.noreturn_type => true,
else => switch (ip.indexToKey(ty)) {
.error_set_type => |error_set_type| error_set_type.names.len == 0,
.enum_type => |enum_type| enum_type.names.len == 0,
else => false,
},
};
}
/// 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: InternPool, index: Index) error{GenericPoison}!std.builtin.TypeId {
return switch (index) {
.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 => .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,
.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)[@enumToInt(index)]) {
.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 => .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_ns,
.type_struct_anon,
.type_tuple_anon,
=> .Struct,
.type_union_tagged,
.type_union_untagged,
.type_union_safety,
=> .Union,
.type_function => .Fn,
// values, not types
.undef,
.runtime_value,
.simple_value,
.ptr_decl,
.ptr_mut_decl,
.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,
.only_possible_value,
.union_value,
.bytes,
.aggregate,
.repeated,
// memoization, not types
.memoized_decl,
.memoized_call,
=> unreachable,
},
.none => unreachable, // special tag
};
}