master
   1const std = @import("std");
   2const Allocator = std.mem.Allocator;
   3const assert = std.debug.assert;
   4
   5const build_options = @import("build_options");
   6const Zcu = @import("../Zcu.zig");
   7const Value = @import("../Value.zig");
   8const Type = @import("../Type.zig");
   9const Air = @import("../Air.zig");
  10const InternPool = @import("../InternPool.zig");
  11
  12pub fn write(air: Air, stream: *std.Io.Writer, pt: Zcu.PerThread, liveness: ?Air.Liveness) void {
  13    comptime assert(build_options.enable_debug_extensions);
  14    const instruction_bytes = air.instructions.len *
  15        // Here we don't use @sizeOf(Air.Inst.Data) because it would include
  16        // the debug safety tag but we want to measure release size.
  17        (@sizeOf(Air.Inst.Tag) + 8);
  18    const extra_bytes = air.extra.items.len * @sizeOf(u32);
  19    const tomb_bytes = if (liveness) |l| l.tomb_bits.len * @sizeOf(usize) else 0;
  20    const liveness_extra_bytes = if (liveness) |l| l.extra.len * @sizeOf(u32) else 0;
  21    const liveness_special_bytes = if (liveness) |l| l.special.count() * 8 else 0;
  22    const total_bytes = @sizeOf(Air) + instruction_bytes + extra_bytes +
  23        @sizeOf(Air.Liveness) + liveness_extra_bytes +
  24        liveness_special_bytes + tomb_bytes;
  25
  26    // zig fmt: off
  27    stream.print(
  28        \\# Total AIR+Liveness bytes: {Bi}
  29        \\# AIR Instructions:         {d} ({Bi})
  30        \\# AIR Extra Data:           {d} ({Bi})
  31        \\# Liveness tomb_bits:       {Bi}
  32        \\# Liveness Extra Data:      {d} ({Bi})
  33        \\# Liveness special table:   {d} ({Bi})
  34        \\
  35    , .{
  36        total_bytes,
  37        air.instructions.len, instruction_bytes,
  38        air.extra.items.len, extra_bytes,
  39        tomb_bytes,
  40        if (liveness) |l| l.extra.len else 0, liveness_extra_bytes,
  41        if (liveness) |l| l.special.count() else 0, liveness_special_bytes,
  42    }) catch return;
  43    // zig fmt: on
  44
  45    var writer: Writer = .{
  46        .pt = pt,
  47        .gpa = pt.zcu.gpa,
  48        .air = air,
  49        .liveness = liveness,
  50        .indent = 2,
  51        .skip_body = false,
  52    };
  53    writer.writeBody(stream, air.getMainBody()) catch return;
  54}
  55
  56pub fn writeInst(
  57    air: Air,
  58    stream: *std.Io.Writer,
  59    inst: Air.Inst.Index,
  60    pt: Zcu.PerThread,
  61    liveness: ?Air.Liveness,
  62) void {
  63    comptime assert(build_options.enable_debug_extensions);
  64    var writer: Writer = .{
  65        .pt = pt,
  66        .gpa = pt.zcu.gpa,
  67        .air = air,
  68        .liveness = liveness,
  69        .indent = 2,
  70        .skip_body = true,
  71    };
  72    writer.writeInst(stream, inst) catch return;
  73}
  74
  75pub fn dump(air: Air, pt: Zcu.PerThread, liveness: ?Air.Liveness) void {
  76    const stderr_bw, _ = std.debug.lockStderrWriter(&.{});
  77    defer std.debug.unlockStderrWriter();
  78    air.write(stderr_bw, pt, liveness);
  79}
  80
  81pub fn dumpInst(air: Air, inst: Air.Inst.Index, pt: Zcu.PerThread, liveness: ?Air.Liveness) void {
  82    const stderr_bw, _ = std.debug.lockStderrWriter(&.{});
  83    defer std.debug.unlockStderrWriter();
  84    air.writeInst(stderr_bw, inst, pt, liveness);
  85}
  86
  87const Writer = struct {
  88    pt: Zcu.PerThread,
  89    gpa: Allocator,
  90    air: Air,
  91    liveness: ?Air.Liveness,
  92    indent: usize,
  93    skip_body: bool,
  94
  95    const Error = std.Io.Writer.Error;
  96
  97    fn writeBody(w: *Writer, s: *std.Io.Writer, body: []const Air.Inst.Index) Error!void {
  98        for (body) |inst| {
  99            try w.writeInst(s, inst);
 100            try s.writeByte('\n');
 101        }
 102    }
 103
 104    fn writeInst(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
 105        const tag = w.air.instructions.items(.tag)[@intFromEnum(inst)];
 106        try s.splatByteAll(' ', w.indent);
 107        try s.print("{f}{c}= {s}(", .{
 108            inst,
 109            @as(u8, if (if (w.liveness) |liveness| liveness.isUnused(inst) else false) '!' else ' '),
 110            @tagName(tag),
 111        });
 112        switch (tag) {
 113            .add,
 114            .add_optimized,
 115            .add_safe,
 116            .add_wrap,
 117            .add_sat,
 118            .sub,
 119            .sub_optimized,
 120            .sub_safe,
 121            .sub_wrap,
 122            .sub_sat,
 123            .mul,
 124            .mul_optimized,
 125            .mul_safe,
 126            .mul_wrap,
 127            .mul_sat,
 128            .div_float,
 129            .div_trunc,
 130            .div_floor,
 131            .div_exact,
 132            .rem,
 133            .mod,
 134            .bit_and,
 135            .bit_or,
 136            .xor,
 137            .cmp_lt,
 138            .cmp_lte,
 139            .cmp_eq,
 140            .cmp_gte,
 141            .cmp_gt,
 142            .cmp_neq,
 143            .bool_and,
 144            .bool_or,
 145            .store,
 146            .store_safe,
 147            .array_elem_val,
 148            .slice_elem_val,
 149            .ptr_elem_val,
 150            .shl,
 151            .shl_exact,
 152            .shl_sat,
 153            .shr,
 154            .shr_exact,
 155            .set_union_tag,
 156            .min,
 157            .max,
 158            .div_float_optimized,
 159            .div_trunc_optimized,
 160            .div_floor_optimized,
 161            .div_exact_optimized,
 162            .rem_optimized,
 163            .mod_optimized,
 164            .cmp_lt_optimized,
 165            .cmp_lte_optimized,
 166            .cmp_eq_optimized,
 167            .cmp_gte_optimized,
 168            .cmp_gt_optimized,
 169            .cmp_neq_optimized,
 170            .memcpy,
 171            .memmove,
 172            .memset,
 173            .memset_safe,
 174            .legalize_vec_elem_val,
 175            => try w.writeBinOp(s, inst),
 176
 177            .is_null,
 178            .is_non_null,
 179            .is_null_ptr,
 180            .is_non_null_ptr,
 181            .is_err,
 182            .is_non_err,
 183            .is_err_ptr,
 184            .is_non_err_ptr,
 185            .ret,
 186            .ret_safe,
 187            .ret_load,
 188            .is_named_enum_value,
 189            .tag_name,
 190            .error_name,
 191            .sqrt,
 192            .sin,
 193            .cos,
 194            .tan,
 195            .exp,
 196            .exp2,
 197            .log,
 198            .log2,
 199            .log10,
 200            .floor,
 201            .ceil,
 202            .round,
 203            .trunc_float,
 204            .neg,
 205            .neg_optimized,
 206            .cmp_lt_errors_len,
 207            .set_err_return_trace,
 208            .c_va_end,
 209            => try w.writeUnOp(s, inst),
 210
 211            .trap,
 212            .breakpoint,
 213            .dbg_empty_stmt,
 214            .unreach,
 215            .ret_addr,
 216            .frame_addr,
 217            .save_err_return_trace_index,
 218            => try w.writeNoOp(s, inst),
 219
 220            .alloc,
 221            .ret_ptr,
 222            .err_return_trace,
 223            .c_va_start,
 224            => try w.writeTy(s, inst),
 225
 226            .arg => try w.writeArg(s, inst),
 227
 228            .not,
 229            .bitcast,
 230            .load,
 231            .fptrunc,
 232            .fpext,
 233            .intcast,
 234            .intcast_safe,
 235            .trunc,
 236            .optional_payload,
 237            .optional_payload_ptr,
 238            .optional_payload_ptr_set,
 239            .errunion_payload_ptr_set,
 240            .wrap_optional,
 241            .unwrap_errunion_payload,
 242            .unwrap_errunion_err,
 243            .unwrap_errunion_payload_ptr,
 244            .unwrap_errunion_err_ptr,
 245            .wrap_errunion_payload,
 246            .wrap_errunion_err,
 247            .slice_ptr,
 248            .slice_len,
 249            .ptr_slice_len_ptr,
 250            .ptr_slice_ptr_ptr,
 251            .struct_field_ptr_index_0,
 252            .struct_field_ptr_index_1,
 253            .struct_field_ptr_index_2,
 254            .struct_field_ptr_index_3,
 255            .array_to_slice,
 256            .float_from_int,
 257            .splat,
 258            .int_from_float,
 259            .int_from_float_optimized,
 260            .int_from_float_safe,
 261            .int_from_float_optimized_safe,
 262            .get_union_tag,
 263            .clz,
 264            .ctz,
 265            .popcount,
 266            .byte_swap,
 267            .bit_reverse,
 268            .abs,
 269            .error_set_has_value,
 270            .addrspace_cast,
 271            .c_va_arg,
 272            .c_va_copy,
 273            => try w.writeTyOp(s, inst),
 274
 275            .block, .dbg_inline_block => try w.writeBlock(s, tag, inst),
 276
 277            .loop => try w.writeLoop(s, inst),
 278
 279            .slice,
 280            .slice_elem_ptr,
 281            .ptr_elem_ptr,
 282            .ptr_add,
 283            .ptr_sub,
 284            .add_with_overflow,
 285            .sub_with_overflow,
 286            .mul_with_overflow,
 287            .shl_with_overflow,
 288            => try w.writeTyPlBin(s, inst),
 289
 290            .call,
 291            .call_always_tail,
 292            .call_never_tail,
 293            .call_never_inline,
 294            => try w.writeCall(s, inst),
 295
 296            .dbg_var_ptr,
 297            .dbg_var_val,
 298            .dbg_arg_inline,
 299            => try w.writeDbgVar(s, inst),
 300
 301            .struct_field_ptr => try w.writeStructField(s, inst),
 302            .struct_field_val => try w.writeStructField(s, inst),
 303            .inferred_alloc => @panic("TODO"),
 304            .inferred_alloc_comptime => @panic("TODO"),
 305            .assembly => try w.writeAssembly(s, inst),
 306            .dbg_stmt => try w.writeDbgStmt(s, inst),
 307
 308            .aggregate_init => try w.writeAggregateInit(s, inst),
 309            .union_init => try w.writeUnionInit(s, inst),
 310            .br => try w.writeBr(s, inst),
 311            .switch_dispatch => try w.writeBr(s, inst),
 312            .repeat => try w.writeRepeat(s, inst),
 313            .cond_br => try w.writeCondBr(s, inst),
 314            .@"try", .try_cold => try w.writeTry(s, inst),
 315            .try_ptr, .try_ptr_cold => try w.writeTryPtr(s, inst),
 316            .loop_switch_br, .switch_br => try w.writeSwitchBr(s, inst),
 317            .cmpxchg_weak, .cmpxchg_strong => try w.writeCmpxchg(s, inst),
 318            .atomic_load => try w.writeAtomicLoad(s, inst),
 319            .prefetch => try w.writePrefetch(s, inst),
 320            .atomic_store_unordered => try w.writeAtomicStore(s, inst, .unordered),
 321            .atomic_store_monotonic => try w.writeAtomicStore(s, inst, .monotonic),
 322            .atomic_store_release => try w.writeAtomicStore(s, inst, .release),
 323            .atomic_store_seq_cst => try w.writeAtomicStore(s, inst, .seq_cst),
 324            .atomic_rmw => try w.writeAtomicRmw(s, inst),
 325            .field_parent_ptr => try w.writeFieldParentPtr(s, inst),
 326            .wasm_memory_size => try w.writeWasmMemorySize(s, inst),
 327            .wasm_memory_grow => try w.writeWasmMemoryGrow(s, inst),
 328            .mul_add => try w.writeMulAdd(s, inst),
 329            .select => try w.writeSelect(s, inst),
 330            .shuffle_one => try w.writeShuffleOne(s, inst),
 331            .shuffle_two => try w.writeShuffleTwo(s, inst),
 332            .reduce, .reduce_optimized => try w.writeReduce(s, inst),
 333            .cmp_vector, .cmp_vector_optimized => try w.writeCmpVector(s, inst),
 334            .runtime_nav_ptr => try w.writeRuntimeNavPtr(s, inst),
 335            .legalize_vec_store_elem => try w.writeLegalizeVecStoreElem(s, inst),
 336            .legalize_compiler_rt_call => try w.writeLegalizeCompilerRtCall(s, inst),
 337
 338            .work_item_id,
 339            .work_group_size,
 340            .work_group_id,
 341            => try w.writeWorkDimension(s, inst),
 342        }
 343        try s.writeByte(')');
 344    }
 345
 346    fn writeBinOp(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
 347        const bin_op = w.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
 348        try w.writeOperand(s, inst, 0, bin_op.lhs);
 349        try s.writeAll(", ");
 350        try w.writeOperand(s, inst, 1, bin_op.rhs);
 351    }
 352
 353    fn writeUnOp(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
 354        const un_op = w.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
 355        try w.writeOperand(s, inst, 0, un_op);
 356    }
 357
 358    fn writeNoOp(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
 359        _ = w;
 360        _ = s;
 361        _ = inst;
 362        // no-op, no argument to write
 363    }
 364
 365    fn writeType(w: *Writer, s: *std.Io.Writer, ty: Type) !void {
 366        return ty.print(s, w.pt, null);
 367    }
 368
 369    fn writeTy(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
 370        const ty = w.air.instructions.items(.data)[@intFromEnum(inst)].ty;
 371        try w.writeType(s, ty);
 372    }
 373
 374    fn writeArg(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
 375        const arg = w.air.instructions.items(.data)[@intFromEnum(inst)].arg;
 376        try w.writeType(s, arg.ty.toType());
 377        try s.print(", {d}", .{arg.zir_param_index});
 378    }
 379
 380    fn writeTyOp(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
 381        const ty_op = w.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
 382        try w.writeType(s, ty_op.ty.toType());
 383        try s.writeAll(", ");
 384        try w.writeOperand(s, inst, 0, ty_op.operand);
 385    }
 386
 387    fn writeBlock(w: *Writer, s: *std.Io.Writer, tag: Air.Inst.Tag, inst: Air.Inst.Index) Error!void {
 388        const ty_pl = w.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
 389        try w.writeType(s, ty_pl.ty.toType());
 390        const body: []const Air.Inst.Index = @ptrCast(switch (tag) {
 391            inline .block, .dbg_inline_block => |comptime_tag| body: {
 392                const extra = w.air.extraData(switch (comptime_tag) {
 393                    .block => Air.Block,
 394                    .dbg_inline_block => Air.DbgInlineBlock,
 395                    else => unreachable,
 396                }, ty_pl.payload);
 397                switch (comptime_tag) {
 398                    .block => {},
 399                    .dbg_inline_block => {
 400                        try s.writeAll(", ");
 401                        try w.writeInstRef(s, Air.internedToRef(extra.data.func), false);
 402                    },
 403                    else => unreachable,
 404                }
 405                break :body w.air.extra.items[extra.end..][0..extra.data.body_len];
 406            },
 407            else => unreachable,
 408        });
 409        if (w.skip_body) return s.writeAll(", ...");
 410        const liveness_block: Air.Liveness.BlockSlices = if (w.liveness) |liveness|
 411            liveness.getBlock(inst)
 412        else
 413            .{ .deaths = &.{} };
 414
 415        try s.writeAll(", {\n");
 416        const old_indent = w.indent;
 417        w.indent += 2;
 418        try w.writeBody(s, body);
 419        w.indent = old_indent;
 420        try s.splatByteAll(' ', w.indent);
 421        try s.writeAll("}");
 422
 423        for (liveness_block.deaths) |operand| {
 424            try s.print(" {f}!", .{operand});
 425        }
 426    }
 427
 428    fn writeLoop(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
 429        const ty_pl = w.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
 430        const extra = w.air.extraData(Air.Block, ty_pl.payload);
 431        const body: []const Air.Inst.Index = @ptrCast(w.air.extra.items[extra.end..][0..extra.data.body_len]);
 432
 433        try w.writeType(s, ty_pl.ty.toType());
 434        if (w.skip_body) return s.writeAll(", ...");
 435        try s.writeAll(", {\n");
 436        const old_indent = w.indent;
 437        w.indent += 2;
 438        try w.writeBody(s, body);
 439        w.indent = old_indent;
 440        try s.splatByteAll(' ', w.indent);
 441        try s.writeAll("}");
 442    }
 443
 444    fn writeAggregateInit(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
 445        const zcu = w.pt.zcu;
 446        const ty_pl = w.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
 447        const vector_ty = ty_pl.ty.toType();
 448        const len = @as(usize, @intCast(vector_ty.arrayLen(zcu)));
 449        const elements = @as([]const Air.Inst.Ref, @ptrCast(w.air.extra.items[ty_pl.payload..][0..len]));
 450
 451        try w.writeType(s, vector_ty);
 452        try s.writeAll(", [");
 453        for (elements, 0..) |elem, i| {
 454            if (i != 0) try s.writeAll(", ");
 455            try w.writeOperand(s, inst, i, elem);
 456        }
 457        try s.writeAll("]");
 458    }
 459
 460    fn writeUnionInit(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
 461        const ty_pl = w.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
 462        const extra = w.air.extraData(Air.UnionInit, ty_pl.payload).data;
 463
 464        try s.print("{d}, ", .{extra.field_index});
 465        try w.writeOperand(s, inst, 0, extra.init);
 466    }
 467
 468    fn writeStructField(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
 469        const ty_pl = w.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
 470        const extra = w.air.extraData(Air.StructField, ty_pl.payload).data;
 471
 472        try w.writeOperand(s, inst, 0, extra.struct_operand);
 473        try s.print(", {d}", .{extra.field_index});
 474    }
 475
 476    fn writeTyPlBin(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
 477        const data = w.air.instructions.items(.data);
 478        const ty_pl = data[@intFromEnum(inst)].ty_pl;
 479        const extra = w.air.extraData(Air.Bin, ty_pl.payload).data;
 480
 481        const inst_ty = data[@intFromEnum(inst)].ty_pl.ty.toType();
 482        try w.writeType(s, inst_ty);
 483        try s.writeAll(", ");
 484        try w.writeOperand(s, inst, 0, extra.lhs);
 485        try s.writeAll(", ");
 486        try w.writeOperand(s, inst, 1, extra.rhs);
 487    }
 488
 489    fn writeCmpxchg(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
 490        const ty_pl = w.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
 491        const extra = w.air.extraData(Air.Cmpxchg, ty_pl.payload).data;
 492
 493        try w.writeOperand(s, inst, 0, extra.ptr);
 494        try s.writeAll(", ");
 495        try w.writeOperand(s, inst, 1, extra.expected_value);
 496        try s.writeAll(", ");
 497        try w.writeOperand(s, inst, 2, extra.new_value);
 498        try s.print(", {s}, {s}", .{
 499            @tagName(extra.successOrder()), @tagName(extra.failureOrder()),
 500        });
 501    }
 502
 503    fn writeMulAdd(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
 504        const pl_op = w.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
 505        const extra = w.air.extraData(Air.Bin, pl_op.payload).data;
 506
 507        try w.writeOperand(s, inst, 0, extra.lhs);
 508        try s.writeAll(", ");
 509        try w.writeOperand(s, inst, 1, extra.rhs);
 510        try s.writeAll(", ");
 511        try w.writeOperand(s, inst, 2, pl_op.operand);
 512    }
 513
 514    fn writeLegalizeVecStoreElem(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
 515        const pl_op = w.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
 516        const bin = w.air.extraData(Air.Bin, pl_op.payload).data;
 517
 518        try w.writeOperand(s, inst, 0, pl_op.operand);
 519        try s.writeAll(", ");
 520        try w.writeOperand(s, inst, 1, bin.lhs);
 521        try s.writeAll(", ");
 522        try w.writeOperand(s, inst, 2, bin.rhs);
 523        try s.writeAll(", ");
 524    }
 525
 526    fn writeLegalizeCompilerRtCall(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
 527        const inst_data = w.air.instructions.items(.data)[@intFromEnum(inst)].legalize_compiler_rt_call;
 528        const extra = w.air.extraData(Air.Call, inst_data.payload);
 529        const args: []const Air.Inst.Ref = @ptrCast(w.air.extra.items[extra.end..][0..extra.data.args_len]);
 530
 531        try s.print("{t}, [", .{inst_data.func});
 532        for (args, 0..) |arg, i| {
 533            if (i != 0) try s.writeAll(", ");
 534            try w.writeOperand(s, inst, i, arg);
 535        }
 536        try s.writeByte(']');
 537    }
 538
 539    fn writeShuffleOne(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
 540        const unwrapped = w.air.unwrapShuffleOne(w.pt.zcu, inst);
 541        try w.writeType(s, unwrapped.result_ty);
 542        try s.writeAll(", ");
 543        try w.writeOperand(s, inst, 0, unwrapped.operand);
 544        try s.writeAll(", [");
 545        for (unwrapped.mask, 0..) |mask_elem, mask_idx| {
 546            if (mask_idx > 0) try s.writeAll(", ");
 547            switch (mask_elem.unwrap()) {
 548                .elem => |idx| try s.print("elem {d}", .{idx}),
 549                .value => |val| try s.print("val {f}", .{Value.fromInterned(val).fmtValue(w.pt)}),
 550            }
 551        }
 552        try s.writeByte(']');
 553    }
 554
 555    fn writeShuffleTwo(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
 556        const unwrapped = w.air.unwrapShuffleTwo(w.pt.zcu, inst);
 557        try w.writeType(s, unwrapped.result_ty);
 558        try s.writeAll(", ");
 559        try w.writeOperand(s, inst, 0, unwrapped.operand_a);
 560        try s.writeAll(", ");
 561        try w.writeOperand(s, inst, 1, unwrapped.operand_b);
 562        try s.writeAll(", [");
 563        for (unwrapped.mask, 0..) |mask_elem, mask_idx| {
 564            if (mask_idx > 0) try s.writeAll(", ");
 565            switch (mask_elem.unwrap()) {
 566                .a_elem => |idx| try s.print("a_elem {d}", .{idx}),
 567                .b_elem => |idx| try s.print("b_elem {d}", .{idx}),
 568                .undef => try s.writeAll("undef"),
 569            }
 570        }
 571        try s.writeByte(']');
 572    }
 573
 574    fn writeSelect(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
 575        const zcu = w.pt.zcu;
 576        const pl_op = w.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
 577        const extra = w.air.extraData(Air.Bin, pl_op.payload).data;
 578
 579        const elem_ty = w.typeOfIndex(inst).childType(zcu);
 580        try w.writeType(s, elem_ty);
 581        try s.writeAll(", ");
 582        try w.writeOperand(s, inst, 0, pl_op.operand);
 583        try s.writeAll(", ");
 584        try w.writeOperand(s, inst, 1, extra.lhs);
 585        try s.writeAll(", ");
 586        try w.writeOperand(s, inst, 2, extra.rhs);
 587    }
 588
 589    fn writeReduce(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
 590        const reduce = w.air.instructions.items(.data)[@intFromEnum(inst)].reduce;
 591
 592        try w.writeOperand(s, inst, 0, reduce.operand);
 593        try s.print(", {s}", .{@tagName(reduce.operation)});
 594    }
 595
 596    fn writeCmpVector(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
 597        const ty_pl = w.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
 598        const extra = w.air.extraData(Air.VectorCmp, ty_pl.payload).data;
 599
 600        try s.print("{s}, ", .{@tagName(extra.compareOperator())});
 601        try w.writeOperand(s, inst, 0, extra.lhs);
 602        try s.writeAll(", ");
 603        try w.writeOperand(s, inst, 1, extra.rhs);
 604    }
 605
 606    fn writeRuntimeNavPtr(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
 607        const ip = &w.pt.zcu.intern_pool;
 608        const ty_nav = w.air.instructions.items(.data)[@intFromEnum(inst)].ty_nav;
 609        try w.writeType(s, .fromInterned(ty_nav.ty));
 610        try s.print(", '{f}'", .{ip.getNav(ty_nav.nav).fqn.fmt(ip)});
 611    }
 612
 613    fn writeAtomicLoad(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
 614        const atomic_load = w.air.instructions.items(.data)[@intFromEnum(inst)].atomic_load;
 615
 616        try w.writeOperand(s, inst, 0, atomic_load.ptr);
 617        try s.print(", {s}", .{@tagName(atomic_load.order)});
 618    }
 619
 620    fn writePrefetch(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
 621        const prefetch = w.air.instructions.items(.data)[@intFromEnum(inst)].prefetch;
 622
 623        try w.writeOperand(s, inst, 0, prefetch.ptr);
 624        try s.print(", {s}, {d}, {s}", .{
 625            @tagName(prefetch.rw), prefetch.locality, @tagName(prefetch.cache),
 626        });
 627    }
 628
 629    fn writeAtomicStore(
 630        w: *Writer,
 631        s: *std.Io.Writer,
 632        inst: Air.Inst.Index,
 633        order: std.builtin.AtomicOrder,
 634    ) Error!void {
 635        const bin_op = w.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
 636        try w.writeOperand(s, inst, 0, bin_op.lhs);
 637        try s.writeAll(", ");
 638        try w.writeOperand(s, inst, 1, bin_op.rhs);
 639        try s.print(", {s}", .{@tagName(order)});
 640    }
 641
 642    fn writeAtomicRmw(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
 643        const pl_op = w.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
 644        const extra = w.air.extraData(Air.AtomicRmw, pl_op.payload).data;
 645
 646        try w.writeOperand(s, inst, 0, pl_op.operand);
 647        try s.writeAll(", ");
 648        try w.writeOperand(s, inst, 1, extra.operand);
 649        try s.print(", {s}, {s}", .{ @tagName(extra.op()), @tagName(extra.ordering()) });
 650    }
 651
 652    fn writeFieldParentPtr(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
 653        const ty_pl = w.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
 654        const extra = w.air.extraData(Air.FieldParentPtr, ty_pl.payload).data;
 655
 656        try w.writeOperand(s, inst, 0, extra.field_ptr);
 657        try s.print(", {d}", .{extra.field_index});
 658    }
 659
 660    fn writeAssembly(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
 661        const ty_pl = w.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
 662        const extra = w.air.extraData(Air.Asm, ty_pl.payload);
 663        const is_volatile = extra.data.flags.is_volatile;
 664        const outputs_len = extra.data.flags.outputs_len;
 665        var extra_i: usize = extra.end;
 666        var op_index: usize = 0;
 667
 668        const ret_ty = w.typeOfIndex(inst);
 669        try w.writeType(s, ret_ty);
 670
 671        if (is_volatile) {
 672            try s.writeAll(", volatile");
 673        }
 674
 675        const outputs = @as([]const Air.Inst.Ref, @ptrCast(w.air.extra.items[extra_i..][0..outputs_len]));
 676        extra_i += outputs.len;
 677        const inputs = @as([]const Air.Inst.Ref, @ptrCast(w.air.extra.items[extra_i..][0..extra.data.inputs_len]));
 678        extra_i += inputs.len;
 679
 680        for (outputs) |output| {
 681            const extra_bytes = std.mem.sliceAsBytes(w.air.extra.items[extra_i..]);
 682            const constraint = std.mem.sliceTo(extra_bytes, 0);
 683            const name = std.mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0);
 684
 685            // This equation accounts for the fact that even if we have exactly 4 bytes
 686            // for the strings and their null terminators, we still use the next u32
 687            // for the null terminator.
 688            extra_i += (constraint.len + name.len + (2 + 3)) / 4;
 689
 690            if (output == .none) {
 691                try s.print(", [{s}] -> {s}", .{ name, constraint });
 692            } else {
 693                try s.print(", [{s}] out {s} = (", .{ name, constraint });
 694                try w.writeOperand(s, inst, op_index, output);
 695                op_index += 1;
 696                try s.writeByte(')');
 697            }
 698        }
 699
 700        for (inputs) |input| {
 701            const extra_bytes = std.mem.sliceAsBytes(w.air.extra.items[extra_i..]);
 702            const constraint = std.mem.sliceTo(extra_bytes, 0);
 703            const name = std.mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0);
 704            // This equation accounts for the fact that even if we have exactly 4 bytes
 705            // for the strings and their null terminators, we still use the next u32
 706            // for the null terminator.
 707            extra_i += (constraint.len + name.len + 1) / 4 + 1;
 708
 709            try s.print(", [{s}] in {s} = (", .{ name, constraint });
 710            try w.writeOperand(s, inst, op_index, input);
 711            op_index += 1;
 712            try s.writeByte(')');
 713        }
 714
 715        const zcu = w.pt.zcu;
 716        const ip = &zcu.intern_pool;
 717        const aggregate = ip.indexToKey(extra.data.clobbers).aggregate;
 718        const struct_type: Type = .fromInterned(aggregate.ty);
 719        switch (aggregate.storage) {
 720            .elems => |elems| for (elems, 0..) |elem, i| {
 721                switch (elem) {
 722                    .bool_true => {
 723                        const clobber = struct_type.structFieldName(i, zcu).toSlice(ip).?;
 724                        assert(clobber.len != 0);
 725                        try s.writeAll(", ~{");
 726                        try s.writeAll(clobber);
 727                        try s.writeAll("}");
 728                    },
 729                    .bool_false => continue,
 730                    else => unreachable,
 731                }
 732            },
 733            .repeated_elem => |elem| {
 734                try s.writeAll(", ");
 735                try s.writeAll(switch (elem) {
 736                    .bool_true => "<all clobbers>",
 737                    .bool_false => "<no clobbers>",
 738                    else => unreachable,
 739                });
 740            },
 741            .bytes => |bytes| {
 742                try s.print(", {x}", .{bytes});
 743            },
 744        }
 745        const asm_source = std.mem.sliceAsBytes(w.air.extra.items[extra_i..])[0..extra.data.source_len];
 746        try s.print(", \"{f}\"", .{std.zig.fmtString(asm_source)});
 747    }
 748
 749    fn writeDbgStmt(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
 750        const dbg_stmt = w.air.instructions.items(.data)[@intFromEnum(inst)].dbg_stmt;
 751        try s.print("{d}:{d}", .{ dbg_stmt.line + 1, dbg_stmt.column + 1 });
 752    }
 753
 754    fn writeDbgVar(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
 755        const pl_op = w.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
 756        try w.writeOperand(s, inst, 0, pl_op.operand);
 757        const name: Air.NullTerminatedString = @enumFromInt(pl_op.payload);
 758        try s.print(", \"{f}\"", .{std.zig.fmtString(name.toSlice(w.air))});
 759    }
 760
 761    fn writeCall(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
 762        const pl_op = w.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
 763        const extra = w.air.extraData(Air.Call, pl_op.payload);
 764        const args = @as([]const Air.Inst.Ref, @ptrCast(w.air.extra.items[extra.end..][0..extra.data.args_len]));
 765        try w.writeOperand(s, inst, 0, pl_op.operand);
 766        try s.writeAll(", [");
 767        for (args, 0..) |arg, i| {
 768            if (i != 0) try s.writeAll(", ");
 769            try w.writeOperand(s, inst, 1 + i, arg);
 770        }
 771        try s.writeAll("]");
 772    }
 773
 774    fn writeBr(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
 775        const br = w.air.instructions.items(.data)[@intFromEnum(inst)].br;
 776        try w.writeInstIndex(s, br.block_inst, false);
 777        try s.writeAll(", ");
 778        try w.writeOperand(s, inst, 0, br.operand);
 779    }
 780
 781    fn writeRepeat(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
 782        const repeat = w.air.instructions.items(.data)[@intFromEnum(inst)].repeat;
 783        try w.writeInstIndex(s, repeat.loop_inst, false);
 784    }
 785
 786    fn writeTry(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
 787        const pl_op = w.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
 788        const extra = w.air.extraData(Air.Try, pl_op.payload);
 789        const body: []const Air.Inst.Index = @ptrCast(w.air.extra.items[extra.end..][0..extra.data.body_len]);
 790        const liveness_condbr: Air.Liveness.CondBrSlices = if (w.liveness) |liveness|
 791            liveness.getCondBr(inst)
 792        else
 793            .{ .then_deaths = &.{}, .else_deaths = &.{} };
 794
 795        try w.writeOperand(s, inst, 0, pl_op.operand);
 796        if (w.skip_body) return s.writeAll(", ...");
 797        try s.writeAll(", {\n");
 798        const old_indent = w.indent;
 799        w.indent += 2;
 800
 801        if (liveness_condbr.else_deaths.len != 0) {
 802            try s.splatByteAll(' ', w.indent);
 803            for (liveness_condbr.else_deaths, 0..) |operand, i| {
 804                if (i != 0) try s.writeAll(" ");
 805                try s.print("{f}!", .{operand});
 806            }
 807            try s.writeAll("\n");
 808        }
 809        try w.writeBody(s, body);
 810
 811        w.indent = old_indent;
 812        try s.splatByteAll(' ', w.indent);
 813        try s.writeAll("}");
 814
 815        for (liveness_condbr.then_deaths) |operand| {
 816            try s.print(" {f}!", .{operand});
 817        }
 818    }
 819
 820    fn writeTryPtr(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
 821        const ty_pl = w.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
 822        const extra = w.air.extraData(Air.TryPtr, ty_pl.payload);
 823        const body: []const Air.Inst.Index = @ptrCast(w.air.extra.items[extra.end..][0..extra.data.body_len]);
 824        const liveness_condbr: Air.Liveness.CondBrSlices = if (w.liveness) |liveness|
 825            liveness.getCondBr(inst)
 826        else
 827            .{ .then_deaths = &.{}, .else_deaths = &.{} };
 828
 829        try w.writeOperand(s, inst, 0, extra.data.ptr);
 830
 831        try s.writeAll(", ");
 832        try w.writeType(s, ty_pl.ty.toType());
 833        if (w.skip_body) return s.writeAll(", ...");
 834        try s.writeAll(", {\n");
 835        const old_indent = w.indent;
 836        w.indent += 2;
 837
 838        if (liveness_condbr.else_deaths.len != 0) {
 839            try s.splatByteAll(' ', w.indent);
 840            for (liveness_condbr.else_deaths, 0..) |operand, i| {
 841                if (i != 0) try s.writeAll(" ");
 842                try s.print("{f}!", .{operand});
 843            }
 844            try s.writeAll("\n");
 845        }
 846        try w.writeBody(s, body);
 847
 848        w.indent = old_indent;
 849        try s.splatByteAll(' ', w.indent);
 850        try s.writeAll("}");
 851
 852        for (liveness_condbr.then_deaths) |operand| {
 853            try s.print(" {f}!", .{operand});
 854        }
 855    }
 856
 857    fn writeCondBr(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
 858        const pl_op = w.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
 859        const extra = w.air.extraData(Air.CondBr, pl_op.payload);
 860        const then_body: []const Air.Inst.Index = @ptrCast(w.air.extra.items[extra.end..][0..extra.data.then_body_len]);
 861        const else_body: []const Air.Inst.Index = @ptrCast(w.air.extra.items[extra.end + then_body.len ..][0..extra.data.else_body_len]);
 862        const liveness_condbr: Air.Liveness.CondBrSlices = if (w.liveness) |liveness|
 863            liveness.getCondBr(inst)
 864        else
 865            .{ .then_deaths = &.{}, .else_deaths = &.{} };
 866
 867        try w.writeOperand(s, inst, 0, pl_op.operand);
 868        if (w.skip_body) return s.writeAll(", ...");
 869        try s.writeAll(",");
 870        if (extra.data.branch_hints.true != .none) {
 871            try s.print(" {s}", .{@tagName(extra.data.branch_hints.true)});
 872        }
 873        if (extra.data.branch_hints.then_cov != .none) {
 874            try s.print(" {s}", .{@tagName(extra.data.branch_hints.then_cov)});
 875        }
 876        try s.writeAll(" {\n");
 877        const old_indent = w.indent;
 878        w.indent += 2;
 879
 880        if (liveness_condbr.then_deaths.len != 0) {
 881            try s.splatByteAll(' ', w.indent);
 882            for (liveness_condbr.then_deaths, 0..) |operand, i| {
 883                if (i != 0) try s.writeAll(" ");
 884                try s.print("{f}!", .{operand});
 885            }
 886            try s.writeAll("\n");
 887        }
 888
 889        try w.writeBody(s, then_body);
 890        try s.splatByteAll(' ', old_indent);
 891        try s.writeAll("},");
 892        if (extra.data.branch_hints.false != .none) {
 893            try s.print(" {s}", .{@tagName(extra.data.branch_hints.false)});
 894        }
 895        if (extra.data.branch_hints.else_cov != .none) {
 896            try s.print(" {s}", .{@tagName(extra.data.branch_hints.else_cov)});
 897        }
 898        try s.writeAll(" {\n");
 899
 900        if (liveness_condbr.else_deaths.len != 0) {
 901            try s.splatByteAll(' ', w.indent);
 902            for (liveness_condbr.else_deaths, 0..) |operand, i| {
 903                if (i != 0) try s.writeAll(" ");
 904                try s.print("{f}!", .{operand});
 905            }
 906            try s.writeAll("\n");
 907        }
 908
 909        try w.writeBody(s, else_body);
 910        w.indent = old_indent;
 911
 912        try s.splatByteAll(' ', old_indent);
 913        try s.writeAll("}");
 914    }
 915
 916    fn writeSwitchBr(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
 917        const switch_br = w.air.unwrapSwitch(inst);
 918
 919        const liveness: Air.Liveness.SwitchBrTable = if (w.liveness) |liveness|
 920            liveness.getSwitchBr(w.gpa, inst, switch_br.cases_len + 1) catch
 921                @panic("out of memory")
 922        else blk: {
 923            const slice = w.gpa.alloc([]const Air.Inst.Index, switch_br.cases_len + 1) catch
 924                @panic("out of memory");
 925            @memset(slice, &.{});
 926            break :blk .{ .deaths = slice };
 927        };
 928        defer w.gpa.free(liveness.deaths);
 929
 930        try w.writeOperand(s, inst, 0, switch_br.operand);
 931        if (w.skip_body) return s.writeAll(", ...");
 932        const old_indent = w.indent;
 933        w.indent += 2;
 934
 935        var it = switch_br.iterateCases();
 936        while (it.next()) |case| {
 937            try s.writeAll(", [");
 938            for (case.items, 0..) |item, item_i| {
 939                if (item_i != 0) try s.writeAll(", ");
 940                try w.writeInstRef(s, item, false);
 941            }
 942            for (case.ranges, 0..) |range, range_i| {
 943                if (range_i != 0 or case.items.len != 0) try s.writeAll(", ");
 944                try w.writeInstRef(s, range[0], false);
 945                try s.writeAll("...");
 946                try w.writeInstRef(s, range[1], false);
 947            }
 948            try s.writeAll("] ");
 949            const hint = switch_br.getHint(case.idx);
 950            if (hint != .none) {
 951                try s.print(".{s} ", .{@tagName(hint)});
 952            }
 953            try s.writeAll("=> {\n");
 954            w.indent += 2;
 955
 956            const deaths = liveness.deaths[case.idx];
 957            if (deaths.len != 0) {
 958                try s.splatByteAll(' ', w.indent);
 959                for (deaths, 0..) |operand, i| {
 960                    if (i != 0) try s.writeAll(" ");
 961                    try s.print("{f}!", .{operand});
 962                }
 963                try s.writeAll("\n");
 964            }
 965
 966            try w.writeBody(s, case.body);
 967            w.indent -= 2;
 968            try s.splatByteAll(' ', w.indent);
 969            try s.writeAll("}");
 970        }
 971
 972        const else_body = it.elseBody();
 973        if (else_body.len != 0) {
 974            try s.writeAll(", else ");
 975            const hint = switch_br.getElseHint();
 976            if (hint != .none) {
 977                try s.print(".{s} ", .{@tagName(hint)});
 978            }
 979            try s.writeAll("=> {\n");
 980            w.indent += 2;
 981
 982            const deaths = liveness.deaths[liveness.deaths.len - 1];
 983            if (deaths.len != 0) {
 984                try s.splatByteAll(' ', w.indent);
 985                for (deaths, 0..) |operand, i| {
 986                    if (i != 0) try s.writeAll(" ");
 987                    try s.print("{f}!", .{operand});
 988                }
 989                try s.writeAll("\n");
 990            }
 991
 992            try w.writeBody(s, else_body);
 993            w.indent -= 2;
 994            try s.splatByteAll(' ', w.indent);
 995            try s.writeAll("}");
 996        }
 997
 998        try s.writeAll("\n");
 999        try s.splatByteAll(' ', old_indent);
1000    }
1001
1002    fn writeWasmMemorySize(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
1003        const pl_op = w.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
1004        try s.print("{d}", .{pl_op.payload});
1005    }
1006
1007    fn writeWasmMemoryGrow(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
1008        const pl_op = w.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
1009        try s.print("{d}, ", .{pl_op.payload});
1010        try w.writeOperand(s, inst, 0, pl_op.operand);
1011    }
1012
1013    fn writeWorkDimension(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
1014        const pl_op = w.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
1015        try s.print("{d}", .{pl_op.payload});
1016    }
1017
1018    fn writeOperand(
1019        w: *Writer,
1020        s: *std.Io.Writer,
1021        inst: Air.Inst.Index,
1022        op_index: usize,
1023        operand: Air.Inst.Ref,
1024    ) Error!void {
1025        const small_tomb_bits = Air.Liveness.bpi - 1;
1026        const dies = if (w.liveness) |liveness| blk: {
1027            if (op_index < small_tomb_bits)
1028                break :blk liveness.operandDies(inst, @intCast(op_index));
1029            var extra_index = liveness.special.get(inst).?;
1030            var tomb_op_index: usize = small_tomb_bits;
1031            while (true) {
1032                const bits = liveness.extra[extra_index];
1033                if (op_index < tomb_op_index + 31) {
1034                    break :blk @as(u1, @truncate(bits >> @as(u5, @intCast(op_index - tomb_op_index)))) != 0;
1035                }
1036                if ((bits >> 31) != 0) break :blk false;
1037                extra_index += 1;
1038                tomb_op_index += 31;
1039            }
1040        } else false;
1041        return w.writeInstRef(s, operand, dies);
1042    }
1043
1044    fn writeInstRef(
1045        w: *Writer,
1046        s: *std.Io.Writer,
1047        operand: Air.Inst.Ref,
1048        dies: bool,
1049    ) Error!void {
1050        if (@intFromEnum(operand) < InternPool.static_len) {
1051            return s.print("@{}", .{operand});
1052        } else if (operand.toInterned()) |ip_index| {
1053            const pt = w.pt;
1054            const ty = Type.fromInterned(pt.zcu.intern_pool.indexToKey(ip_index).typeOf());
1055            try s.print("<{f}, {f}>", .{
1056                ty.fmt(pt),
1057                Value.fromInterned(ip_index).fmtValue(pt),
1058            });
1059        } else {
1060            return w.writeInstIndex(s, operand.toIndex().?, dies);
1061        }
1062    }
1063
1064    fn writeInstIndex(
1065        w: *Writer,
1066        s: *std.Io.Writer,
1067        inst: Air.Inst.Index,
1068        dies: bool,
1069    ) Error!void {
1070        _ = w;
1071        try s.print("{f}", .{inst});
1072        if (dies) try s.writeByte('!');
1073    }
1074
1075    fn typeOfIndex(w: *Writer, inst: Air.Inst.Index) Type {
1076        const zcu = w.pt.zcu;
1077        return w.air.typeOfIndex(inst, &zcu.intern_pool);
1078    }
1079};