zig/src/Air/print.zig
Matthew Lugg 74931fe25c
std.debug.lockStderrWriter: also return ttyconf
`std.Io.tty.Config.detect` may be an expensive check (e.g. involving
syscalls), and doing it every time we need to print isn't really
necessary; under normal usage, we can compute the value once and cache
it for the whole program's execution. Since anyone outputting to stderr
may reasonably want this information (in fact they are very likely to),
it makes sense to cache it and return it from `lockStderrWriter`. Call
sites who do not need it will experience no significant overhead, and
can just ignore the TTY config with a `const w, _` destructure.
2025-10-30 09:31:28 +00:00

1064 lines
39 KiB
Zig

const std = @import("std");
const Allocator = std.mem.Allocator;
const assert = std.debug.assert;
const build_options = @import("build_options");
const Zcu = @import("../Zcu.zig");
const Value = @import("../Value.zig");
const Type = @import("../Type.zig");
const Air = @import("../Air.zig");
const InternPool = @import("../InternPool.zig");
pub fn write(air: Air, stream: *std.Io.Writer, pt: Zcu.PerThread, liveness: ?Air.Liveness) void {
comptime assert(build_options.enable_debug_extensions);
const instruction_bytes = air.instructions.len *
// Here we don't use @sizeOf(Air.Inst.Data) because it would include
// the debug safety tag but we want to measure release size.
(@sizeOf(Air.Inst.Tag) + 8);
const extra_bytes = air.extra.items.len * @sizeOf(u32);
const tomb_bytes = if (liveness) |l| l.tomb_bits.len * @sizeOf(usize) else 0;
const liveness_extra_bytes = if (liveness) |l| l.extra.len * @sizeOf(u32) else 0;
const liveness_special_bytes = if (liveness) |l| l.special.count() * 8 else 0;
const total_bytes = @sizeOf(Air) + instruction_bytes + extra_bytes +
@sizeOf(Air.Liveness) + liveness_extra_bytes +
liveness_special_bytes + tomb_bytes;
// zig fmt: off
stream.print(
\\# Total AIR+Liveness bytes: {Bi}
\\# AIR Instructions: {d} ({Bi})
\\# AIR Extra Data: {d} ({Bi})
\\# Liveness tomb_bits: {Bi}
\\# Liveness Extra Data: {d} ({Bi})
\\# Liveness special table: {d} ({Bi})
\\
, .{
total_bytes,
air.instructions.len, instruction_bytes,
air.extra.items.len, extra_bytes,
tomb_bytes,
if (liveness) |l| l.extra.len else 0, liveness_extra_bytes,
if (liveness) |l| l.special.count() else 0, liveness_special_bytes,
}) catch return;
// zig fmt: on
var writer: Writer = .{
.pt = pt,
.gpa = pt.zcu.gpa,
.air = air,
.liveness = liveness,
.indent = 2,
.skip_body = false,
};
writer.writeBody(stream, air.getMainBody()) catch return;
}
pub fn writeInst(
air: Air,
stream: *std.Io.Writer,
inst: Air.Inst.Index,
pt: Zcu.PerThread,
liveness: ?Air.Liveness,
) void {
comptime assert(build_options.enable_debug_extensions);
var writer: Writer = .{
.pt = pt,
.gpa = pt.zcu.gpa,
.air = air,
.liveness = liveness,
.indent = 2,
.skip_body = true,
};
writer.writeInst(stream, inst) catch return;
}
pub fn dump(air: Air, pt: Zcu.PerThread, liveness: ?Air.Liveness) void {
const stderr_bw, _ = std.debug.lockStderrWriter(&.{});
defer std.debug.unlockStderrWriter();
air.write(stderr_bw, pt, liveness);
}
pub fn dumpInst(air: Air, inst: Air.Inst.Index, pt: Zcu.PerThread, liveness: ?Air.Liveness) void {
const stderr_bw, _ = std.debug.lockStderrWriter(&.{});
defer std.debug.unlockStderrWriter();
air.writeInst(stderr_bw, inst, pt, liveness);
}
const Writer = struct {
pt: Zcu.PerThread,
gpa: Allocator,
air: Air,
liveness: ?Air.Liveness,
indent: usize,
skip_body: bool,
const Error = std.Io.Writer.Error;
fn writeBody(w: *Writer, s: *std.Io.Writer, body: []const Air.Inst.Index) Error!void {
for (body) |inst| {
try w.writeInst(s, inst);
try s.writeByte('\n');
}
}
fn writeInst(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
const tag = w.air.instructions.items(.tag)[@intFromEnum(inst)];
try s.splatByteAll(' ', w.indent);
try s.print("{f}{c}= {s}(", .{
inst,
@as(u8, if (if (w.liveness) |liveness| liveness.isUnused(inst) else false) '!' else ' '),
@tagName(tag),
});
switch (tag) {
.add,
.add_optimized,
.add_safe,
.add_wrap,
.add_sat,
.sub,
.sub_optimized,
.sub_safe,
.sub_wrap,
.sub_sat,
.mul,
.mul_optimized,
.mul_safe,
.mul_wrap,
.mul_sat,
.div_float,
.div_trunc,
.div_floor,
.div_exact,
.rem,
.mod,
.bit_and,
.bit_or,
.xor,
.cmp_lt,
.cmp_lte,
.cmp_eq,
.cmp_gte,
.cmp_gt,
.cmp_neq,
.bool_and,
.bool_or,
.store,
.store_safe,
.array_elem_val,
.slice_elem_val,
.ptr_elem_val,
.shl,
.shl_exact,
.shl_sat,
.shr,
.shr_exact,
.set_union_tag,
.min,
.max,
.div_float_optimized,
.div_trunc_optimized,
.div_floor_optimized,
.div_exact_optimized,
.rem_optimized,
.mod_optimized,
.cmp_lt_optimized,
.cmp_lte_optimized,
.cmp_eq_optimized,
.cmp_gte_optimized,
.cmp_gt_optimized,
.cmp_neq_optimized,
.memcpy,
.memmove,
.memset,
.memset_safe,
=> try w.writeBinOp(s, inst),
.is_null,
.is_non_null,
.is_null_ptr,
.is_non_null_ptr,
.is_err,
.is_non_err,
.is_err_ptr,
.is_non_err_ptr,
.ret,
.ret_safe,
.ret_load,
.is_named_enum_value,
.tag_name,
.error_name,
.sqrt,
.sin,
.cos,
.tan,
.exp,
.exp2,
.log,
.log2,
.log10,
.floor,
.ceil,
.round,
.trunc_float,
.neg,
.neg_optimized,
.cmp_lt_errors_len,
.set_err_return_trace,
.c_va_end,
=> try w.writeUnOp(s, inst),
.trap,
.breakpoint,
.dbg_empty_stmt,
.unreach,
.ret_addr,
.frame_addr,
.save_err_return_trace_index,
=> try w.writeNoOp(s, inst),
.alloc,
.ret_ptr,
.err_return_trace,
.c_va_start,
=> try w.writeTy(s, inst),
.arg => try w.writeArg(s, inst),
.not,
.bitcast,
.load,
.fptrunc,
.fpext,
.intcast,
.intcast_safe,
.trunc,
.optional_payload,
.optional_payload_ptr,
.optional_payload_ptr_set,
.errunion_payload_ptr_set,
.wrap_optional,
.unwrap_errunion_payload,
.unwrap_errunion_err,
.unwrap_errunion_payload_ptr,
.unwrap_errunion_err_ptr,
.wrap_errunion_payload,
.wrap_errunion_err,
.slice_ptr,
.slice_len,
.ptr_slice_len_ptr,
.ptr_slice_ptr_ptr,
.struct_field_ptr_index_0,
.struct_field_ptr_index_1,
.struct_field_ptr_index_2,
.struct_field_ptr_index_3,
.array_to_slice,
.float_from_int,
.splat,
.int_from_float,
.int_from_float_optimized,
.int_from_float_safe,
.int_from_float_optimized_safe,
.get_union_tag,
.clz,
.ctz,
.popcount,
.byte_swap,
.bit_reverse,
.abs,
.error_set_has_value,
.addrspace_cast,
.c_va_arg,
.c_va_copy,
=> try w.writeTyOp(s, inst),
.block, .dbg_inline_block => try w.writeBlock(s, tag, inst),
.loop => try w.writeLoop(s, inst),
.slice,
.slice_elem_ptr,
.ptr_elem_ptr,
.ptr_add,
.ptr_sub,
.add_with_overflow,
.sub_with_overflow,
.mul_with_overflow,
.shl_with_overflow,
=> try w.writeTyPlBin(s, inst),
.call,
.call_always_tail,
.call_never_tail,
.call_never_inline,
=> try w.writeCall(s, inst),
.dbg_var_ptr,
.dbg_var_val,
.dbg_arg_inline,
=> try w.writeDbgVar(s, inst),
.struct_field_ptr => try w.writeStructField(s, inst),
.struct_field_val => try w.writeStructField(s, inst),
.inferred_alloc => @panic("TODO"),
.inferred_alloc_comptime => @panic("TODO"),
.assembly => try w.writeAssembly(s, inst),
.dbg_stmt => try w.writeDbgStmt(s, inst),
.aggregate_init => try w.writeAggregateInit(s, inst),
.union_init => try w.writeUnionInit(s, inst),
.br => try w.writeBr(s, inst),
.switch_dispatch => try w.writeBr(s, inst),
.repeat => try w.writeRepeat(s, inst),
.cond_br => try w.writeCondBr(s, inst),
.@"try", .try_cold => try w.writeTry(s, inst),
.try_ptr, .try_ptr_cold => try w.writeTryPtr(s, inst),
.loop_switch_br, .switch_br => try w.writeSwitchBr(s, inst),
.cmpxchg_weak, .cmpxchg_strong => try w.writeCmpxchg(s, inst),
.atomic_load => try w.writeAtomicLoad(s, inst),
.prefetch => try w.writePrefetch(s, inst),
.atomic_store_unordered => try w.writeAtomicStore(s, inst, .unordered),
.atomic_store_monotonic => try w.writeAtomicStore(s, inst, .monotonic),
.atomic_store_release => try w.writeAtomicStore(s, inst, .release),
.atomic_store_seq_cst => try w.writeAtomicStore(s, inst, .seq_cst),
.atomic_rmw => try w.writeAtomicRmw(s, inst),
.field_parent_ptr => try w.writeFieldParentPtr(s, inst),
.wasm_memory_size => try w.writeWasmMemorySize(s, inst),
.wasm_memory_grow => try w.writeWasmMemoryGrow(s, inst),
.mul_add => try w.writeMulAdd(s, inst),
.select => try w.writeSelect(s, inst),
.shuffle_one => try w.writeShuffleOne(s, inst),
.shuffle_two => try w.writeShuffleTwo(s, inst),
.reduce, .reduce_optimized => try w.writeReduce(s, inst),
.cmp_vector, .cmp_vector_optimized => try w.writeCmpVector(s, inst),
.vector_store_elem => try w.writeVectorStoreElem(s, inst),
.runtime_nav_ptr => try w.writeRuntimeNavPtr(s, inst),
.work_item_id,
.work_group_size,
.work_group_id,
=> try w.writeWorkDimension(s, inst),
}
try s.writeByte(')');
}
fn writeBinOp(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
const bin_op = w.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
try w.writeOperand(s, inst, 0, bin_op.lhs);
try s.writeAll(", ");
try w.writeOperand(s, inst, 1, bin_op.rhs);
}
fn writeUnOp(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
const un_op = w.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
try w.writeOperand(s, inst, 0, un_op);
}
fn writeNoOp(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
_ = w;
_ = s;
_ = inst;
// no-op, no argument to write
}
fn writeType(w: *Writer, s: *std.Io.Writer, ty: Type) !void {
return ty.print(s, w.pt);
}
fn writeTy(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
const ty = w.air.instructions.items(.data)[@intFromEnum(inst)].ty;
try w.writeType(s, ty);
}
fn writeArg(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
const arg = w.air.instructions.items(.data)[@intFromEnum(inst)].arg;
try w.writeType(s, arg.ty.toType());
try s.print(", {d}", .{arg.zir_param_index});
}
fn writeTyOp(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
const ty_op = w.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
try w.writeType(s, ty_op.ty.toType());
try s.writeAll(", ");
try w.writeOperand(s, inst, 0, ty_op.operand);
}
fn writeBlock(w: *Writer, s: *std.Io.Writer, tag: Air.Inst.Tag, inst: Air.Inst.Index) Error!void {
const ty_pl = w.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
try w.writeType(s, ty_pl.ty.toType());
const body: []const Air.Inst.Index = @ptrCast(switch (tag) {
inline .block, .dbg_inline_block => |comptime_tag| body: {
const extra = w.air.extraData(switch (comptime_tag) {
.block => Air.Block,
.dbg_inline_block => Air.DbgInlineBlock,
else => unreachable,
}, ty_pl.payload);
switch (comptime_tag) {
.block => {},
.dbg_inline_block => {
try s.writeAll(", ");
try w.writeInstRef(s, Air.internedToRef(extra.data.func), false);
},
else => unreachable,
}
break :body w.air.extra.items[extra.end..][0..extra.data.body_len];
},
else => unreachable,
});
if (w.skip_body) return s.writeAll(", ...");
const liveness_block: Air.Liveness.BlockSlices = if (w.liveness) |liveness|
liveness.getBlock(inst)
else
.{ .deaths = &.{} };
try s.writeAll(", {\n");
const old_indent = w.indent;
w.indent += 2;
try w.writeBody(s, body);
w.indent = old_indent;
try s.splatByteAll(' ', w.indent);
try s.writeAll("}");
for (liveness_block.deaths) |operand| {
try s.print(" {f}!", .{operand});
}
}
fn writeLoop(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
const ty_pl = w.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
const extra = w.air.extraData(Air.Block, ty_pl.payload);
const body: []const Air.Inst.Index = @ptrCast(w.air.extra.items[extra.end..][0..extra.data.body_len]);
try w.writeType(s, ty_pl.ty.toType());
if (w.skip_body) return s.writeAll(", ...");
try s.writeAll(", {\n");
const old_indent = w.indent;
w.indent += 2;
try w.writeBody(s, body);
w.indent = old_indent;
try s.splatByteAll(' ', w.indent);
try s.writeAll("}");
}
fn writeAggregateInit(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
const zcu = w.pt.zcu;
const ty_pl = w.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
const vector_ty = ty_pl.ty.toType();
const len = @as(usize, @intCast(vector_ty.arrayLen(zcu)));
const elements = @as([]const Air.Inst.Ref, @ptrCast(w.air.extra.items[ty_pl.payload..][0..len]));
try w.writeType(s, vector_ty);
try s.writeAll(", [");
for (elements, 0..) |elem, i| {
if (i != 0) try s.writeAll(", ");
try w.writeOperand(s, inst, i, elem);
}
try s.writeAll("]");
}
fn writeUnionInit(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
const ty_pl = w.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
const extra = w.air.extraData(Air.UnionInit, ty_pl.payload).data;
try s.print("{d}, ", .{extra.field_index});
try w.writeOperand(s, inst, 0, extra.init);
}
fn writeStructField(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
const ty_pl = w.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
const extra = w.air.extraData(Air.StructField, ty_pl.payload).data;
try w.writeOperand(s, inst, 0, extra.struct_operand);
try s.print(", {d}", .{extra.field_index});
}
fn writeTyPlBin(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
const data = w.air.instructions.items(.data);
const ty_pl = data[@intFromEnum(inst)].ty_pl;
const extra = w.air.extraData(Air.Bin, ty_pl.payload).data;
const inst_ty = data[@intFromEnum(inst)].ty_pl.ty.toType();
try w.writeType(s, inst_ty);
try s.writeAll(", ");
try w.writeOperand(s, inst, 0, extra.lhs);
try s.writeAll(", ");
try w.writeOperand(s, inst, 1, extra.rhs);
}
fn writeCmpxchg(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
const ty_pl = w.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
const extra = w.air.extraData(Air.Cmpxchg, ty_pl.payload).data;
try w.writeOperand(s, inst, 0, extra.ptr);
try s.writeAll(", ");
try w.writeOperand(s, inst, 1, extra.expected_value);
try s.writeAll(", ");
try w.writeOperand(s, inst, 2, extra.new_value);
try s.print(", {s}, {s}", .{
@tagName(extra.successOrder()), @tagName(extra.failureOrder()),
});
}
fn writeMulAdd(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
const pl_op = w.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
const extra = w.air.extraData(Air.Bin, pl_op.payload).data;
try w.writeOperand(s, inst, 0, extra.lhs);
try s.writeAll(", ");
try w.writeOperand(s, inst, 1, extra.rhs);
try s.writeAll(", ");
try w.writeOperand(s, inst, 2, pl_op.operand);
}
fn writeShuffleOne(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
const unwrapped = w.air.unwrapShuffleOne(w.pt.zcu, inst);
try w.writeType(s, unwrapped.result_ty);
try s.writeAll(", ");
try w.writeOperand(s, inst, 0, unwrapped.operand);
try s.writeAll(", [");
for (unwrapped.mask, 0..) |mask_elem, mask_idx| {
if (mask_idx > 0) try s.writeAll(", ");
switch (mask_elem.unwrap()) {
.elem => |idx| try s.print("elem {d}", .{idx}),
.value => |val| try s.print("val {f}", .{Value.fromInterned(val).fmtValue(w.pt)}),
}
}
try s.writeByte(']');
}
fn writeShuffleTwo(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
const unwrapped = w.air.unwrapShuffleTwo(w.pt.zcu, inst);
try w.writeType(s, unwrapped.result_ty);
try s.writeAll(", ");
try w.writeOperand(s, inst, 0, unwrapped.operand_a);
try s.writeAll(", ");
try w.writeOperand(s, inst, 1, unwrapped.operand_b);
try s.writeAll(", [");
for (unwrapped.mask, 0..) |mask_elem, mask_idx| {
if (mask_idx > 0) try s.writeAll(", ");
switch (mask_elem.unwrap()) {
.a_elem => |idx| try s.print("a_elem {d}", .{idx}),
.b_elem => |idx| try s.print("b_elem {d}", .{idx}),
.undef => try s.writeAll("undef"),
}
}
try s.writeByte(']');
}
fn writeSelect(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
const zcu = w.pt.zcu;
const pl_op = w.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
const extra = w.air.extraData(Air.Bin, pl_op.payload).data;
const elem_ty = w.typeOfIndex(inst).childType(zcu);
try w.writeType(s, elem_ty);
try s.writeAll(", ");
try w.writeOperand(s, inst, 0, pl_op.operand);
try s.writeAll(", ");
try w.writeOperand(s, inst, 1, extra.lhs);
try s.writeAll(", ");
try w.writeOperand(s, inst, 2, extra.rhs);
}
fn writeReduce(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
const reduce = w.air.instructions.items(.data)[@intFromEnum(inst)].reduce;
try w.writeOperand(s, inst, 0, reduce.operand);
try s.print(", {s}", .{@tagName(reduce.operation)});
}
fn writeCmpVector(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
const ty_pl = w.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
const extra = w.air.extraData(Air.VectorCmp, ty_pl.payload).data;
try s.print("{s}, ", .{@tagName(extra.compareOperator())});
try w.writeOperand(s, inst, 0, extra.lhs);
try s.writeAll(", ");
try w.writeOperand(s, inst, 1, extra.rhs);
}
fn writeVectorStoreElem(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
const data = w.air.instructions.items(.data)[@intFromEnum(inst)].vector_store_elem;
const extra = w.air.extraData(Air.VectorCmp, data.payload).data;
try w.writeOperand(s, inst, 0, data.vector_ptr);
try s.writeAll(", ");
try w.writeOperand(s, inst, 1, extra.lhs);
try s.writeAll(", ");
try w.writeOperand(s, inst, 2, extra.rhs);
}
fn writeRuntimeNavPtr(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
const ip = &w.pt.zcu.intern_pool;
const ty_nav = w.air.instructions.items(.data)[@intFromEnum(inst)].ty_nav;
try w.writeType(s, .fromInterned(ty_nav.ty));
try s.print(", '{f}'", .{ip.getNav(ty_nav.nav).fqn.fmt(ip)});
}
fn writeAtomicLoad(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
const atomic_load = w.air.instructions.items(.data)[@intFromEnum(inst)].atomic_load;
try w.writeOperand(s, inst, 0, atomic_load.ptr);
try s.print(", {s}", .{@tagName(atomic_load.order)});
}
fn writePrefetch(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
const prefetch = w.air.instructions.items(.data)[@intFromEnum(inst)].prefetch;
try w.writeOperand(s, inst, 0, prefetch.ptr);
try s.print(", {s}, {d}, {s}", .{
@tagName(prefetch.rw), prefetch.locality, @tagName(prefetch.cache),
});
}
fn writeAtomicStore(
w: *Writer,
s: *std.Io.Writer,
inst: Air.Inst.Index,
order: std.builtin.AtomicOrder,
) Error!void {
const bin_op = w.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
try w.writeOperand(s, inst, 0, bin_op.lhs);
try s.writeAll(", ");
try w.writeOperand(s, inst, 1, bin_op.rhs);
try s.print(", {s}", .{@tagName(order)});
}
fn writeAtomicRmw(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
const pl_op = w.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
const extra = w.air.extraData(Air.AtomicRmw, pl_op.payload).data;
try w.writeOperand(s, inst, 0, pl_op.operand);
try s.writeAll(", ");
try w.writeOperand(s, inst, 1, extra.operand);
try s.print(", {s}, {s}", .{ @tagName(extra.op()), @tagName(extra.ordering()) });
}
fn writeFieldParentPtr(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
const ty_pl = w.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
const extra = w.air.extraData(Air.FieldParentPtr, ty_pl.payload).data;
try w.writeOperand(s, inst, 0, extra.field_ptr);
try s.print(", {d}", .{extra.field_index});
}
fn writeAssembly(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
const ty_pl = w.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
const extra = w.air.extraData(Air.Asm, ty_pl.payload);
const is_volatile = extra.data.flags.is_volatile;
const outputs_len = extra.data.flags.outputs_len;
var extra_i: usize = extra.end;
var op_index: usize = 0;
const ret_ty = w.typeOfIndex(inst);
try w.writeType(s, ret_ty);
if (is_volatile) {
try s.writeAll(", volatile");
}
const outputs = @as([]const Air.Inst.Ref, @ptrCast(w.air.extra.items[extra_i..][0..outputs_len]));
extra_i += outputs.len;
const inputs = @as([]const Air.Inst.Ref, @ptrCast(w.air.extra.items[extra_i..][0..extra.data.inputs_len]));
extra_i += inputs.len;
for (outputs) |output| {
const extra_bytes = std.mem.sliceAsBytes(w.air.extra.items[extra_i..]);
const constraint = std.mem.sliceTo(extra_bytes, 0);
const name = std.mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0);
// This equation accounts for the fact that even if we have exactly 4 bytes
// for the strings and their null terminators, we still use the next u32
// for the null terminator.
extra_i += (constraint.len + name.len + (2 + 3)) / 4;
if (output == .none) {
try s.print(", [{s}] -> {s}", .{ name, constraint });
} else {
try s.print(", [{s}] out {s} = (", .{ name, constraint });
try w.writeOperand(s, inst, op_index, output);
op_index += 1;
try s.writeByte(')');
}
}
for (inputs) |input| {
const extra_bytes = std.mem.sliceAsBytes(w.air.extra.items[extra_i..]);
const constraint = std.mem.sliceTo(extra_bytes, 0);
const name = std.mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0);
// This equation accounts for the fact that even if we have exactly 4 bytes
// for the strings and their null terminators, we still use the next u32
// for the null terminator.
extra_i += (constraint.len + name.len + 1) / 4 + 1;
try s.print(", [{s}] in {s} = (", .{ name, constraint });
try w.writeOperand(s, inst, op_index, input);
op_index += 1;
try s.writeByte(')');
}
const zcu = w.pt.zcu;
const ip = &zcu.intern_pool;
const aggregate = ip.indexToKey(extra.data.clobbers).aggregate;
const struct_type: Type = .fromInterned(aggregate.ty);
switch (aggregate.storage) {
.elems => |elems| for (elems, 0..) |elem, i| {
switch (elem) {
.bool_true => {
const clobber = struct_type.structFieldName(i, zcu).toSlice(ip).?;
assert(clobber.len != 0);
try s.writeAll(", ~{");
try s.writeAll(clobber);
try s.writeAll("}");
},
.bool_false => continue,
else => unreachable,
}
},
.repeated_elem => |elem| {
try s.writeAll(", ");
try s.writeAll(switch (elem) {
.bool_true => "<all clobbers>",
.bool_false => "<no clobbers>",
else => unreachable,
});
},
.bytes => |bytes| {
try s.print(", {x}", .{bytes});
},
}
const asm_source = std.mem.sliceAsBytes(w.air.extra.items[extra_i..])[0..extra.data.source_len];
try s.print(", \"{f}\"", .{std.zig.fmtString(asm_source)});
}
fn writeDbgStmt(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
const dbg_stmt = w.air.instructions.items(.data)[@intFromEnum(inst)].dbg_stmt;
try s.print("{d}:{d}", .{ dbg_stmt.line + 1, dbg_stmt.column + 1 });
}
fn writeDbgVar(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
const pl_op = w.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
try w.writeOperand(s, inst, 0, pl_op.operand);
const name: Air.NullTerminatedString = @enumFromInt(pl_op.payload);
try s.print(", \"{f}\"", .{std.zig.fmtString(name.toSlice(w.air))});
}
fn writeCall(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
const pl_op = w.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
const extra = w.air.extraData(Air.Call, pl_op.payload);
const args = @as([]const Air.Inst.Ref, @ptrCast(w.air.extra.items[extra.end..][0..extra.data.args_len]));
try w.writeOperand(s, inst, 0, pl_op.operand);
try s.writeAll(", [");
for (args, 0..) |arg, i| {
if (i != 0) try s.writeAll(", ");
try w.writeOperand(s, inst, 1 + i, arg);
}
try s.writeAll("]");
}
fn writeBr(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
const br = w.air.instructions.items(.data)[@intFromEnum(inst)].br;
try w.writeInstIndex(s, br.block_inst, false);
try s.writeAll(", ");
try w.writeOperand(s, inst, 0, br.operand);
}
fn writeRepeat(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
const repeat = w.air.instructions.items(.data)[@intFromEnum(inst)].repeat;
try w.writeInstIndex(s, repeat.loop_inst, false);
}
fn writeTry(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
const pl_op = w.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
const extra = w.air.extraData(Air.Try, pl_op.payload);
const body: []const Air.Inst.Index = @ptrCast(w.air.extra.items[extra.end..][0..extra.data.body_len]);
const liveness_condbr: Air.Liveness.CondBrSlices = if (w.liveness) |liveness|
liveness.getCondBr(inst)
else
.{ .then_deaths = &.{}, .else_deaths = &.{} };
try w.writeOperand(s, inst, 0, pl_op.operand);
if (w.skip_body) return s.writeAll(", ...");
try s.writeAll(", {\n");
const old_indent = w.indent;
w.indent += 2;
if (liveness_condbr.else_deaths.len != 0) {
try s.splatByteAll(' ', w.indent);
for (liveness_condbr.else_deaths, 0..) |operand, i| {
if (i != 0) try s.writeAll(" ");
try s.print("{f}!", .{operand});
}
try s.writeAll("\n");
}
try w.writeBody(s, body);
w.indent = old_indent;
try s.splatByteAll(' ', w.indent);
try s.writeAll("}");
for (liveness_condbr.then_deaths) |operand| {
try s.print(" {f}!", .{operand});
}
}
fn writeTryPtr(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
const ty_pl = w.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
const extra = w.air.extraData(Air.TryPtr, ty_pl.payload);
const body: []const Air.Inst.Index = @ptrCast(w.air.extra.items[extra.end..][0..extra.data.body_len]);
const liveness_condbr: Air.Liveness.CondBrSlices = if (w.liveness) |liveness|
liveness.getCondBr(inst)
else
.{ .then_deaths = &.{}, .else_deaths = &.{} };
try w.writeOperand(s, inst, 0, extra.data.ptr);
try s.writeAll(", ");
try w.writeType(s, ty_pl.ty.toType());
if (w.skip_body) return s.writeAll(", ...");
try s.writeAll(", {\n");
const old_indent = w.indent;
w.indent += 2;
if (liveness_condbr.else_deaths.len != 0) {
try s.splatByteAll(' ', w.indent);
for (liveness_condbr.else_deaths, 0..) |operand, i| {
if (i != 0) try s.writeAll(" ");
try s.print("{f}!", .{operand});
}
try s.writeAll("\n");
}
try w.writeBody(s, body);
w.indent = old_indent;
try s.splatByteAll(' ', w.indent);
try s.writeAll("}");
for (liveness_condbr.then_deaths) |operand| {
try s.print(" {f}!", .{operand});
}
}
fn writeCondBr(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
const pl_op = w.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
const extra = w.air.extraData(Air.CondBr, pl_op.payload);
const then_body: []const Air.Inst.Index = @ptrCast(w.air.extra.items[extra.end..][0..extra.data.then_body_len]);
const else_body: []const Air.Inst.Index = @ptrCast(w.air.extra.items[extra.end + then_body.len ..][0..extra.data.else_body_len]);
const liveness_condbr: Air.Liveness.CondBrSlices = if (w.liveness) |liveness|
liveness.getCondBr(inst)
else
.{ .then_deaths = &.{}, .else_deaths = &.{} };
try w.writeOperand(s, inst, 0, pl_op.operand);
if (w.skip_body) return s.writeAll(", ...");
try s.writeAll(",");
if (extra.data.branch_hints.true != .none) {
try s.print(" {s}", .{@tagName(extra.data.branch_hints.true)});
}
if (extra.data.branch_hints.then_cov != .none) {
try s.print(" {s}", .{@tagName(extra.data.branch_hints.then_cov)});
}
try s.writeAll(" {\n");
const old_indent = w.indent;
w.indent += 2;
if (liveness_condbr.then_deaths.len != 0) {
try s.splatByteAll(' ', w.indent);
for (liveness_condbr.then_deaths, 0..) |operand, i| {
if (i != 0) try s.writeAll(" ");
try s.print("{f}!", .{operand});
}
try s.writeAll("\n");
}
try w.writeBody(s, then_body);
try s.splatByteAll(' ', old_indent);
try s.writeAll("},");
if (extra.data.branch_hints.false != .none) {
try s.print(" {s}", .{@tagName(extra.data.branch_hints.false)});
}
if (extra.data.branch_hints.else_cov != .none) {
try s.print(" {s}", .{@tagName(extra.data.branch_hints.else_cov)});
}
try s.writeAll(" {\n");
if (liveness_condbr.else_deaths.len != 0) {
try s.splatByteAll(' ', w.indent);
for (liveness_condbr.else_deaths, 0..) |operand, i| {
if (i != 0) try s.writeAll(" ");
try s.print("{f}!", .{operand});
}
try s.writeAll("\n");
}
try w.writeBody(s, else_body);
w.indent = old_indent;
try s.splatByteAll(' ', old_indent);
try s.writeAll("}");
}
fn writeSwitchBr(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
const switch_br = w.air.unwrapSwitch(inst);
const liveness: Air.Liveness.SwitchBrTable = if (w.liveness) |liveness|
liveness.getSwitchBr(w.gpa, inst, switch_br.cases_len + 1) catch
@panic("out of memory")
else blk: {
const slice = w.gpa.alloc([]const Air.Inst.Index, switch_br.cases_len + 1) catch
@panic("out of memory");
@memset(slice, &.{});
break :blk .{ .deaths = slice };
};
defer w.gpa.free(liveness.deaths);
try w.writeOperand(s, inst, 0, switch_br.operand);
if (w.skip_body) return s.writeAll(", ...");
const old_indent = w.indent;
w.indent += 2;
var it = switch_br.iterateCases();
while (it.next()) |case| {
try s.writeAll(", [");
for (case.items, 0..) |item, item_i| {
if (item_i != 0) try s.writeAll(", ");
try w.writeInstRef(s, item, false);
}
for (case.ranges, 0..) |range, range_i| {
if (range_i != 0 or case.items.len != 0) try s.writeAll(", ");
try w.writeInstRef(s, range[0], false);
try s.writeAll("...");
try w.writeInstRef(s, range[1], false);
}
try s.writeAll("] ");
const hint = switch_br.getHint(case.idx);
if (hint != .none) {
try s.print(".{s} ", .{@tagName(hint)});
}
try s.writeAll("=> {\n");
w.indent += 2;
const deaths = liveness.deaths[case.idx];
if (deaths.len != 0) {
try s.splatByteAll(' ', w.indent);
for (deaths, 0..) |operand, i| {
if (i != 0) try s.writeAll(" ");
try s.print("{f}!", .{operand});
}
try s.writeAll("\n");
}
try w.writeBody(s, case.body);
w.indent -= 2;
try s.splatByteAll(' ', w.indent);
try s.writeAll("}");
}
const else_body = it.elseBody();
if (else_body.len != 0) {
try s.writeAll(", else ");
const hint = switch_br.getElseHint();
if (hint != .none) {
try s.print(".{s} ", .{@tagName(hint)});
}
try s.writeAll("=> {\n");
w.indent += 2;
const deaths = liveness.deaths[liveness.deaths.len - 1];
if (deaths.len != 0) {
try s.splatByteAll(' ', w.indent);
for (deaths, 0..) |operand, i| {
if (i != 0) try s.writeAll(" ");
try s.print("{f}!", .{operand});
}
try s.writeAll("\n");
}
try w.writeBody(s, else_body);
w.indent -= 2;
try s.splatByteAll(' ', w.indent);
try s.writeAll("}");
}
try s.writeAll("\n");
try s.splatByteAll(' ', old_indent);
}
fn writeWasmMemorySize(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
const pl_op = w.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
try s.print("{d}", .{pl_op.payload});
}
fn writeWasmMemoryGrow(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
const pl_op = w.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
try s.print("{d}, ", .{pl_op.payload});
try w.writeOperand(s, inst, 0, pl_op.operand);
}
fn writeWorkDimension(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
const pl_op = w.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
try s.print("{d}", .{pl_op.payload});
}
fn writeOperand(
w: *Writer,
s: *std.Io.Writer,
inst: Air.Inst.Index,
op_index: usize,
operand: Air.Inst.Ref,
) Error!void {
const small_tomb_bits = Air.Liveness.bpi - 1;
const dies = if (w.liveness) |liveness| blk: {
if (op_index < small_tomb_bits)
break :blk liveness.operandDies(inst, @intCast(op_index));
var extra_index = liveness.special.get(inst).?;
var tomb_op_index: usize = small_tomb_bits;
while (true) {
const bits = liveness.extra[extra_index];
if (op_index < tomb_op_index + 31) {
break :blk @as(u1, @truncate(bits >> @as(u5, @intCast(op_index - tomb_op_index)))) != 0;
}
if ((bits >> 31) != 0) break :blk false;
extra_index += 1;
tomb_op_index += 31;
}
} else false;
return w.writeInstRef(s, operand, dies);
}
fn writeInstRef(
w: *Writer,
s: *std.Io.Writer,
operand: Air.Inst.Ref,
dies: bool,
) Error!void {
if (@intFromEnum(operand) < InternPool.static_len) {
return s.print("@{}", .{operand});
} else if (operand.toInterned()) |ip_index| {
const pt = w.pt;
const ty = Type.fromInterned(pt.zcu.intern_pool.indexToKey(ip_index).typeOf());
try s.print("<{f}, {f}>", .{
ty.fmt(pt),
Value.fromInterned(ip_index).fmtValue(pt),
});
} else {
return w.writeInstIndex(s, operand.toIndex().?, dies);
}
}
fn writeInstIndex(
w: *Writer,
s: *std.Io.Writer,
inst: Air.Inst.Index,
dies: bool,
) Error!void {
_ = w;
try s.print("{f}", .{inst});
if (dies) try s.writeByte('!');
}
fn typeOfIndex(w: *Writer, inst: Air.Inst.Index) Type {
const zcu = w.pt.zcu;
return w.air.typeOfIndex(inst, &zcu.intern_pool);
}
};