zig/src/mutable_value.zig

577 lines
26 KiB
Zig

const std = @import("std");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const Zcu = @import("Zcu.zig");
const InternPool = @import("InternPool.zig");
const Type = @import("Type.zig");
const Value = @import("Value.zig");
/// We use a tagged union here because while it wastes a few bytes for some tags, having a fixed
/// size for the type makes the common `aggregate` representation more efficient.
/// For aggregates, the sentinel value, if any, *is* stored.
pub const MutableValue = union(enum) {
/// An interned value.
interned: InternPool.Index,
/// An error union value which is a payload (not an error).
eu_payload: SubValue,
/// An optional value which is a payload (not `null`).
opt_payload: SubValue,
/// An aggregate consisting of a single repeated value.
repeated: SubValue,
/// An aggregate of `u8` consisting of "plain" bytes (no lazy or undefined elements).
bytes: Bytes,
/// An aggregate with arbitrary sub-values.
aggregate: Aggregate,
/// A slice, containing a pointer and length.
slice: Slice,
/// An instance of a union.
un: Union,
pub const SubValue = struct {
ty: InternPool.Index,
child: *MutableValue,
};
pub const Bytes = struct {
ty: InternPool.Index,
data: []u8,
};
pub const Aggregate = struct {
ty: InternPool.Index,
elems: []MutableValue,
};
pub const Slice = struct {
ty: InternPool.Index,
/// Must have the appropriate many-ptr type.
/// TODO: we want this to be an `InternPool.Index`, but `Sema.beginComptimePtrMutation` doesn't support it.
ptr: *MutableValue,
/// Must be of type `usize`.
/// TODO: we want this to be an `InternPool.Index`, but `Sema.beginComptimePtrMutation` doesn't support it.
len: *MutableValue,
};
pub const Union = struct {
ty: InternPool.Index,
tag: InternPool.Index,
payload: *MutableValue,
};
pub fn intern(mv: MutableValue, pt: Zcu.PerThread, arena: Allocator) Allocator.Error!Value {
return Value.fromInterned(switch (mv) {
.interned => |ip_index| ip_index,
.eu_payload => |sv| try pt.intern(.{ .error_union = .{
.ty = sv.ty,
.val = .{ .payload = (try sv.child.intern(pt, arena)).toIntern() },
} }),
.opt_payload => |sv| try pt.intern(.{ .opt = .{
.ty = sv.ty,
.val = (try sv.child.intern(pt, arena)).toIntern(),
} }),
.repeated => |sv| try pt.intern(.{ .aggregate = .{
.ty = sv.ty,
.storage = .{ .repeated_elem = (try sv.child.intern(pt, arena)).toIntern() },
} }),
.bytes => |b| try pt.intern(.{ .aggregate = .{
.ty = b.ty,
.storage = .{ .bytes = try pt.zcu.intern_pool.getOrPutString(pt.zcu.gpa, pt.tid, b.data, .maybe_embedded_nulls) },
} }),
.aggregate => |a| {
const elems = try arena.alloc(InternPool.Index, a.elems.len);
for (a.elems, elems) |mut_elem, *interned_elem| {
interned_elem.* = (try mut_elem.intern(pt, arena)).toIntern();
}
return Value.fromInterned(try pt.intern(.{ .aggregate = .{
.ty = a.ty,
.storage = .{ .elems = elems },
} }));
},
.slice => |s| try pt.intern(.{ .slice = .{
.ty = s.ty,
.ptr = (try s.ptr.intern(pt, arena)).toIntern(),
.len = (try s.len.intern(pt, arena)).toIntern(),
} }),
.un => |u| try pt.internUnion(.{
.ty = u.ty,
.tag = u.tag,
.val = (try u.payload.intern(pt, arena)).toIntern(),
}),
});
}
/// Un-interns the top level of this `MutableValue`, if applicable.
/// * Non-error error unions use `eu_payload`
/// * Non-null optionals use `eu_payload
/// * Slices use `slice`
/// * Unions use `un`
/// * Aggregates use `repeated` or `bytes` or `aggregate`
/// If `!allow_bytes`, the `bytes` representation will not be used.
/// If `!allow_repeated`, the `repeated` representation will not be used.
pub fn unintern(
mv: *MutableValue,
pt: Zcu.PerThread,
arena: Allocator,
allow_bytes: bool,
allow_repeated: bool,
) Allocator.Error!void {
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
switch (mv.*) {
.interned => |ip_index| switch (ip.indexToKey(ip_index)) {
.opt => |opt| if (opt.val != .none) {
const mut_payload = try arena.create(MutableValue);
mut_payload.* = .{ .interned = opt.val };
mv.* = .{ .opt_payload = .{
.ty = opt.ty,
.child = mut_payload,
} };
},
.error_union => |eu| switch (eu.val) {
.err_name => {},
.payload => |payload| {
const mut_payload = try arena.create(MutableValue);
mut_payload.* = .{ .interned = payload };
mv.* = .{ .eu_payload = .{
.ty = eu.ty,
.child = mut_payload,
} };
},
},
.slice => |slice| {
const ptr = try arena.create(MutableValue);
const len = try arena.create(MutableValue);
ptr.* = .{ .interned = slice.ptr };
len.* = .{ .interned = slice.len };
mv.* = .{ .slice = .{
.ty = slice.ty,
.ptr = ptr,
.len = len,
} };
},
.un => |un| {
const payload = try arena.create(MutableValue);
payload.* = .{ .interned = un.val };
mv.* = .{ .un = .{
.ty = un.ty,
.tag = un.tag,
.payload = payload,
} };
},
.aggregate => |agg| switch (agg.storage) {
.bytes => |bytes| {
const len: usize = @intCast(ip.aggregateTypeLenIncludingSentinel(agg.ty));
assert(ip.childType(agg.ty) == .u8_type);
if (allow_bytes) {
const arena_bytes = try arena.alloc(u8, len);
@memcpy(arena_bytes, bytes.toSlice(len, ip));
mv.* = .{ .bytes = .{
.ty = agg.ty,
.data = arena_bytes,
} };
} else {
const mut_elems = try arena.alloc(MutableValue, len);
for (bytes.toSlice(len, ip), mut_elems) |b, *mut_elem| {
mut_elem.* = .{ .interned = try pt.intern(.{ .int = .{
.ty = .u8_type,
.storage = .{ .u64 = b },
} }) };
}
mv.* = .{ .aggregate = .{
.ty = agg.ty,
.elems = mut_elems,
} };
}
},
.elems => |elems| {
assert(elems.len == ip.aggregateTypeLenIncludingSentinel(agg.ty));
const mut_elems = try arena.alloc(MutableValue, elems.len);
for (elems, mut_elems) |interned_elem, *mut_elem| {
mut_elem.* = .{ .interned = interned_elem };
}
mv.* = .{ .aggregate = .{
.ty = agg.ty,
.elems = mut_elems,
} };
},
.repeated_elem => |val| {
if (allow_repeated) {
const repeated_val = try arena.create(MutableValue);
repeated_val.* = .{ .interned = val };
mv.* = .{ .repeated = .{
.ty = agg.ty,
.child = repeated_val,
} };
} else {
const len = ip.aggregateTypeLenIncludingSentinel(agg.ty);
const mut_elems = try arena.alloc(MutableValue, @intCast(len));
@memset(mut_elems, .{ .interned = val });
mv.* = .{ .aggregate = .{
.ty = agg.ty,
.elems = mut_elems,
} };
}
},
},
.undef => |ty_ip| switch (Type.fromInterned(ty_ip).zigTypeTag(zcu)) {
.@"struct", .array, .vector => |type_tag| {
const ty = Type.fromInterned(ty_ip);
const opt_sent = ty.sentinel(zcu);
if (type_tag == .@"struct" or opt_sent != null or !allow_repeated) {
const len_no_sent = ip.aggregateTypeLen(ty_ip);
const elems = try arena.alloc(MutableValue, @intCast(len_no_sent + @intFromBool(opt_sent != null)));
switch (type_tag) {
.array, .vector => {
const elem_ty = ip.childType(ty_ip);
const undef_elem = try pt.intern(.{ .undef = elem_ty });
@memset(elems[0..@intCast(len_no_sent)], .{ .interned = undef_elem });
},
.@"struct" => for (elems[0..@intCast(len_no_sent)], 0..) |*mut_elem, i| {
const field_ty = ty.fieldType(i, zcu).toIntern();
mut_elem.* = .{ .interned = try pt.intern(.{ .undef = field_ty }) };
},
else => unreachable,
}
if (opt_sent) |s| elems[@intCast(len_no_sent)] = .{ .interned = s.toIntern() };
mv.* = .{ .aggregate = .{
.ty = ty_ip,
.elems = elems,
} };
} else {
const repeated_val = try arena.create(MutableValue);
repeated_val.* = .{
.interned = try pt.intern(.{ .undef = ip.childType(ty_ip) }),
};
mv.* = .{ .repeated = .{
.ty = ty_ip,
.child = repeated_val,
} };
}
},
.@"union" => {
const payload = try arena.create(MutableValue);
const backing_ty = try Type.fromInterned(ty_ip).unionBackingType(pt);
payload.* = .{ .interned = try pt.intern(.{ .undef = backing_ty.toIntern() }) };
mv.* = .{ .un = .{
.ty = ty_ip,
.tag = .none,
.payload = payload,
} };
},
.pointer => {
const ptr_ty = ip.indexToKey(ty_ip).ptr_type;
if (ptr_ty.flags.size != .slice) return;
const ptr = try arena.create(MutableValue);
const len = try arena.create(MutableValue);
ptr.* = .{ .interned = try pt.intern(.{ .undef = ip.slicePtrType(ty_ip) }) };
len.* = .{ .interned = .undef_usize };
mv.* = .{ .slice = .{
.ty = ty_ip,
.ptr = ptr,
.len = len,
} };
},
else => {},
},
else => {},
},
.bytes => |bytes| if (!allow_bytes) {
const elems = try arena.alloc(MutableValue, bytes.data.len);
for (bytes.data, elems) |byte, *interned_byte| {
interned_byte.* = .{ .interned = try pt.intern(.{ .int = .{
.ty = .u8_type,
.storage = .{ .u64 = byte },
} }) };
}
mv.* = .{ .aggregate = .{
.ty = bytes.ty,
.elems = elems,
} };
},
else => {},
}
}
/// Get a pointer to the `MutableValue` associated with a field/element.
/// The returned pointer can be safety mutated through to modify the field value.
/// The returned pointer is valid until the representation of `mv` changes.
pub fn elem(
mv: *MutableValue,
pt: Zcu.PerThread,
arena: Allocator,
field_idx: usize,
) Allocator.Error!*MutableValue {
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
// Convert to the `aggregate` representation.
switch (mv.*) {
.eu_payload, .opt_payload, .un => unreachable,
.interned => {
try mv.unintern(pt, arena, false, false);
},
.bytes => |bytes| {
const elems = try arena.alloc(MutableValue, bytes.data.len);
for (bytes.data, elems) |byte, *interned_byte| {
interned_byte.* = .{ .interned = try pt.intern(.{ .int = .{
.ty = .u8_type,
.storage = .{ .u64 = byte },
} }) };
}
mv.* = .{ .aggregate = .{
.ty = bytes.ty,
.elems = elems,
} };
},
.repeated => |repeated| {
const len = ip.aggregateTypeLenIncludingSentinel(repeated.ty);
const elems = try arena.alloc(MutableValue, @intCast(len));
@memset(elems, repeated.child.*);
mv.* = .{ .aggregate = .{
.ty = repeated.ty,
.elems = elems,
} };
},
.slice, .aggregate => {},
}
switch (mv.*) {
.aggregate => |*agg| return &agg.elems[field_idx],
.slice => |*slice| return switch (field_idx) {
Value.slice_ptr_index => slice.ptr,
Value.slice_len_index => slice.len,
else => unreachable,
},
else => unreachable,
}
}
/// Modify a single field of a `MutableValue` which represents an aggregate or slice, leaving others
/// untouched. When an entire field must be modified, this should be used in preference to `elemPtr`
/// to allow for an optimal representation.
/// For slices, uses `Value.slice_ptr_index` and `Value.slice_len_index`.
pub fn setElem(
mv: *MutableValue,
pt: Zcu.PerThread,
arena: Allocator,
field_idx: usize,
field_val: MutableValue,
) Allocator.Error!void {
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
const is_trivial_int = field_val.isTrivialInt(zcu);
try mv.unintern(pt, arena, is_trivial_int, true);
switch (mv.*) {
.interned,
.eu_payload,
.opt_payload,
.un,
=> unreachable,
.slice => |*s| switch (field_idx) {
Value.slice_ptr_index => s.ptr.* = field_val,
Value.slice_len_index => s.len.* = field_val,
else => unreachable,
},
.bytes => |b| {
assert(is_trivial_int);
assert(field_val.typeOf(zcu).toIntern() == .u8_type);
b.data[field_idx] = @intCast(Value.fromInterned(field_val.interned).toUnsignedInt(zcu));
},
.repeated => |r| {
if (field_val.eqlTrivial(r.child.*)) return;
// We must switch to either the `aggregate` or the `bytes` representation.
const len_inc_sent = ip.aggregateTypeLenIncludingSentinel(r.ty);
if (Type.fromInterned(r.ty).zigTypeTag(zcu) != .@"struct" and
is_trivial_int and
Type.fromInterned(r.ty).childType(zcu).toIntern() == .u8_type and
r.child.isTrivialInt(zcu))
{
// We can use the `bytes` representation.
const bytes = try arena.alloc(u8, @intCast(len_inc_sent));
const repeated_byte = Value.fromInterned(r.child.interned).toUnsignedInt(zcu);
@memset(bytes, @intCast(repeated_byte));
bytes[field_idx] = @intCast(Value.fromInterned(field_val.interned).toUnsignedInt(zcu));
mv.* = .{ .bytes = .{
.ty = r.ty,
.data = bytes,
} };
} else {
// We must use the `aggregate` representation.
const mut_elems = try arena.alloc(MutableValue, @intCast(len_inc_sent));
@memset(mut_elems, r.child.*);
mut_elems[field_idx] = field_val;
mv.* = .{ .aggregate = .{
.ty = r.ty,
.elems = mut_elems,
} };
}
},
.aggregate => |a| {
a.elems[field_idx] = field_val;
const is_struct = Type.fromInterned(a.ty).zigTypeTag(zcu) == .@"struct";
// Attempt to switch to a more efficient representation.
const is_repeated = for (a.elems) |e| {
if (!e.eqlTrivial(field_val)) break false;
} else true;
if (!is_struct and is_repeated) {
// Switch to `repeated` repr
const mut_repeated = try arena.create(MutableValue);
mut_repeated.* = field_val;
mv.* = .{ .repeated = .{
.ty = a.ty,
.child = mut_repeated,
} };
} else if (!is_struct and is_trivial_int and Type.fromInterned(a.ty).childType(zcu).toIntern() == .u8_type) {
// See if we can switch to `bytes` repr
for (a.elems) |e| {
switch (e) {
else => break,
.interned => |ip_index| switch (ip.indexToKey(ip_index)) {
else => break,
.int => |int| switch (int.storage) {
.u64, .i64, .big_int => {},
.lazy_align, .lazy_size => break,
},
},
}
} else {
const bytes = try arena.alloc(u8, a.elems.len);
for (a.elems, bytes) |elem_val, *b| {
b.* = @intCast(Value.fromInterned(elem_val.interned).toUnsignedInt(zcu));
}
mv.* = .{ .bytes = .{
.ty = a.ty,
.data = bytes,
} };
}
}
},
}
}
/// Get the value of a single field of a `MutableValue` which represents an aggregate or slice.
/// For slices, uses `Value.slice_ptr_index` and `Value.slice_len_index`.
pub fn getElem(
mv: MutableValue,
pt: Zcu.PerThread,
field_idx: usize,
) Allocator.Error!MutableValue {
return switch (mv) {
.eu_payload,
.opt_payload,
=> unreachable,
.interned => |ip_index| {
const ty = Type.fromInterned(pt.zcu.intern_pool.typeOf(ip_index));
switch (ty.zigTypeTag(pt.zcu)) {
.array, .vector => return .{ .interned = (try Value.fromInterned(ip_index).elemValue(pt, field_idx)).toIntern() },
.@"struct", .@"union" => return .{ .interned = (try Value.fromInterned(ip_index).fieldValue(pt, field_idx)).toIntern() },
.pointer => {
assert(ty.isSlice(pt.zcu));
return switch (field_idx) {
Value.slice_ptr_index => .{ .interned = Value.fromInterned(ip_index).slicePtr(pt.zcu).toIntern() },
Value.slice_len_index => .{ .interned = switch (pt.zcu.intern_pool.indexToKey(ip_index)) {
.undef => .undef_usize,
.slice => |s| s.len,
else => unreachable,
} },
else => unreachable,
};
},
else => unreachable,
}
},
.un => |un| {
// TODO assert the tag is correct
return un.payload.*;
},
.slice => |s| switch (field_idx) {
Value.slice_ptr_index => s.ptr.*,
Value.slice_len_index => s.len.*,
else => unreachable,
},
.bytes => |b| .{ .interned = try pt.intern(.{ .int = .{
.ty = .u8_type,
.storage = .{ .u64 = b.data[field_idx] },
} }) },
.repeated => |r| r.child.*,
.aggregate => |a| a.elems[field_idx],
};
}
fn isTrivialInt(mv: MutableValue, zcu: *Zcu) bool {
return switch (mv) {
else => false,
.interned => |ip_index| switch (zcu.intern_pool.indexToKey(ip_index)) {
else => false,
.int => |int| switch (int.storage) {
.u64, .i64, .big_int => true,
.lazy_align, .lazy_size => false,
},
},
};
}
pub fn typeOf(mv: MutableValue, zcu: *Zcu) Type {
return switch (mv) {
.interned => |ip_index| Type.fromInterned(zcu.intern_pool.typeOf(ip_index)),
inline else => |x| Type.fromInterned(x.ty),
};
}
pub fn unpackOptional(mv: MutableValue, zcu: *Zcu) union(enum) {
undef,
null,
payload: MutableValue,
} {
return switch (mv) {
.opt_payload => |pl| return .{ .payload = pl.child.* },
.interned => |ip_index| switch (zcu.intern_pool.indexToKey(ip_index)) {
.undef => return .undef,
.opt => |opt| if (opt.val == .none) .null else .{ .payload = .{ .interned = opt.val } },
else => unreachable,
},
else => unreachable,
};
}
pub fn unpackErrorUnion(mv: MutableValue, zcu: *Zcu) union(enum) {
undef,
err: InternPool.NullTerminatedString,
payload: MutableValue,
} {
return switch (mv) {
.eu_payload => |pl| return .{ .payload = pl.child.* },
.interned => |ip_index| switch (zcu.intern_pool.indexToKey(ip_index)) {
.undef => return .undef,
.error_union => |eu| switch (eu.val) {
.err_name => |name| .{ .err = name },
.payload => |pl| .{ .payload = .{ .interned = pl } },
},
else => unreachable,
},
else => unreachable,
};
}
/// Fast equality checking which may return false negatives.
/// Used for deciding when to switch aggregate representations without fully
/// interning many values.
fn eqlTrivial(a: MutableValue, b: MutableValue) bool {
const Tag = @typeInfo(MutableValue).@"union".tag_type.?;
if (@as(Tag, a) != @as(Tag, b)) return false;
return switch (a) {
.interned => |a_ip| a_ip == b.interned,
.eu_payload => |a_pl| a_pl.ty == b.eu_payload.ty and a_pl.child.eqlTrivial(b.eu_payload.child.*),
.opt_payload => |a_pl| a_pl.ty == b.opt_payload.ty and a_pl.child.eqlTrivial(b.opt_payload.child.*),
.repeated => |a_rep| a_rep.ty == b.repeated.ty and a_rep.child.eqlTrivial(b.repeated.child.*),
.bytes => |a_bytes| a_bytes.ty == b.bytes.ty and std.mem.eql(u8, a_bytes.data, b.bytes.data),
.aggregate => |a_agg| {
const b_agg = b.aggregate;
if (a_agg.ty != b_agg.ty) return false;
if (a_agg.elems.len != b_agg.elems.len) return false;
for (a_agg.elems, b_agg.elems) |a_elem, b_elem| {
if (!a_elem.eqlTrivial(b_elem)) return false;
}
return true;
},
.slice => |a_slice| a_slice.ty == b.slice.ty and
a_slice.ptr.interned == b.slice.ptr.interned and
a_slice.len.interned == b.slice.len.interned,
.un => |a_un| a_un.ty == b.un.ty and a_un.tag == b.un.tag and a_un.payload.eqlTrivial(b.un.payload.*),
};
}
};