const std = @import("std"); const Type = @import("type.zig").Type; const Value = @import("Value.zig"); const Module = @import("Module.zig"); const Allocator = std.mem.Allocator; const TypedValue = @This(); const Target = std.Target; ty: Type, val: Value, /// Memory management for TypedValue. The main purpose of this type /// is to be small and have a deinit() function to free associated resources. pub const Managed = struct { /// If the tag value is less than Tag.no_payload_count, then no pointer /// dereference is needed. typed_value: TypedValue, /// If this is `null` then there is no memory management needed. arena: ?*std.heap.ArenaAllocator.State = null, pub fn deinit(self: *Managed, allocator: Allocator) void { if (self.arena) |a| a.promote(allocator).deinit(); self.* = undefined; } }; /// Assumes arena allocation. Does a recursive copy. pub fn copy(self: TypedValue, arena: Allocator) error{OutOfMemory}!TypedValue { return TypedValue{ .ty = self.ty, .val = try self.val.copy(arena), }; } pub fn eql(a: TypedValue, b: TypedValue, mod: *Module) bool { if (a.ty.toIntern() != b.ty.toIntern()) return false; return a.val.eql(b.val, a.ty, mod); } pub fn hash(tv: TypedValue, hasher: *std.hash.Wyhash, mod: *Module) void { return tv.val.hash(tv.ty, hasher, mod); } pub fn intFromEnum(tv: TypedValue, mod: *Module) Allocator.Error!Value { return tv.val.intFromEnum(tv.ty, mod); } const max_aggregate_items = 100; const max_string_len = 256; const FormatContext = struct { tv: TypedValue, mod: *Module, }; pub fn format( ctx: FormatContext, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype, ) !void { _ = options; comptime std.debug.assert(fmt.len == 0); return ctx.tv.print(writer, 3, ctx.mod) catch |err| switch (err) { error.OutOfMemory => @panic("OOM"), // We're not allowed to return this from a format function else => |e| return e, }; } /// Prints the Value according to the Type, not according to the Value Tag. pub fn print( tv: TypedValue, writer: anytype, level: u8, mod: *Module, ) (@TypeOf(writer).Error || Allocator.Error)!void { var val = tv.val; var ty = tv.ty; const ip = &mod.intern_pool; while (true) switch (val.ip_index) { .none => switch (val.tag()) { .aggregate => return printAggregate(ty, val, writer, level, mod), .@"union" => { if (level == 0) { return writer.writeAll(".{ ... }"); } const payload = val.castTag(.@"union").?.data; try writer.writeAll(".{ "); if (payload.tag) |tag| { try print(.{ .ty = Type.fromInterned(ip.loadUnionType(ty.toIntern()).enum_tag_ty), .val = tag, }, writer, level - 1, mod); try writer.writeAll(" = "); const field_ty = ty.unionFieldType(tag, mod).?; try print(.{ .ty = field_ty, .val = payload.val, }, writer, level - 1, mod); } else { try writer.writeAll("(unknown tag) = "); const backing_ty = try ty.unionBackingType(mod); try print(.{ .ty = backing_ty, .val = payload.val, }, writer, level - 1, mod); } return writer.writeAll(" }"); }, .bytes => return writer.print("\"{}\"", .{std.zig.fmtEscapes(val.castTag(.bytes).?.data)}), .repeated => { if (level == 0) { return writer.writeAll(".{ ... }"); } var i: u32 = 0; try writer.writeAll(".{ "); const elem_tv = TypedValue{ .ty = ty.elemType2(mod), .val = val.castTag(.repeated).?.data, }; const len = ty.arrayLen(mod); const max_len = @min(len, max_aggregate_items); while (i < max_len) : (i += 1) { if (i != 0) try writer.writeAll(", "); try print(elem_tv, writer, level - 1, mod); } if (len > max_aggregate_items) { try writer.writeAll(", ..."); } return writer.writeAll(" }"); }, .slice => { if (level == 0) { return writer.writeAll(".{ ... }"); } const payload = val.castTag(.slice).?.data; const elem_ty = ty.elemType2(mod); const len = payload.len.toUnsignedInt(mod); if (elem_ty.eql(Type.u8, mod)) str: { const max_len: usize = @min(len, max_string_len); var buf: [max_string_len]u8 = undefined; var i: u32 = 0; while (i < max_len) : (i += 1) { const maybe_elem_val = payload.ptr.maybeElemValue(mod, i) catch |err| switch (err) { error.OutOfMemory => @panic("OOM"), // TODO: eliminate this panic }; const elem_val = maybe_elem_val orelse return writer.writeAll(".{ (reinterpreted data) }"); if (elem_val.isUndef(mod)) break :str; buf[i] = std.math.cast(u8, elem_val.toUnsignedInt(mod)) orelse break :str; } // TODO would be nice if this had a bit of unicode awareness. const truncated = if (len > max_string_len) " (truncated)" else ""; return writer.print("\"{}{s}\"", .{ std.zig.fmtEscapes(buf[0..max_len]), truncated }); } try writer.writeAll(".{ "); const max_len = @min(len, max_aggregate_items); var i: u32 = 0; while (i < max_len) : (i += 1) { if (i != 0) try writer.writeAll(", "); const maybe_elem_val = payload.ptr.maybeElemValue(mod, i) catch |err| switch (err) { error.OutOfMemory => @panic("OOM"), // TODO: eliminate this panic }; const elem_val = maybe_elem_val orelse return writer.writeAll("(reinterpreted data) }"); try print(.{ .ty = elem_ty, .val = elem_val, }, writer, level - 1, mod); } if (len > max_aggregate_items) { try writer.writeAll(", ..."); } return writer.writeAll(" }"); }, .eu_payload => { val = val.castTag(.eu_payload).?.data; ty = ty.errorUnionPayload(mod); }, .opt_payload => { val = val.castTag(.opt_payload).?.data; ty = ty.optionalChild(mod); }, }, else => switch (ip.indexToKey(val.toIntern())) { .int_type, .ptr_type, .array_type, .vector_type, .opt_type, .anyframe_type, .error_union_type, .simple_type, .struct_type, .anon_struct_type, .union_type, .opaque_type, .enum_type, .func_type, .error_set_type, .inferred_error_set_type, => return Type.print(val.toType(), writer, mod), .undef => return writer.writeAll("undefined"), .simple_value => |simple_value| switch (simple_value) { .void => return writer.writeAll("{}"), .empty_struct => return printAggregate(ty, val, writer, level, mod), .generic_poison => return writer.writeAll("(generic poison)"), else => return writer.writeAll(@tagName(simple_value)), }, .variable => return writer.writeAll("(variable)"), .extern_func => |extern_func| return writer.print("(extern function '{}')", .{ mod.declPtr(extern_func.decl).name.fmt(ip), }), .func => |func| return writer.print("(function '{}')", .{ mod.declPtr(func.owner_decl).name.fmt(ip), }), .int => |int| switch (int.storage) { inline .u64, .i64, .big_int => |x| return writer.print("{}", .{x}), .lazy_align => |lazy_ty| return writer.print("{d}", .{ Type.fromInterned(lazy_ty).abiAlignment(mod), }), .lazy_size => |lazy_ty| return writer.print("{d}", .{ Type.fromInterned(lazy_ty).abiSize(mod), }), }, .err => |err| return writer.print("error.{}", .{ err.name.fmt(ip), }), .error_union => |error_union| switch (error_union.val) { .err_name => |err_name| return writer.print("error.{}", .{ err_name.fmt(ip), }), .payload => |payload| { val = Value.fromInterned(payload); ty = ty.errorUnionPayload(mod); }, }, .enum_literal => |enum_literal| return writer.print(".{}", .{ enum_literal.fmt(ip), }), .enum_tag => |enum_tag| { if (level == 0) { return writer.writeAll("(enum)"); } const enum_type = ip.loadEnumType(ty.toIntern()); if (enum_type.tagValueIndex(ip, val.toIntern())) |tag_index| { try writer.print(".{i}", .{enum_type.names.get(ip)[tag_index].fmt(ip)}); return; } try writer.writeAll("@enumFromInt("); try print(.{ .ty = Type.fromInterned(ip.typeOf(enum_tag.int)), .val = Value.fromInterned(enum_tag.int), }, writer, level - 1, mod); try writer.writeAll(")"); return; }, .empty_enum_value => return writer.writeAll("(empty enum value)"), .float => |float| switch (float.storage) { inline else => |x| return writer.print("{d}", .{@as(f64, @floatCast(x))}), }, .slice => |slice| { const ptr_ty = switch (ip.indexToKey(slice.ptr)) { .ptr => |ptr| ty: { if (ptr.addr == .int) return print(.{ .ty = Type.fromInterned(ptr.ty), .val = Value.fromInterned(slice.ptr), }, writer, level - 1, mod); break :ty ip.indexToKey(ptr.ty).ptr_type; }, .undef => |ptr_ty| ip.indexToKey(ptr_ty).ptr_type, else => unreachable, }; if (level == 0) { return writer.writeAll(".{ ... }"); } const elem_ty = Type.fromInterned(ptr_ty.child); const len = Value.fromInterned(slice.len).toUnsignedInt(mod); if (elem_ty.eql(Type.u8, mod)) str: { const max_len = @min(len, max_string_len); var buf: [max_string_len]u8 = undefined; for (buf[0..max_len], 0..) |*c, i| { const maybe_elem = try val.maybeElemValue(mod, i); const elem = maybe_elem orelse return writer.writeAll(".{ (reinterpreted data) }"); if (elem.isUndef(mod)) break :str; c.* = @as(u8, @intCast(elem.toUnsignedInt(mod))); } const truncated = if (len > max_string_len) " (truncated)" else ""; return writer.print("\"{}{s}\"", .{ std.zig.fmtEscapes(buf[0..max_len]), truncated }); } try writer.writeAll(".{ "); const max_len = @min(len, max_aggregate_items); for (0..max_len) |i| { if (i != 0) try writer.writeAll(", "); const maybe_elem = try val.maybeElemValue(mod, i); const elem = maybe_elem orelse return writer.writeAll("(reinterpreted data) }"); try print(.{ .ty = elem_ty, .val = elem, }, writer, level - 1, mod); } if (len > max_aggregate_items) { try writer.writeAll(", ..."); } return writer.writeAll(" }"); }, .ptr => |ptr| { switch (ptr.addr) { .decl => |decl_index| { const decl = mod.declPtr(decl_index); if (level == 0) return writer.print("(decl '{}')", .{decl.name.fmt(ip)}); return print(.{ .ty = decl.ty, .val = decl.val, }, writer, level - 1, mod); }, .anon_decl => |anon_decl| { const decl_val = anon_decl.val; if (level == 0) return writer.print("(anon decl '{d}')", .{ @intFromEnum(decl_val), }); return print(.{ .ty = Type.fromInterned(ip.typeOf(decl_val)), .val = Value.fromInterned(decl_val), }, writer, level - 1, mod); }, .mut_decl => |mut_decl| { const decl = mod.declPtr(mut_decl.decl); if (level == 0) return writer.print("(mut decl '{}')", .{decl.name.fmt(ip)}); return print(.{ .ty = decl.ty, .val = decl.val, }, writer, level - 1, mod); }, .comptime_field => |field_val_ip| { return print(.{ .ty = Type.fromInterned(ip.typeOf(field_val_ip)), .val = Value.fromInterned(field_val_ip), }, writer, level - 1, mod); }, .int => |int_ip| { try writer.writeAll("@ptrFromInt("); try print(.{ .ty = Type.usize, .val = Value.fromInterned(int_ip), }, writer, level - 1, mod); try writer.writeByte(')'); }, .eu_payload => |eu_ip| { try writer.writeAll("(payload of "); try print(.{ .ty = Type.fromInterned(ip.typeOf(eu_ip)), .val = Value.fromInterned(eu_ip), }, writer, level - 1, mod); try writer.writeAll(")"); }, .opt_payload => |opt_ip| { try print(.{ .ty = Type.fromInterned(ip.typeOf(opt_ip)), .val = Value.fromInterned(opt_ip), }, writer, level - 1, mod); try writer.writeAll(".?"); }, .elem => |elem| { if (level == 0) { try writer.writeAll("(...)"); } else { try print(.{ .ty = Type.fromInterned(ip.typeOf(elem.base)), .val = Value.fromInterned(elem.base), }, writer, level - 1, mod); } try writer.print("[{}]", .{elem.index}); }, .field => |field| { const ptr_container_ty = Type.fromInterned(ip.typeOf(field.base)); if (level == 0) { try writer.writeAll("(...)"); } else { try print(.{ .ty = ptr_container_ty, .val = Value.fromInterned(field.base), }, writer, level - 1, mod); } const container_ty = ptr_container_ty.childType(mod); switch (container_ty.zigTypeTag(mod)) { .Struct => { if (container_ty.structFieldName(@intCast(field.index), mod).unwrap()) |field_name| { try writer.print(".{i}", .{field_name.fmt(ip)}); } else { try writer.print("[{d}]", .{field.index}); } }, .Union => { const field_name = mod.typeToUnion(container_ty).?.loadTagType(ip).names.get(ip)[@intCast(field.index)]; try writer.print(".{i}", .{field_name.fmt(ip)}); }, .Pointer => { std.debug.assert(container_ty.isSlice(mod)); try writer.writeAll(switch (field.index) { Value.slice_ptr_index => ".ptr", Value.slice_len_index => ".len", else => unreachable, }); }, else => unreachable, } }, } return; }, .opt => |opt| switch (opt.val) { .none => return writer.writeAll("null"), else => |payload| { val = Value.fromInterned(payload); ty = ty.optionalChild(mod); }, }, .aggregate => |aggregate| switch (aggregate.storage) { .bytes => |bytes| { // Strip the 0 sentinel off of strings before printing const zero_sent = blk: { const sent = ty.sentinel(mod) orelse break :blk false; break :blk sent.eql(Value.zero_u8, Type.u8, mod); }; const str = if (zero_sent) bytes[0 .. bytes.len - 1] else bytes; return writer.print("\"{}\"", .{std.zig.fmtEscapes(str)}); }, .elems, .repeated_elem => return printAggregate(ty, val, writer, level, mod), }, .un => |un| { try writer.writeAll(".{ "); if (level > 0) { if (un.tag != .none) { try print(.{ .ty = ty.unionTagTypeHypothetical(mod), .val = Value.fromInterned(un.tag), }, writer, level - 1, mod); try writer.writeAll(" = "); const field_ty = ty.unionFieldType(Value.fromInterned(un.tag), mod).?; try print(.{ .ty = field_ty, .val = Value.fromInterned(un.val), }, writer, level - 1, mod); } else { try writer.writeAll("(unknown tag) = "); const backing_ty = try ty.unionBackingType(mod); try print(.{ .ty = backing_ty, .val = Value.fromInterned(un.val), }, writer, level - 1, mod); } } else try writer.writeAll("..."); return writer.writeAll(" }"); }, .memoized_call => unreachable, }, }; } fn printAggregate( ty: Type, val: Value, writer: anytype, level: u8, mod: *Module, ) (@TypeOf(writer).Error || Allocator.Error)!void { if (level == 0) { return writer.writeAll(".{ ... }"); } const ip = &mod.intern_pool; if (ty.zigTypeTag(mod) == .Struct) { try writer.writeAll(".{"); const max_len = @min(ty.structFieldCount(mod), max_aggregate_items); for (0..max_len) |i| { if (i != 0) try writer.writeAll(", "); const field_name = ty.structFieldName(@intCast(i), mod); if (field_name.unwrap()) |name| try writer.print(".{} = ", .{name.fmt(ip)}); try print(.{ .ty = ty.structFieldType(i, mod), .val = try val.fieldValue(mod, i), }, writer, level - 1, mod); } if (ty.structFieldCount(mod) > max_aggregate_items) { try writer.writeAll(", ..."); } return writer.writeAll("}"); } else { const elem_ty = ty.elemType2(mod); const len = ty.arrayLen(mod); if (elem_ty.eql(Type.u8, mod)) str: { const max_len: usize = @min(len, max_string_len); var buf: [max_string_len]u8 = undefined; var i: u32 = 0; while (i < max_len) : (i += 1) { const elem = try val.fieldValue(mod, i); if (elem.isUndef(mod)) break :str; buf[i] = std.math.cast(u8, elem.toUnsignedInt(mod)) orelse break :str; } const truncated = if (len > max_string_len) " (truncated)" else ""; return writer.print("\"{}{s}\"", .{ std.zig.fmtEscapes(buf[0..max_len]), truncated }); } try writer.writeAll(".{ "); const max_len = @min(len, max_aggregate_items); var i: u32 = 0; while (i < max_len) : (i += 1) { if (i != 0) try writer.writeAll(", "); try print(.{ .ty = elem_ty, .val = try val.fieldValue(mod, i), }, writer, level - 1, mod); } if (len > max_aggregate_items) { try writer.writeAll(", ..."); } return writer.writeAll(" }"); } }