[Flint] adding IR lowering and printer
Mirror Gitea refs to GitHub / mirror (push) Successful in 18s
Test / build_and_test (push) Successful in 6m17s
Build / build (push) Successful in 7m51s

[IR] switching from "passes" to "transformers" for clarity
This commit is contained in:
2026-07-29 15:39:41 +02:00
parent 045497b264
commit 948e8b86a3
27 changed files with 3453 additions and 1816 deletions
+183
View File
@@ -0,0 +1,183 @@
//! Flint-specific shader IR for Intel Gen hardware.
//! This is the mutable, non-SSA layer between the common shader IR and machine code.
pub const device = @import("device.zig");
pub const ir = @import("ir/ir.zig");
pub const lower = @import("lower/lower.zig");
pub const id = ir.id;
pub const instruction = ir.instruction;
pub const operand = ir.operand;
pub const printer = ir.printer;
pub const program = ir.program;
pub const validator = ir.validator;
pub const Program = ir.Program;
pub const Stage = ir.Stage;
const std = @import("std");
test "Flint IR foundation" {
// ; Flint program:
// ; .stage: vertex
// ; .generation: gen9
// ; .platform: skylake
// ; .dispatch_width: simd8
//
// %position: vgrf f32[8] = class(varying), size(32), alignment(32), spillable
// %urb_payload: vgrf u32[16] = class(payload), size(64), alignment(32)
//
// .entry:
// [simd8] load_input %position:f32, location(0), component(0)
// [simd8] multiply %position:f32, %position:f32, 1:f32
// [simd8] mov %position:f32, %position:f32[byte=4, broadcast]
// [simd8] store_output builtin(position), component(0), %position:f32
// [simd8] send urb_write[offset(0), channels(xyzw), end_of_thread], payload(%urb_payload[2])
// end_thread
const device_info: device.DeviceInfo = .{
.generation = .gen9,
.platform = .skylake,
.pci_device_id = 0x1912,
.grf_count = 128,
};
var shader = Program.init(std.testing.allocator, .vertex, device_info, .simd8);
defer shader.deinit();
const position = try shader.addVirtualRegister(.{
.size_bytes = 32,
.alignment_bytes = 32,
.element_type = .f32,
.lane_count = 8,
.class = .varying,
.name = "position",
});
const urb_payload = try shader.addVirtualRegister(.{
.size_bytes = 64,
.alignment_bytes = 32,
.element_type = .u32,
.lane_count = 16,
.class = .payload,
.spillable = false,
.name = "urb_payload",
});
const entry = try shader.addBlock("entry");
try shader.setEntryBlock(entry);
_ = try shader.appendInstruction(entry, .simd8, null, .{
.load_input = .{
.destination = .{
.register = .{ .virtual = position },
.type = .f32,
},
.semantic = .{
.location = .{
.location = 0,
},
},
},
});
_ = try shader.appendInstruction(entry, .simd8, null, .{
.binary = .{
.opcode = .multiply,
.destination = .{
.register = .{ .virtual = position },
.type = .f32,
},
.lhs = .{
.register = .{ .virtual = position },
.type = .f32,
.region = operand.Region.contiguous(.simd8),
},
.rhs = .{
.register = .{
.immediate = .{ .f32 = 1.0 },
},
.type = .f32,
.region = operand.Region.broadcast(),
},
},
});
_ = try shader.appendInstruction(entry, .simd8, null, .{
.move = .{
.destination = .{
.register = .{ .virtual = position },
.type = .f32,
},
.source = .{
.register = .{ .virtual = position },
.type = .f32,
.region = .{
.byte_offset = 4,
.vertical_stride = 0,
.width = 1,
.horizontal_stride = 0,
},
},
},
});
_ = try shader.appendInstruction(entry, .simd8, null, .{
.store_output = .{
.semantic = .{
.builtin = .{ .builtin = .position },
},
.source = .{
.register = .{ .virtual = position },
.type = .f32,
.region = operand.Region.contiguous(.simd8),
},
},
});
_ = try shader.appendInstruction(entry, .simd8, null, .{
.send = .{
.message = .{
.urb_write = .{
.offset = 0,
.end_of_thread = true,
},
},
.payload = .{
.base = .{ .virtual = urb_payload },
.register_count = 2,
},
},
});
try shader.setTerminator(entry, .end_thread);
shader.properties.instructions_selected = true;
try validator.validate(&shader);
try std.testing.expectEqual(entry, shader.entry_block.?);
try std.testing.expect(shader.properties.instructions_selected);
const text = try printer.allocPrint(std.testing.allocator, &shader);
defer std.testing.allocator.free(text);
try std.testing.expect(std.mem.indexOf(u8, text, "Flint program") != null);
try std.testing.expect(std.mem.indexOf(u8, text, "vertex") != null);
try std.testing.expect(std.mem.indexOf(u8, text, "gen9") != null);
try std.testing.expect(std.mem.indexOf(u8, text, "skylake") != null);
try std.testing.expect(std.mem.indexOf(u8, text, "simd8") != null);
try std.testing.expect(std.mem.indexOf(u8, text, "%position: vgrf f32[8]") != null);
try std.testing.expect(std.mem.indexOf(u8, text, "[simd8] multiply %position:f32, %position:f32, 1:f32") != null);
try std.testing.expect(std.mem.indexOf(u8, text, "[simd8] mov %position:f32, %position:f32[byte=4, broadcast]") != null);
try std.testing.expect(std.mem.indexOf(u8, text, "send urb_write[offset(0), channels(xyzw), end_of_thread], payload(%urb_payload[2])") != null);
}
test "ID stability after removal" {
var store: id.Store(id.VirtualFlagId, operand.VirtualFlag) = .{};
defer store.entries.deinit(std.testing.allocator);
const first = try store.add(std.testing.allocator, .{ .name = "first" });
try std.testing.expect(store.remove(first));
const second = try store.add(std.testing.allocator, .{ .name = "second" });
try std.testing.expect(first != second);
try std.testing.expect(store.get(first) == null);
try std.testing.expectEqualStrings("second", store.get(second).?.name.?);
}
test {
_ = lower;
}
@@ -1,12 +1,10 @@
const shared_ids = @import("shader_ir").ir.id;
pub const FunctionTag = opaque {};
pub const BlockTag = opaque {};
pub const InstructionTag = opaque {};
pub const VirtualRegisterTag = opaque {};
pub const VirtualFlagTag = opaque {};
pub const FunctionId = shared_ids.Id(FunctionTag);
pub const BlockId = shared_ids.Id(BlockTag);
pub const InstructionId = shared_ids.Id(InstructionTag);
pub const VirtualRegisterId = shared_ids.Id(VirtualRegisterTag);
@@ -1,5 +1,5 @@
const std = @import("std");
const device = @import("device.zig");
const device = @import("../device.zig");
const ids = @import("id.zig");
const operand = @import("operand.zig");
@@ -91,19 +91,12 @@ pub const Send = struct {
response: ?operand.RegisterSpan = null,
};
pub const Call = struct {
function: ids.FunctionId,
destination: ?operand.Destination = null,
arguments: []const operand.Source,
};
pub const Operation = union(enum) {
load_input: LoadInput,
store_output: StoreOutput,
move: Move,
binary: Binary,
compare: Compare,
call: Call,
send: Send,
};
@@ -121,8 +114,6 @@ pub const Terminator = union(enum) {
true_block: ids.BlockId,
false_block: ids.BlockId,
},
return_void,
return_value: operand.Source,
end_thread,
@"unreachable",
};
@@ -139,7 +130,6 @@ pub const StructuredControl = union(enum) {
};
pub const Block = struct {
parent_function: ids.FunctionId,
instructions: std.ArrayList(ids.InstructionId) = .empty,
terminator: ?Terminator = null,
structured_control: StructuredControl = .none,
+9
View File
@@ -0,0 +1,9 @@
pub const id = @import("id.zig");
pub const instruction = @import("instruction.zig");
pub const operand = @import("operand.zig");
pub const printer = @import("printer.zig");
pub const program = @import("program.zig");
pub const validator = @import("validator.zig");
pub const Program = program.Program;
pub const Stage = program.Stage;
@@ -1,4 +1,4 @@
const device = @import("device.zig");
const device = @import("../device.zig");
const ids = @import("id.zig");
pub const DataType = enum {
@@ -1,4 +1,5 @@
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");
@@ -11,25 +12,18 @@ pub fn write(program: *const program_ir.Program, writer: *std.Io.Writer) std.Io.
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", .{program.dispatch_width});
if (program.entry_function) |entry| {
try writer.writeAll("; .entry: ");
try writeFunctionRef(program, writer, entry);
try writer.writeByte('\n');
}
try writer.writeByte('\n');
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}), spillable({})\n", .{
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,
register.spillable,
if (register.spillable) ", spillable" else "",
});
}
@@ -39,69 +33,45 @@ pub fn write(program: *const program_ir.Program, writer: *std.Io.Writer) std.Io.
try writer.writeAll(": vflag\n");
}
for (program.functions.entries.items, 0..) |entry, function_index| {
const function = entry orelse continue;
try writer.writeByte('\n');
try writer.writeAll("\nfn ");
try writeFunctionRef(program, writer, ids.FunctionId.fromIndex(function_index));
try writer.writeByte('(');
for (function.parameters.items, 0..) |parameter, index| {
if (index != 0)
try writer.writeAll(", ");
try writeVirtualRegisterRef(program, writer, parameter);
try writer.writeAll(": ");
if (program.virtual_registers.get(parameter)) |register|
try writer.print("{t}", .{register.element_type})
else
try writer.print("<invalid-vgrf-{d}>", .{parameter.index()});
}
try writer.writeAll(") -> ");
if (function.return_type) |return_type|
try writer.print("{t}", .{return_type})
else
try writer.writeAll("void");
try writer.writeAll("\n{\n");
for (program.blocks.entries.items, 0..) |entry, block_index| {
const block = entry orelse continue;
const block_id = ids.BlockId.fromIndex(block_index);
for (function.blocks.items) |block_id| {
const block = program.blocks.get(block_id) orelse continue;
try writeBlockRef(program, writer, block_id);
try writer.writeAll(":\n");
try writer.writeAll(indent);
try writeBlockRef(program, writer, block_id);
try writer.writeAll(":\n");
switch (block.structured_control) {
.none => {},
.selection => |selection| {
try writer.writeAll(indent ** 2 ++ "structured_selection ");
try writeBlockRef(program, writer, selection.merge_block);
try writer.writeByte('\n');
},
.loop => |loop| {
try writer.writeAll(indent ** 2 ++ "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 ** 2);
try writeInstruction(program, writer, instruction.*);
switch (block.structured_control) {
.none => {},
.selection => |selection| {
try writer.writeAll(indent ++ "structured_selection ");
try writeBlockRef(program, writer, selection.merge_block);
try writer.writeByte('\n');
}
if (block.terminator) |terminator| {
try writer.writeAll(indent ** 2);
try writeTerminator(program, writer, terminator);
try writer.writeAll("\n\n");
} else {
try writer.writeAll(indent ** 2 ++ "<missing terminator>\n\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");
},
}
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 ++ "<missing terminator>\n\n");
}
}
}
@@ -118,62 +88,51 @@ fn writeInstruction(program: *const program_ir.Program, writer: *std.Io.Writer,
try writePredicate(program, writer, predicate);
try writer.writeByte(' ');
}
try writeOperation(program, writer, instruction.operation);
try writeOperation(program, writer, instruction.execution_size, instruction.operation);
}
fn writeOperation(program: *const program_ir.Program, writer: *std.Io.Writer, operation: inst_ir.Operation) !void {
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 writeDestination(program, writer, op.destination);
try writer.writeAll(" = load_input ");
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, op.source);
try writeSource(program, writer, execution_size, op.source);
},
.move => |op| {
try writeDestination(program, writer, op.destination);
try writer.writeAll(" = move ");
try writeSource(program, writer, op.source);
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 writeDestination(program, writer, op.destination);
try writer.print(" = {t} ", .{op.opcode});
try writeSource(program, writer, op.lhs);
try writer.print("{t} ", .{op.opcode});
try writeDestination(program, writer, execution_size, op.destination);
try writer.writeAll(", ");
try writeSource(program, writer, op.rhs);
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.print(" = cmp_{t} ", .{op.opcode});
try writeSource(program, writer, op.lhs);
try writer.writeAll(", ");
try writeSource(program, writer, op.rhs);
},
.call => |op| {
if (op.destination) |destination| {
try writeDestination(program, writer, destination);
try writer.writeAll(" = ");
}
try writer.writeAll("call ");
try writeFunctionRef(program, writer, op.function);
try writer.writeByte('(');
for (op.arguments, 0..) |argument, index| {
if (index != 0)
try writer.writeAll(", ");
try writeSource(program, writer, argument);
}
try writer.writeByte(')');
try writeSource(program, writer, execution_size, op.lhs);
try writer.writeAll(", ");
try writeSource(program, writer, execution_size, op.rhs);
},
.send => |op| {
try writer.writeAll("send ");
if (op.response) |response| {
try writeRegisterSpan(program, writer, response);
try writer.writeAll(" = ");
try writer.writeAll(", ");
}
try writer.writeAll("send ");
try writeMessage(writer, op.message);
try writer.writeAll(", payload(");
try writeRegisterSpan(program, writer, op.payload);
@@ -196,63 +155,94 @@ fn writeTerminator(program: *const program_ir.Program, writer: *std.Io.Writer, t
try writer.writeAll(", ");
try writeBlockRef(program, writer, branch.false_block);
},
.return_void => try writer.writeAll("return"),
.return_value => |value| {
try writer.writeAll("return ");
try writeSource(program, writer, value);
},
.end_thread => try writer.writeAll("end_thread"),
.@"unreachable" => try writer.writeAll("unreachable"),
}
}
fn writeSource(program: *const program_ir.Program, writer: *std.Io.Writer, source: operand.Source) !void {
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(");
switch (source.register) {
.immediate => |immediate| try writeImmediate(writer, immediate),
else => {
try writeRegisterAtOffset(program, writer, source.register, source.region.byte_offset);
try writer.print("<{d};{d},{d}>", .{
source.region.vertical_stride,
source.region.width,
source.region.horizontal_stride,
});
},
}
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, destination: operand.Destination) !void {
try writeRegisterAtOffset(program, writer, destination.register, destination.region.byte_offset);
try writer.print("<{d}>:{t}", .{ destination.region.horizontal_stride, destination.type });
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 writeRegisterAtOffset(
program: *const program_ir.Program,
writer: *std.Io.Writer,
register: operand.RegisterRef,
byte_offset: u16,
) !void {
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);
try writer.print(".{d}", .{byte_offset});
},
.physical_grf => |physical| try writer.print("r{d}.{d}", .{
physical.number,
@as(u16, physical.byte_offset) + byte_offset,
}),
.architecture => |architecture| {
try writeArchitectureRegister(writer, architecture);
try writer.print(".{d}", .{byte_offset});
},
.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"),
}
@@ -290,7 +280,10 @@ fn writeFlagRef(program: *const program_ir.Program, writer: *std.Io.Writer, flag
}
fn writeRegisterSpan(program: *const program_ir.Program, writer: *std.Io.Writer, span: operand.RegisterSpan) !void {
try writeRegisterAtOffset(program, writer, span.base, 0);
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});
}
@@ -336,11 +329,6 @@ fn writeBlockRef(program: *const program_ir.Program, writer: *std.Io.Writer, blo
try writeNamedRef(writer, if (block) |value| value.name else null, "b", block_id.index(), '.');
}
fn writeFunctionRef(program: *const program_ir.Program, writer: *std.Io.Writer, function_id: ids.FunctionId) !void {
const function = program.functions.get(function_id);
try writeNamedRef(writer, if (function) |value| value.name else null, "fn", function_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| {
@@ -1,6 +1,6 @@
const std = @import("std");
const shared_ir = @import("shader_ir").ir.module;
const device = @import("device.zig");
const device = @import("../device.zig");
const ids = @import("id.zig");
const instructions = @import("instruction.zig");
const operand = @import("operand.zig");
@@ -10,7 +10,6 @@ pub const Stage = shared_ir.Stage;
pub const Properties = packed struct {
instructions_selected: bool = false,
block_parameters_lowered: bool = false,
calls_lowered: bool = false,
stage_io_lowered: bool = false,
resources_lowered: bool = false,
@@ -24,7 +23,7 @@ pub const Properties = packed struct {
flags_allocated: bool = false,
branches_resolved: bool = false,
_padding: u20 = 0,
_padding: u21 = 0,
};
pub const VertexPayload = struct {
@@ -43,15 +42,6 @@ pub const ProgramData = struct {
scratch_size_bytes: u32 = 0,
};
pub const Function = struct {
return_type: ?operand.DataType,
parameters: std.ArrayList(ids.VirtualRegisterId) = .empty,
blocks: std.ArrayList(ids.BlockId) = .empty,
entry_block: ?ids.BlockId = null,
name: ?[]const u8 = null,
};
pub const FunctionStore = ids.Store(ids.FunctionId, Function);
pub const BlockStore = ids.Store(ids.BlockId, instructions.Block);
pub const InstructionStore = ids.Store(ids.InstructionId, instructions.Instruction);
pub const VirtualRegisterStore = ids.Store(ids.VirtualRegisterId, operand.VirtualRegister);
@@ -64,9 +54,8 @@ pub const Program = struct {
device_info: device.DeviceInfo,
dispatch_width: device.DispatchWidth,
entry_function: ?ids.FunctionId = null,
entry_block: ?ids.BlockId = null,
functions: FunctionStore = .{},
blocks: BlockStore = .{},
instructions: InstructionStore = .{},
virtual_registers: VirtualRegisterStore = .{},
@@ -76,12 +65,7 @@ pub const Program = struct {
program_data: ProgramData = .{},
properties: Properties = .{},
pub fn init(
backing_allocator: std.mem.Allocator,
stage: Stage,
device_info: device.DeviceInfo,
dispatch_width: device.DispatchWidth,
) Program {
pub fn init(backing_allocator: std.mem.Allocator, stage: Stage, device_info: device.DeviceInfo, dispatch_width: device.DispatchWidth) Program {
return .{
.arena = std.heap.ArenaAllocator.init(backing_allocator),
.stage = stage,
@@ -99,27 +83,6 @@ pub const Program = struct {
return self.arena.allocator();
}
pub fn addFunction(self: *Program, return_type: ?operand.DataType, name: ?[]const u8) !ids.FunctionId {
const owned_name = if (name) |value| try self.allocator().dupe(u8, value) else null;
return self.functions.add(self.allocator(), .{
.return_type = return_type,
.name = owned_name,
});
}
pub fn setEntryFunction(self: *Program, function_id: ids.FunctionId) !void {
if (!self.functions.isLive(function_id))
return error.InvalidFunction;
self.entry_function = function_id;
}
pub fn addFunctionParameter(self: *Program, function_id: ids.FunctionId, register_id: ids.VirtualRegisterId) !void {
const function = self.functions.getMut(function_id) orelse return error.InvalidFunction;
if (!self.virtual_registers.isLive(register_id))
return error.InvalidVirtualRegister;
try function.parameters.append(self.allocator(), register_id);
}
pub fn addVirtualRegister(self: *Program, register: operand.VirtualRegister) !ids.VirtualRegisterId {
var owned = register;
if (register.name) |name|
@@ -134,40 +97,30 @@ pub const Program = struct {
return self.virtual_flags.add(self.allocator(), owned);
}
pub fn addBlock(self: *Program, function_id: ids.FunctionId, name: ?[]const u8) !ids.BlockId {
const function = self.functions.getMut(function_id) orelse return error.InvalidFunction;
pub fn addBlock(self: *Program, name: ?[]const u8) !ids.BlockId {
const owned_name = if (name) |value| try self.allocator().dupe(u8, value) else null;
const block_id = try self.blocks.add(self.allocator(), .{
.parent_function = function_id,
.name = owned_name,
});
errdefer _ = self.blocks.remove(block_id);
try function.blocks.append(self.allocator(), block_id);
if (function.entry_block == null)
function.entry_block = block_id;
if (self.entry_block == null)
self.entry_block = block_id;
return block_id;
}
pub fn appendInstruction(
self: *Program,
block_id: ids.BlockId,
execution_size: device.ExecutionSize,
predicate: ?operand.Predicate,
operation: instructions.Operation,
) !ids.InstructionId {
pub fn setEntryBlock(self: *Program, block_id: ids.BlockId) !void {
if (!self.blocks.isLive(block_id))
return error.InvalidBlock;
self.entry_block = block_id;
}
pub fn appendInstruction(self: *Program, block_id: ids.BlockId, execution_size: device.ExecutionSize, predicate: ?operand.Predicate, operation: instructions.Operation) !ids.InstructionId {
const block = self.blocks.getMut(block_id) orelse return error.InvalidBlock;
var owned_operation = operation;
switch (owned_operation) {
.call => |*call| call.arguments = try self.allocator().dupe(operand.Source, call.arguments),
else => {},
}
const instruction_id = try self.instructions.add(self.allocator(), .{
.parent_block = block_id,
.execution_size = execution_size,
.predicate = predicate,
.operation = owned_operation,
.operation = operation,
});
try block.instructions.append(self.allocator(), instruction_id);
return instruction_id;
@@ -9,12 +9,8 @@ pub const Error = error{
UnsupportedDispatchWidth,
UnsupportedExecutionSize,
UnsupportedDataType,
MissingEntryFunction,
InvalidFunction,
MissingEntryBlock,
InvalidBlock,
WrongParentFunction,
CrossFunctionBranch,
MissingTerminator,
InvalidInstruction,
InvalidVirtualRegister,
@@ -28,10 +24,6 @@ pub const Error = error{
InvalidDestination,
InvalidImmediateType,
InvalidRegisterSpan,
WrongArgumentCount,
WrongArgumentType,
WrongReturnType,
InvalidEntryTerminator,
};
pub fn validate(program: *const program_ir.Program) Error!void {
@@ -42,35 +34,9 @@ pub fn validate(program: *const program_ir.Program) Error!void {
if (program.dispatch_width != .simd8 or !program.device_info.supportsDispatch(.simd8))
return error.UnsupportedDispatchWidth;
const entry_function_id = program.entry_function orelse return error.MissingEntryFunction;
const entry_function = program.functions.get(entry_function_id) orelse return error.InvalidFunction;
const entry_block = entry_function.entry_block orelse return error.MissingEntryBlock;
const entry_block_data = program.blocks.get(entry_block) orelse return error.InvalidBlock;
if (entry_block_data.parent_function != entry_function_id)
return error.WrongParentFunction;
for (program.functions.entries.items, 0..) |entry, function_index| {
const function = entry orelse continue;
const function_id = ids.FunctionId.fromIndex(function_index);
if (function.return_type) |return_type|
try validateType(return_type);
const function_entry = function.entry_block orelse return error.MissingEntryBlock;
const function_entry_data = program.blocks.get(function_entry) orelse return error.InvalidBlock;
if (function_entry_data.parent_function != function_id)
return error.WrongParentFunction;
for (function.parameters.items) |parameter| {
if (!program.virtual_registers.isLive(parameter))
return error.InvalidVirtualRegister;
}
for (function.blocks.items) |block_id| {
const block = program.blocks.get(block_id) orelse return error.InvalidBlock;
if (block.parent_function != function_id)
return error.WrongParentFunction;
}
}
const entry_block = program.entry_block orelse return error.MissingEntryBlock;
if (!program.blocks.isLive(entry_block))
return error.InvalidBlock;
for (program.virtual_registers.entries.items) |entry| {
const register = entry orelse continue;
@@ -86,8 +52,6 @@ pub fn validate(program: *const program_ir.Program) Error!void {
for (program.blocks.entries.items, 0..) |entry, block_index| {
const block = entry orelse continue;
if (!program.functions.isLive(block.parent_function) or !functionContainsBlock(program, block.parent_function, ids.BlockId.fromIndex(block_index)))
return error.WrongParentFunction;
if (block.terminator == null)
return error.MissingTerminator;
@@ -99,8 +63,8 @@ pub fn validate(program: *const program_ir.Program) Error!void {
try validateInstruction(program, inst.*);
}
try validateStructuredControl(program, block.parent_function, block.structured_control);
try validateTerminator(program, block.parent_function, block.terminator.?);
try validateStructuredControl(program, block.structured_control);
try validateTerminator(program, block.terminator.?);
}
}
@@ -130,7 +94,6 @@ fn validateInstruction(program: *const program_ir.Program, inst: instruction.Ins
try validateSource(program, op.lhs);
try validateSource(program, op.rhs);
},
.call => |op| try validateCall(program, op),
.send => |op| {
try validateSpan(program, op.payload);
if (op.response) |response|
@@ -139,28 +102,6 @@ fn validateInstruction(program: *const program_ir.Program, inst: instruction.Ins
}
}
fn validateCall(program: *const program_ir.Program, call: instruction.Call) Error!void {
const function = program.functions.get(call.function) orelse return error.InvalidFunction;
if (call.arguments.len != function.parameters.items.len)
return error.WrongArgumentCount;
for (call.arguments, function.parameters.items) |argument, parameter_id| {
try validateSource(program, argument);
const parameter = program.virtual_registers.get(parameter_id) orelse return error.InvalidVirtualRegister;
if (argument.type != parameter.element_type)
return error.WrongArgumentType;
}
if (function.return_type) |return_type| {
const destination = call.destination orelse return error.WrongReturnType;
try validateDestination(program, destination);
if (destination.type != return_type)
return error.WrongReturnType;
} else if (call.destination != null) {
return error.WrongReturnType;
}
}
fn validateType(data_type: operand.DataType) Error!void {
if (!data_type.isInitialTargetType())
return error.UnsupportedDataType;
@@ -224,59 +165,30 @@ fn validateSpan(program: *const program_ir.Program, span: operand.RegisterSpan)
}
}
fn validateTerminator(
program: *const program_ir.Program,
function_id: ids.FunctionId,
terminator: instruction.Terminator,
) Error!void {
const function = program.functions.get(function_id) orelse return error.InvalidFunction;
fn validateTerminator(program: *const program_ir.Program, terminator: instruction.Terminator) Error!void {
switch (terminator) {
.jump => |target| try validateBlockTarget(program, function_id, target),
.jump => |target| try validateBlockTarget(program, target),
.conditional_branch => |branch| {
try validateFlag(program, branch.predicate.flag);
try validateBlockTarget(program, function_id, branch.true_block);
try validateBlockTarget(program, function_id, branch.false_block);
try validateBlockTarget(program, branch.true_block);
try validateBlockTarget(program, branch.false_block);
},
.return_void => if (function.return_type != null)
return error.WrongReturnType,
.return_value => |value| {
const return_type = function.return_type orelse return error.WrongReturnType;
try validateSource(program, value);
if (value.type != return_type)
return error.WrongReturnType;
},
.end_thread => if (program.entry_function != function_id)
return error.InvalidEntryTerminator,
.@"unreachable" => {},
.end_thread, .@"unreachable" => {},
}
}
fn validateStructuredControl(
program: *const program_ir.Program,
function_id: ids.FunctionId,
control: instruction.StructuredControl,
) Error!void {
fn validateStructuredControl(program: *const program_ir.Program, control: instruction.StructuredControl) Error!void {
switch (control) {
.none => {},
.selection => |selection| try validateBlockTarget(program, function_id, selection.merge_block),
.selection => |selection| try validateBlockTarget(program, selection.merge_block),
.loop => |loop| {
try validateBlockTarget(program, function_id, loop.merge_block);
try validateBlockTarget(program, function_id, loop.continue_block);
try validateBlockTarget(program, loop.merge_block);
try validateBlockTarget(program, loop.continue_block);
},
}
}
fn validateBlockTarget(program: *const program_ir.Program, function_id: ids.FunctionId, block_id: ids.BlockId) Error!void {
const block = program.blocks.get(block_id) orelse return error.InvalidBlock;
if (block.parent_function != function_id)
return error.CrossFunctionBranch;
}
fn functionContainsBlock(program: *const program_ir.Program, function_id: ids.FunctionId, block_id: ids.BlockId) bool {
const function = program.functions.get(function_id) orelse return false;
for (function.blocks.items) |candidate| {
if (candidate == block_id)
return true;
}
return false;
fn validateBlockTarget(program: *const program_ir.Program, block_id: ids.BlockId) Error!void {
if (!program.blocks.isLive(block_id))
return error.InvalidBlock;
}
-74
View File
@@ -1,74 +0,0 @@
const std = @import("std");
const shader_ir = @import("shader_ir").ir;
const device = @import("device.zig");
const program_ir = @import("program.zig");
pub const Options = struct {
dispatch_width: device.DispatchWidth = .simd8,
};
pub const Error = std.mem.Allocator.Error || error{
MissingEntryPoint,
InvalidEntryPoint,
UnsupportedGeneration,
UnsupportedStage,
UnsupportedDispatchWidth,
UnsupportedType,
UnsupportedOperation,
UnsupportedTerminator,
LoweringNotImplemented,
};
pub const Lowerer = struct {
allocator: std.mem.Allocator,
module: *const shader_ir.module.Module,
device_info: device.DeviceInfo,
options: Options,
pub fn init(
allocator: std.mem.Allocator,
module: *const shader_ir.module.Module,
device_info: device.DeviceInfo,
options: Options,
) Lowerer {
return .{
.allocator = allocator,
.module = module,
.device_info = device_info,
.options = options,
};
}
pub fn lower(self: *Lowerer) Error!program_ir.Program {
var program = program_ir.Program.init(self.allocator, self.module.stage, self.device_info, self.options.dispatch_width);
errdefer program.deinit();
const entry_point = self.source.entryPoint(self.module.stage);
if (entry_point) |ep| {
program.entry_block = try program.addBlock(ep.name);
_ = program.appendInstruction(program.entry_block orelse return error.InvalidBlock, self.options.dispatch_width, null, .{ .name = ep.name });
}
const blocks = self.source.blocks();
for (blocks) |block| {
const block_id = try program.addBlock(block.name);
for (block.instructions) |inst| {
_ = program.appendInstruction(block_id, self.options.dispatch_width, null, .{ .name = inst.name });
}
_ = program.setTerminator(block_id, .{ .name = block.terminator.name });
}
return program;
}
};
/// Convenience entry point for callers that do not need to retain a lowerer.
pub inline fn lower(
allocator: std.mem.Allocator,
module: *const shader_ir.module.Module,
device_info: device.DeviceInfo,
options: Options,
) Error!program_ir.Program {
var lowerer = Lowerer.init(allocator, module, device_info, options);
return lowerer.lower();
}
File diff suppressed because it is too large Load Diff
-266
View File
@@ -1,266 +0,0 @@
//! Backend-specific shader IR for Intel Gen hardware.
//! This is the mutable, non-SSA layer between the shared shader IR and machine encoding.
pub const device = @import("device.zig");
pub const id = @import("id.zig");
pub const instruction = @import("instruction.zig");
pub const lower = @import("lower.zig");
pub const operand = @import("operand.zig");
pub const printer = @import("printer.zig");
pub const program = @import("program.zig");
pub const validator = @import("validator.zig");
pub const Function = program.Function;
pub const FunctionId = id.FunctionId;
pub const Program = program.Program;
pub const Stage = program.Stage;
const std = @import("std");
test "Flint IR foundation" {
// ; Flint program:
// ; .stage: vertex
// ; .generation: gen9
// ; .platform: skylake
// ; .dispatch_width: simd8
// ; .entry: @main
//
// %position: vgrf f32[8] = class(varying), size(32), alignment(32), spillable(true)
// %urb_payload: vgrf u32[16] = class(payload), size(64), alignment(32), spillable(false)
//
// fn @main() -> void
// {
// .entry:
// [simd8] %position.0<1>:f32 = load_input location(0), component(0)
// [simd8] %position.0<1>:f32 = multiply %position.0<8;8,1>:f32, 1:f32
// [simd8] store_output builtin(position), component(0), %position.0<8;8,1>:f32
// [simd8] send urb_write[offset(0), channels(xyzw), end_of_thread], payload(%urb_payload.0[2])
// end_thread
//
// }
const device_info: device.DeviceInfo = .{
.generation = .gen9,
.platform = .skylake,
.pci_device_id = 0x1912,
.grf_count = 128,
};
var shader = Program.init(std.testing.allocator, .vertex, device_info, .simd8);
defer shader.deinit();
const main = try shader.addFunction(null, "main");
try shader.setEntryFunction(main);
const position = try shader.addVirtualRegister(.{
.size_bytes = 32,
.alignment_bytes = 32,
.element_type = .f32,
.lane_count = 8,
.class = .varying,
.name = "position",
});
const urb_payload = try shader.addVirtualRegister(.{
.size_bytes = 64,
.alignment_bytes = 32,
.element_type = .u32,
.lane_count = 16,
.class = .payload,
.spillable = false,
.name = "urb_payload",
});
const entry = try shader.addBlock(main, "entry");
_ = try shader.appendInstruction(entry, .simd8, null, .{
.load_input = .{
.destination = .{
.register = .{ .virtual = position },
.type = .f32,
},
.semantic = .{
.location = .{
.location = 0,
},
},
},
});
_ = try shader.appendInstruction(entry, .simd8, null, .{
.binary = .{
.opcode = .multiply,
.destination = .{
.register = .{ .virtual = position },
.type = .f32,
},
.lhs = .{
.register = .{ .virtual = position },
.type = .f32,
.region = operand.Region.contiguous(.simd8),
},
.rhs = .{
.register = .{
.immediate = .{ .f32 = 1.0 },
},
.type = .f32,
.region = operand.Region.broadcast(),
},
},
});
_ = try shader.appendInstruction(entry, .simd8, null, .{
.store_output = .{
.semantic = .{
.builtin = .{ .builtin = .position },
},
.source = .{
.register = .{ .virtual = position },
.type = .f32,
.region = operand.Region.contiguous(.simd8),
},
},
});
_ = try shader.appendInstruction(entry, .simd8, null, .{
.send = .{
.message = .{
.urb_write = .{
.offset = 0,
.end_of_thread = true,
},
},
.payload = .{
.base = .{ .virtual = urb_payload },
.register_count = 2,
},
},
});
try shader.setTerminator(entry, .end_thread);
shader.properties.instructions_selected = true;
try validator.validate(&shader);
try std.testing.expectEqual(main, shader.entry_function.?);
try std.testing.expectEqual(entry, shader.functions.get(main).?.entry_block.?);
try std.testing.expect(shader.properties.instructions_selected);
const text = try printer.allocPrint(std.testing.allocator, &shader);
defer std.testing.allocator.free(text);
try std.testing.expect(std.mem.indexOf(u8, text, "Flint program") != null);
try std.testing.expect(std.mem.indexOf(u8, text, "vertex") != null);
try std.testing.expect(std.mem.indexOf(u8, text, "gen9") != null);
try std.testing.expect(std.mem.indexOf(u8, text, "skylake") != null);
try std.testing.expect(std.mem.indexOf(u8, text, "simd8") != null);
try std.testing.expect(std.mem.indexOf(u8, text, "@main") != null);
try std.testing.expect(std.mem.indexOf(u8, text, "%position: vgrf f32[8]") != null);
try std.testing.expect(std.mem.indexOf(u8, text, "[simd8] %position.0<1>:f32 = multiply %position.0<8;8,1>:f32, 1:f32") != null);
try std.testing.expect(std.mem.indexOf(u8, text, "send urb_write[offset(0), channels(xyzw), end_of_thread], payload(%urb_payload.0[2])") != null);
}
test "Flint IR function calls" {
// ; Flint program:
// ; .stage: vertex
// ; .generation: gen9
// ; .platform: skylake
// ; .dispatch_width: simd8
// ; .entry: @main
//
// %value: vgrf u32[8] = class(temporary), size(32), alignment(32), spillable(true)
// %result: vgrf u32[8] = class(temporary), size(32), alignment(32), spillable(true)
//
// fn @main() -> void
// {
// .entry:
// [simd8] %result.0<1>:u32 = call @identity(1:u32)
// end_thread
//
// }
//
// fn @identity(%value: u32) -> u32
// {
// .entry:
// return %value.0<8;8,1>:u32
//
// }
const device_info: device.DeviceInfo = .{
.generation = .gen9,
.platform = .skylake,
.pci_device_id = 0x1912,
.grf_count = 128,
};
var shader = Program.init(std.testing.allocator, .vertex, device_info, .simd8);
defer shader.deinit();
const main = try shader.addFunction(null, "main");
const identity = try shader.addFunction(.u32, "identity");
try shader.setEntryFunction(main);
const parameter = try shader.addVirtualRegister(.{
.size_bytes = 32,
.alignment_bytes = 32,
.element_type = .u32,
.lane_count = 8,
.class = .temporary,
.name = "value",
});
const result = try shader.addVirtualRegister(.{
.size_bytes = 32,
.alignment_bytes = 32,
.element_type = .u32,
.lane_count = 8,
.class = .temporary,
.name = "result",
});
try shader.addFunctionParameter(identity, parameter);
const main_entry = try shader.addBlock(main, "entry");
const identity_entry = try shader.addBlock(identity, "entry");
const call_id = try shader.appendInstruction(main_entry, .simd8, null, .{
.call = .{
.function = identity,
.destination = .{
.register = .{ .virtual = result },
.type = .u32,
},
.arguments = &.{
.{
.register = .{ .immediate = .{ .u32 = 1 } },
.type = .u32,
.region = operand.Region.broadcast(),
},
},
},
});
try shader.setTerminator(main_entry, .end_thread);
try shader.setTerminator(identity_entry, .{ .return_value = .{
.register = .{ .virtual = parameter },
.type = .u32,
.region = operand.Region.contiguous(.simd8),
} });
try validator.validate(&shader);
const call = shader.instructions.get(call_id).?.operation.call;
try std.testing.expectEqual(identity, call.function);
try std.testing.expectEqual(@as(usize, 1), call.arguments.len);
const text = try printer.allocPrint(std.testing.allocator, &shader);
defer std.testing.allocator.free(text);
try std.testing.expect(std.mem.indexOf(u8, text, "fn @identity(%value: u32) -> u32") != null);
try std.testing.expect(std.mem.indexOf(u8, text, "[simd8] %result.0<1>:u32 = call @identity(1:u32)") != null);
try std.testing.expect(std.mem.indexOf(u8, text, "return %value.0<8;8,1>:u32") != null);
}
test "ID stability after removal" {
var store: id.Store(id.VirtualFlagId, operand.VirtualFlag) = .{};
defer store.entries.deinit(std.testing.allocator);
const first = try store.add(std.testing.allocator, .{ .name = "first" });
try std.testing.expect(store.remove(first));
const second = try store.add(std.testing.allocator, .{ .name = "second" });
try std.testing.expect(first != second);
try std.testing.expect(store.get(first) == null);
try std.testing.expectEqualStrings("second", store.get(second).?.name.?);
}
+7 -7
View File
@@ -2,15 +2,12 @@ const std = @import("std");
const vk = @import("vulkan");
pub const base = @import("base");
pub const kmd = @import("kmd.zig");
pub const compiler = @import("compiler/compiler.zig");
pub const c = @import("intel_c");
pub const config = base.config;
pub const FlintInstance = @import("FlintInstance.zig");
pub const FlintDevice = @import("FlintDevice.zig");
pub const FlintPhysicalDevice = @import("FlintPhysicalDevice.zig");
pub const FlintQueue = @import("FlintQueue.zig");
pub const kmd = @import("kmd.zig");
pub const FlintBinarySemaphore = @import("FlintBinarySemaphore.zig");
pub const FlintBuffer = @import("FlintBuffer.zig");
pub const FlintBufferView = @import("FlintBufferView.zig");
@@ -19,20 +16,23 @@ pub const FlintCommandPool = @import("FlintCommandPool.zig");
pub const FlintDescriptorPool = @import("FlintDescriptorPool.zig");
pub const FlintDescriptorSet = @import("FlintDescriptorSet.zig");
pub const FlintDescriptorSetLayout = @import("FlintDescriptorSetLayout.zig");
pub const FlintDevice = @import("FlintDevice.zig");
pub const FlintDeviceMemory = @import("FlintDeviceMemory.zig");
pub const FlintEvent = @import("FlintEvent.zig");
pub const FlintFence = @import("FlintFence.zig");
pub const FlintFramebuffer = @import("FlintFramebuffer.zig");
pub const FlintImage = @import("FlintImage.zig");
pub const FlintImageView = @import("FlintImageView.zig");
pub const FlintInstance = @import("FlintInstance.zig");
pub const FlintPhysicalDevice = @import("FlintPhysicalDevice.zig");
pub const FlintPipeline = @import("FlintPipeline.zig");
pub const FlintPipelineCache = @import("FlintPipelineCache.zig");
pub const FlintPipelineLayout = @import("FlintPipelineLayout.zig");
pub const FlintQueryPool = @import("FlintQueryPool.zig");
pub const FlintQueue = @import("FlintQueue.zig");
pub const FlintRenderPass = @import("FlintRenderPass.zig");
pub const FlintSampler = @import("FlintSampler.zig");
pub const FlintShaderModule = @import("FlintShaderModule.zig");
pub const compiler = @import("compiler/root.zig");
pub const Instance = FlintInstance;