const std = @import("std"); const device = @import("../device.zig"); const ids = @import("id.zig"); const inst_ir = @import("instruction.zig"); const operand = @import("operand.zig"); const program_ir = @import("program.zig"); const pseudo = @import("pseudo.zig"); const indent = " "; pub fn write(program: *const program_ir.Program, writer: *std.Io.Writer) std.Io.Writer.Error!void { try writer.writeAll("; Flint program:\n"); try writer.print("; .stage: {t}\n", .{program.stage}); try writer.print("; .generation: {t}\n", .{program.device_info.generation}); try writer.print("; .platform: {t}\n", .{program.device_info.platform}); try writer.print("; .dispatch_width: {t}\n\n", .{program.dispatch_width}); for (program.virtual_registers.entries.items, 0..) |entry, index| { const register = entry orelse continue; try writeVirtualRegisterRef(program, writer, ids.VirtualRegisterId.fromIndex(index)); try writer.print(": vgrf {t}[{d}], class({t}), size({d}), alignment({d}){s}\n", .{ register.element_type, register.lane_count, register.class, register.size_bytes, register.alignment_bytes, if (register.spillable) ", spillable" else "", }); } for (program.virtual_flags.entries.items, 0..) |entry, index| { _ = entry orelse continue; try writeVirtualFlagRef(program, writer, ids.VirtualFlagId.fromIndex(index)); try writer.writeAll(": vflag\n"); } try writer.writeByte('\n'); for (program.blocks.entries.items, 0..) |entry, block_index| { const block = entry orelse continue; const block_id = ids.BlockId.fromIndex(block_index); try writeBlockRef(program, writer, block_id); if (block.parameters.items.len != 0) { try writer.writeByte('('); for (block.parameters.items, 0..) |parameter, index| { if (index != 0) try writer.writeAll(", "); try writeBlockParameter(program, writer, parameter); } try writer.writeByte(')'); } try writer.writeAll(":\n"); switch (block.structured_control) { .none => {}, .selection => |selection| { try writer.writeAll(indent ++ "structured_selection "); try writeBlockRef(program, writer, selection.merge_block); try writer.writeByte('\n'); }, .loop => |loop| { try writer.writeAll(indent ++ "structured_loop merge("); try writeBlockRef(program, writer, loop.merge_block); try writer.writeAll("), continue("); try writeBlockRef(program, writer, loop.continue_block); try writer.writeAll(")\n"); }, } for (block.instructions.items) |instruction_id| { const instruction = program.instructions.get(instruction_id) orelse continue; try writer.writeAll(indent); try writeInstruction(program, writer, instruction.*); try writer.writeByte('\n'); } if (block.terminator) |terminator| { try writer.writeAll(indent); try writeTerminator(program, writer, terminator); try writer.writeAll("\n\n"); } else { try writer.writeAll(indent ++ "\n\n"); } } } pub fn allocPrint(allocator: std.mem.Allocator, program: *const program_ir.Program) ![]u8 { var output: std.Io.Writer.Allocating = .init(allocator); defer output.deinit(); try write(program, &output.writer); return output.toOwnedSlice(); } fn writeInstruction(program: *const program_ir.Program, writer: *std.Io.Writer, instruction: inst_ir.Instruction) !void { try writer.print("[simd{d}] ", .{@intFromEnum(instruction.execution_size)}); if (instruction.predicate) |predicate| { try writePredicate(program, writer, predicate); try writer.writeByte(' '); } try writeOperation(program, writer, instruction.execution_size, instruction.operation); } fn writeOperation(program: *const program_ir.Program, writer: *std.Io.Writer, execution_size: device.ExecutionSize, operation: inst_ir.Operation) !void { switch (operation) { .load_input => |op| { try writer.writeAll("load_input "); try writeDestination(program, writer, execution_size, op.destination); try writer.writeAll(", "); try writeInterfaceSemantic(writer, op.semantic); }, .store_output => |op| { try writer.writeAll("store_output "); try writeInterfaceSemantic(writer, op.semantic); try writer.writeAll(", "); try writeSource(program, writer, execution_size, op.source); }, .move => |op| { try writer.writeAll("mov "); try writeDestination(program, writer, execution_size, op.destination); try writer.writeAll(", "); try writeSource(program, writer, execution_size, op.source); }, .binary => |op| { try writer.print("{t} ", .{op.opcode}); try writeDestination(program, writer, execution_size, op.destination); try writer.writeAll(", "); try writeSource(program, writer, execution_size, op.lhs); try writer.writeAll(", "); try writeSource(program, writer, execution_size, op.rhs); }, .compare => |op| { try writer.print("cmp_{t} ", .{op.opcode}); try writeFlagRef(program, writer, op.destination); try writer.writeAll(", "); try writeSource(program, writer, execution_size, op.lhs); try writer.writeAll(", "); try writeSource(program, writer, execution_size, op.rhs); }, .parallel_copy => |op| try writeParallelCopy(program, writer, execution_size, op), .send => |op| { try writer.writeAll("send "); if (op.response) |response| { try writeRegisterSpan(program, writer, response); try writer.writeAll(", "); } try writeMessage(writer, op.message); try writer.writeAll(", payload("); try writeRegisterSpan(program, writer, op.payload); try writer.writeByte(')'); }, } } fn writeParallelCopy(program: *const program_ir.Program, writer: *std.Io.Writer, execution_size: device.ExecutionSize, copy: pseudo.ParallelCopy) !void { try writer.writeAll("parallel_copy ["); var needs_separator = false; for (copy.register_copies) |item| { if (needs_separator) try writer.writeAll(", "); try writeDestination(program, writer, execution_size, item.destination); try writer.writeAll(" <- "); try writeSource(program, writer, execution_size, item.source); needs_separator = true; } for (copy.flag_copies) |item| { if (needs_separator) try writer.writeAll(", "); try writeVirtualFlagRef(program, writer, item.destination); try writer.writeAll(" <- "); switch (item.source) { .constant => |value| try writer.writeAll(if (value) "true" else "false"), .dynamic => |predicate| try writePredicate(program, writer, predicate), } needs_separator = true; } try writer.writeByte(']'); } fn writeTerminator(program: *const program_ir.Program, writer: *std.Io.Writer, terminator: inst_ir.Terminator) !void { switch (terminator) { .jump => |edge| { try writer.writeAll("jump "); try writeEdge(program, writer, edge); }, .conditional_branch => |branch| { try writer.writeAll("conditional_branch "); try writePredicate(program, writer, branch.predicate); try writer.writeAll(", "); try writeEdge(program, writer, branch.true_edge); try writer.writeAll(", "); try writeEdge(program, writer, branch.false_edge); }, .end_thread => try writer.writeAll("end_thread"), .@"unreachable" => try writer.writeAll("unreachable"), } } fn writeBlockParameter(program: *const program_ir.Program, writer: *std.Io.Writer, parameter: pseudo.BlockParameter) !void { switch (parameter) { .register => |register_id| try writeVirtualRegisterRef(program, writer, register_id), .flag => |flag_id| try writeVirtualFlagRef(program, writer, flag_id), } } fn writeEdge(program: *const program_ir.Program, writer: *std.Io.Writer, edge: inst_ir.Edge) !void { try writeBlockRef(program, writer, edge.target); if (edge.arguments.len == 0) return; const execution_size: device.ExecutionSize = @enumFromInt(@intFromEnum(program.dispatch_width)); try writer.writeByte('('); for (edge.arguments, 0..) |argument, index| { if (index != 0) try writer.writeAll(", "); switch (argument) { .source => |source| try writeSource(program, writer, execution_size, source), .predicate => |predicate_value| switch (predicate_value) { .constant => |value| try writer.writeAll(if (value) "true" else "false"), .dynamic => |predicate| try writePredicate(program, writer, predicate), }, } } try writer.writeByte(')'); } fn writeSource(program: *const program_ir.Program, writer: *std.Io.Writer, execution_size: device.ExecutionSize, source: operand.Source) !void { if (source.negate) try writer.writeByte('-'); if (source.absolute) try writer.writeAll("abs("); try writeRegister(program, writer, source.register); try writer.print(":{t}", .{source.type}); if (source.register != .immediate) try writeSourceRegion(writer, execution_size, source.register, source.region); if (source.absolute) try writer.writeByte(')'); } fn writeDestination(program: *const program_ir.Program, writer: *std.Io.Writer, execution_size: device.ExecutionSize, destination: operand.Destination) !void { _ = execution_size; try writeRegister(program, writer, destination.register); try writer.print(":{t}", .{destination.type}); try writeDestinationRegion(writer, destination.register, destination.region); } fn writeSourceRegion(writer: *std.Io.Writer, execution_size: device.ExecutionSize, register: operand.RegisterRef, region: operand.Region) !void { const byte_offset = registerByteOffset(register) + region.byte_offset; const execution_width: u8 = @intFromEnum(execution_size); const is_default = region.vertical_stride == execution_width and region.width == execution_width and region.horizontal_stride == 1; const is_broadcast = region.vertical_stride == 0 and region.width == 1 and region.horizontal_stride == 0; if (byte_offset == 0 and is_default) return; try writer.writeByte('['); if (byte_offset != 0) try writer.print("byte={d}", .{byte_offset}); if (is_broadcast) { if (byte_offset != 0) try writer.writeAll(", "); try writer.writeAll("broadcast"); } else if (!is_default) { if (byte_offset != 0) try writer.writeAll(", "); try writer.print("vstride={d}, width={d}, hstride={d}", .{ region.vertical_stride, region.width, region.horizontal_stride, }); } try writer.writeByte(']'); } fn writeDestinationRegion(writer: *std.Io.Writer, register: operand.RegisterRef, region: operand.DestinationRegion) !void { const byte_offset = registerByteOffset(register) + region.byte_offset; if (byte_offset == 0 and region.horizontal_stride == 1) return; try writer.writeByte('['); if (byte_offset != 0) try writer.print("byte={d}", .{byte_offset}); if (region.horizontal_stride != 1) { if (byte_offset != 0) try writer.writeAll(", "); try writer.print("hstride={d}", .{region.horizontal_stride}); } try writer.writeByte(']'); } fn registerByteOffset(register: operand.RegisterRef) u16 { return switch (register) { .physical_grf => |physical| physical.byte_offset, else => 0, }; } fn writeRegister(program: *const program_ir.Program, writer: *std.Io.Writer, register: operand.RegisterRef) !void { switch (register) { .virtual => |virtual| try writeVirtualRegisterRef(program, writer, virtual), .physical_grf => |physical| try writer.print("r{d}", .{physical.number}), .architecture => |architecture| try writeArchitectureRegister(writer, architecture), .immediate => |immediate| try writeImmediate(writer, immediate), .null => try writer.writeAll("null"), } } fn writeArchitectureRegister(writer: *std.Io.Writer, register: operand.ArchitectureRegister) !void { switch (register) { .flag => |index| try writer.print("f{d}", .{index}), .address => |index| try writer.print("a{d}", .{index}), .accumulator => |index| try writer.print("acc{d}", .{index}), .notification => |index| try writer.print("n{d}", .{index}), .instruction_pointer => try writer.writeAll("ip"), } } fn writeImmediate(writer: *std.Io.Writer, immediate: operand.Immediate) !void { switch (immediate) { .u32 => |value| try writer.print("{d}", .{value}), .i32 => |value| try writer.print("{d}", .{value}), .f32 => |value| try writer.print("{d}", .{value}), } } fn writePredicate(program: *const program_ir.Program, writer: *std.Io.Writer, predicate: operand.Predicate) !void { try writer.writeAll(if (predicate.inverse) "(-" else "(+"); try writeFlagRef(program, writer, predicate.flag); try writer.writeByte(')'); } fn writeFlagRef(program: *const program_ir.Program, writer: *std.Io.Writer, flag: operand.FlagRef) !void { switch (flag) { .virtual => |virtual| try writeVirtualFlagRef(program, writer, virtual), .physical => |physical| try writer.print("f{d}.{d}", .{ physical.register, physical.subregister }), } } fn writeRegisterSpan(program: *const program_ir.Program, writer: *std.Io.Writer, span: operand.RegisterSpan) !void { try writeRegister(program, writer, span.base); const byte_offset = registerByteOffset(span.base); if (byte_offset != 0) try writer.print("[byte={d}]", .{byte_offset}); try writer.print("[{d}]", .{span.register_count}); } fn writeInterfaceSemantic(writer: *std.Io.Writer, semantic: inst_ir.InterfaceSemantic) !void { switch (semantic) { .location => |location| try writer.print("location({d}), component({d})", .{ location.location, location.component }), .builtin => |builtin| try writer.print("builtin({t}), component({d})", .{ builtin.builtin, builtin.component }), } } fn writeMessage(writer: *std.Io.Writer, message: inst_ir.Message) !void { switch (message) { .urb_write => |urb| { try writer.print("urb_write[offset({d}), channels(", .{urb.offset}); try writeChannelMask(writer, urb.channels); try writer.writeByte(')'); if (urb.end_of_thread) try writer.writeAll(", end_of_thread"); try writer.writeByte(']'); }, } } fn writeChannelMask(writer: *std.Io.Writer, mask: inst_ir.ChannelMask) !void { if (mask.x) try writer.writeByte('x'); if (mask.y) try writer.writeByte('y'); if (mask.z) try writer.writeByte('z'); if (mask.w) try writer.writeByte('w'); } fn writeVirtualRegisterRef(program: *const program_ir.Program, writer: *std.Io.Writer, register_id: ids.VirtualRegisterId) !void { const register = program.virtual_registers.get(register_id); try writeNamedRef(writer, if (register) |value| value.name else null, "v", register_id.index(), '%'); } fn writeVirtualFlagRef(program: *const program_ir.Program, writer: *std.Io.Writer, flag_id: ids.VirtualFlagId) !void { const flag = program.virtual_flags.get(flag_id); try writeNamedRef(writer, if (flag) |value| value.name else null, "f", flag_id.index(), '%'); } fn writeBlockRef(program: *const program_ir.Program, writer: *std.Io.Writer, block_id: ids.BlockId) !void { const block = program.blocks.get(block_id); try writeNamedRef(writer, if (block) |value| value.name else null, "b", block_id.index(), '.'); } fn writeNamedRef(writer: *std.Io.Writer, name: ?[]const u8, fallback: []const u8, index: usize, prefix: u8) !void { try writer.writeByte(prefix); if (name) |text| { if (isValidName(text)) { try writer.writeAll(text); return; } } try writer.print("{s}{d}", .{ fallback, index }); } fn isValidName(name: []const u8) bool { if (name.len == 0 or (!std.ascii.isAlphabetic(name[0]) and name[0] != '_')) return false; for (name[1..]) |byte| { if (!std.ascii.isAlphanumeric(byte) and byte != '_') return false; } return true; }