[Flint] adding foundations for Flint's IR
This commit is contained in:
@@ -0,0 +1,56 @@
|
|||||||
|
pub const Generation = enum {
|
||||||
|
gen9,
|
||||||
|
gen10,
|
||||||
|
gen11,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Platform = enum {
|
||||||
|
skylake,
|
||||||
|
broxton,
|
||||||
|
kabylake,
|
||||||
|
gemini_lake,
|
||||||
|
coffee_lake,
|
||||||
|
whiskey_lake,
|
||||||
|
comet_lake,
|
||||||
|
ice_lake,
|
||||||
|
elkhart_lake,
|
||||||
|
jasper_lake,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const DeviceInfo = struct {
|
||||||
|
generation: Generation,
|
||||||
|
platform: Platform,
|
||||||
|
pci_device_id: u16,
|
||||||
|
|
||||||
|
grf_count: u16,
|
||||||
|
grf_size_bytes: u16 = 32,
|
||||||
|
|
||||||
|
supports_int64: bool = false,
|
||||||
|
supports_float64: bool = false,
|
||||||
|
supports_half_float: bool = false,
|
||||||
|
supports_simd16: bool = false,
|
||||||
|
supports_simd32: bool = false,
|
||||||
|
|
||||||
|
pub fn supportsDispatch(self: DeviceInfo, width: DispatchWidth) bool {
|
||||||
|
return switch (width) {
|
||||||
|
.simd8 => true,
|
||||||
|
.simd16 => self.supports_simd16,
|
||||||
|
.simd32 => self.supports_simd32,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const DispatchWidth = enum(u8) {
|
||||||
|
simd8 = 8,
|
||||||
|
simd16 = 16,
|
||||||
|
simd32 = 32,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const ExecutionSize = enum(u8) {
|
||||||
|
simd1 = 1,
|
||||||
|
simd2 = 2,
|
||||||
|
simd4 = 4,
|
||||||
|
simd8 = 8,
|
||||||
|
simd16 = 16,
|
||||||
|
simd32 = 32,
|
||||||
|
};
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
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);
|
||||||
|
pub const VirtualFlagId = shared_ids.Id(VirtualFlagTag);
|
||||||
|
|
||||||
|
pub const Id = shared_ids.Id;
|
||||||
|
pub const Store = shared_ids.Store;
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const device = @import("device.zig");
|
||||||
|
const ids = @import("id.zig");
|
||||||
|
const operand = @import("operand.zig");
|
||||||
|
|
||||||
|
pub const Builtin = enum {
|
||||||
|
position,
|
||||||
|
vertex_index,
|
||||||
|
instance_index,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const InterfaceSemantic = union(enum) {
|
||||||
|
location: struct {
|
||||||
|
location: u32,
|
||||||
|
component: u8 = 0,
|
||||||
|
},
|
||||||
|
builtin: struct {
|
||||||
|
builtin: Builtin,
|
||||||
|
component: u8 = 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const LoadInput = struct {
|
||||||
|
destination: operand.Destination,
|
||||||
|
semantic: InterfaceSemantic,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const StoreOutput = struct {
|
||||||
|
semantic: InterfaceSemantic,
|
||||||
|
source: operand.Source,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Move = struct {
|
||||||
|
destination: operand.Destination,
|
||||||
|
source: operand.Source,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const BinaryOpcode = enum {
|
||||||
|
add,
|
||||||
|
multiply,
|
||||||
|
bitwise_and,
|
||||||
|
bitwise_or,
|
||||||
|
bitwise_xor,
|
||||||
|
shift_left,
|
||||||
|
shift_right,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Binary = struct {
|
||||||
|
opcode: BinaryOpcode,
|
||||||
|
destination: operand.Destination,
|
||||||
|
lhs: operand.Source,
|
||||||
|
rhs: operand.Source,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const CompareOpcode = enum {
|
||||||
|
equal,
|
||||||
|
not_equal,
|
||||||
|
less_than,
|
||||||
|
less_or_equal,
|
||||||
|
greater_than,
|
||||||
|
greater_or_equal,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Compare = struct {
|
||||||
|
opcode: CompareOpcode,
|
||||||
|
destination: operand.FlagRef,
|
||||||
|
lhs: operand.Source,
|
||||||
|
rhs: operand.Source,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const ChannelMask = packed struct(u4) {
|
||||||
|
x: bool = true,
|
||||||
|
y: bool = true,
|
||||||
|
z: bool = true,
|
||||||
|
w: bool = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const UrbWrite = struct {
|
||||||
|
offset: u16,
|
||||||
|
channels: ChannelMask = .{},
|
||||||
|
end_of_thread: bool = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Message = union(enum) {
|
||||||
|
urb_write: UrbWrite,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Send = struct {
|
||||||
|
message: Message,
|
||||||
|
payload: operand.RegisterSpan,
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Instruction = struct {
|
||||||
|
parent_block: ids.BlockId,
|
||||||
|
execution_size: device.ExecutionSize,
|
||||||
|
predicate: ?operand.Predicate = null,
|
||||||
|
operation: Operation,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Terminator = union(enum) {
|
||||||
|
jump: ids.BlockId,
|
||||||
|
conditional_branch: struct {
|
||||||
|
predicate: operand.Predicate,
|
||||||
|
true_block: ids.BlockId,
|
||||||
|
false_block: ids.BlockId,
|
||||||
|
},
|
||||||
|
return_void,
|
||||||
|
return_value: operand.Source,
|
||||||
|
end_thread,
|
||||||
|
@"unreachable",
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const StructuredControl = union(enum) {
|
||||||
|
none,
|
||||||
|
selection: struct {
|
||||||
|
merge_block: ids.BlockId,
|
||||||
|
},
|
||||||
|
loop: struct {
|
||||||
|
merge_block: ids.BlockId,
|
||||||
|
continue_block: ids.BlockId,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Block = struct {
|
||||||
|
parent_function: ids.FunctionId,
|
||||||
|
instructions: std.ArrayList(ids.InstructionId) = .empty,
|
||||||
|
terminator: ?Terminator = null,
|
||||||
|
structured_control: StructuredControl = .none,
|
||||||
|
name: ?[]const u8 = null,
|
||||||
|
};
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
const device = @import("device.zig");
|
||||||
|
const ids = @import("id.zig");
|
||||||
|
|
||||||
|
pub const DataType = enum {
|
||||||
|
u8,
|
||||||
|
i8,
|
||||||
|
u16,
|
||||||
|
i16,
|
||||||
|
f16,
|
||||||
|
u32,
|
||||||
|
i32,
|
||||||
|
f32,
|
||||||
|
u64,
|
||||||
|
i64,
|
||||||
|
f64,
|
||||||
|
|
||||||
|
pub fn sizeBytes(self: DataType) u8 {
|
||||||
|
return switch (self) {
|
||||||
|
.u8, .i8 => 1,
|
||||||
|
.u16, .i16, .f16 => 2,
|
||||||
|
.u32, .i32, .f32 => 4,
|
||||||
|
.u64, .i64, .f64 => 8,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn isInitialTargetType(self: DataType) bool {
|
||||||
|
return switch (self) {
|
||||||
|
.u32, .i32, .f32 => true,
|
||||||
|
else => false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const RegisterClass = enum {
|
||||||
|
uniform,
|
||||||
|
varying,
|
||||||
|
payload,
|
||||||
|
response,
|
||||||
|
temporary,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const VirtualRegister = struct {
|
||||||
|
size_bytes: u32,
|
||||||
|
alignment_bytes: u16,
|
||||||
|
element_type: DataType,
|
||||||
|
lane_count: u8,
|
||||||
|
class: RegisterClass,
|
||||||
|
spillable: bool = true,
|
||||||
|
name: ?[]const u8 = null,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const VirtualFlag = struct {
|
||||||
|
name: ?[]const u8 = null,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const PhysicalGrf = struct {
|
||||||
|
number: u16,
|
||||||
|
byte_offset: u8 = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const PhysicalFlag = struct {
|
||||||
|
register: u8 = 0,
|
||||||
|
subregister: u8 = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const ArchitectureRegister = union(enum) {
|
||||||
|
flag: u8,
|
||||||
|
address: u8,
|
||||||
|
accumulator: u8,
|
||||||
|
notification: u8,
|
||||||
|
instruction_pointer,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Immediate = union(enum) {
|
||||||
|
u32: u32,
|
||||||
|
i32: i32,
|
||||||
|
f32: f32,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const RegisterRef = union(enum) {
|
||||||
|
virtual: ids.VirtualRegisterId,
|
||||||
|
physical_grf: PhysicalGrf,
|
||||||
|
architecture: ArchitectureRegister,
|
||||||
|
immediate: Immediate,
|
||||||
|
null,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const FlagRef = union(enum) {
|
||||||
|
virtual: ids.VirtualFlagId,
|
||||||
|
physical: PhysicalFlag,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Predicate = struct {
|
||||||
|
flag: FlagRef,
|
||||||
|
inverse: bool = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Region = struct {
|
||||||
|
byte_offset: u16 = 0,
|
||||||
|
vertical_stride: u8,
|
||||||
|
width: u8,
|
||||||
|
horizontal_stride: u8,
|
||||||
|
|
||||||
|
pub fn scalar() Region {
|
||||||
|
return .{
|
||||||
|
.vertical_stride = 0,
|
||||||
|
.width = 1,
|
||||||
|
.horizontal_stride = 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn contiguous(execution_size: device.ExecutionSize) Region {
|
||||||
|
const width: u8 = @intFromEnum(execution_size);
|
||||||
|
return .{
|
||||||
|
.vertical_stride = width,
|
||||||
|
.width = width,
|
||||||
|
.horizontal_stride = 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn broadcast() Region {
|
||||||
|
return scalar();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const DestinationRegion = struct {
|
||||||
|
byte_offset: u16 = 0,
|
||||||
|
horizontal_stride: u8 = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Source = struct {
|
||||||
|
register: RegisterRef,
|
||||||
|
type: DataType,
|
||||||
|
region: Region,
|
||||||
|
negate: bool = false,
|
||||||
|
absolute: bool = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Destination = struct {
|
||||||
|
register: RegisterRef,
|
||||||
|
type: DataType,
|
||||||
|
region: DestinationRegion = .{},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const RegisterSpan = struct {
|
||||||
|
base: RegisterRef,
|
||||||
|
register_count: u8,
|
||||||
|
};
|
||||||
@@ -0,0 +1,364 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const ids = @import("id.zig");
|
||||||
|
const inst_ir = @import("instruction.zig");
|
||||||
|
const operand = @import("operand.zig");
|
||||||
|
const program_ir = @import("program.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", .{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');
|
||||||
|
|
||||||
|
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", .{
|
||||||
|
register.element_type,
|
||||||
|
register.lane_count,
|
||||||
|
register.class,
|
||||||
|
register.size_bytes,
|
||||||
|
register.alignment_bytes,
|
||||||
|
register.spillable,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (program.functions.entries.items, 0..) |entry, function_index| {
|
||||||
|
const function = entry orelse continue;
|
||||||
|
|
||||||
|
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 (function.blocks.items) |block_id| {
|
||||||
|
const block = program.blocks.get(block_id) orelse continue;
|
||||||
|
|
||||||
|
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.*);
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try writer.writeAll("}\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.operation);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn writeOperation(program: *const program_ir.Program, writer: *std.Io.Writer, operation: inst_ir.Operation) !void {
|
||||||
|
switch (operation) {
|
||||||
|
.load_input => |op| {
|
||||||
|
try writeDestination(program, writer, op.destination);
|
||||||
|
try writer.writeAll(" = load_input ");
|
||||||
|
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);
|
||||||
|
},
|
||||||
|
.move => |op| {
|
||||||
|
try writeDestination(program, writer, op.destination);
|
||||||
|
try writer.writeAll(" = move ");
|
||||||
|
try writeSource(program, writer, op.source);
|
||||||
|
},
|
||||||
|
.binary => |op| {
|
||||||
|
try writeDestination(program, writer, op.destination);
|
||||||
|
try writer.print(" = {t} ", .{op.opcode});
|
||||||
|
try writeSource(program, writer, op.lhs);
|
||||||
|
try writer.writeAll(", ");
|
||||||
|
try writeSource(program, writer, op.rhs);
|
||||||
|
},
|
||||||
|
.compare => |op| {
|
||||||
|
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(')');
|
||||||
|
},
|
||||||
|
.send => |op| {
|
||||||
|
if (op.response) |response| {
|
||||||
|
try writeRegisterSpan(program, writer, response);
|
||||||
|
try writer.writeAll(" = ");
|
||||||
|
}
|
||||||
|
try writer.writeAll("send ");
|
||||||
|
try writeMessage(writer, op.message);
|
||||||
|
try writer.writeAll(", payload(");
|
||||||
|
try writeRegisterSpan(program, writer, op.payload);
|
||||||
|
try writer.writeByte(')');
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn writeTerminator(program: *const program_ir.Program, writer: *std.Io.Writer, terminator: inst_ir.Terminator) !void {
|
||||||
|
switch (terminator) {
|
||||||
|
.jump => |target| {
|
||||||
|
try writer.writeAll("jump ");
|
||||||
|
try writeBlockRef(program, writer, target);
|
||||||
|
},
|
||||||
|
.conditional_branch => |branch| {
|
||||||
|
try writer.writeAll("conditional_branch ");
|
||||||
|
try writePredicate(program, writer, branch.predicate);
|
||||||
|
try writer.writeAll(", ");
|
||||||
|
try writeBlockRef(program, writer, branch.true_block);
|
||||||
|
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 {
|
||||||
|
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 writer.print(":{t}", .{source.type});
|
||||||
|
|
||||||
|
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 writeRegisterAtOffset(
|
||||||
|
program: *const program_ir.Program,
|
||||||
|
writer: *std.Io.Writer,
|
||||||
|
register: operand.RegisterRef,
|
||||||
|
byte_offset: u16,
|
||||||
|
) !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});
|
||||||
|
},
|
||||||
|
.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 writeRegisterAtOffset(program, writer, span.base, 0);
|
||||||
|
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 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| {
|
||||||
|
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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,182 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const shared_ir = @import("shader_ir").ir.module;
|
||||||
|
const device = @import("device.zig");
|
||||||
|
const ids = @import("id.zig");
|
||||||
|
const instructions = @import("instruction.zig");
|
||||||
|
const operand = @import("operand.zig");
|
||||||
|
|
||||||
|
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,
|
||||||
|
messages_lowered: bool = false,
|
||||||
|
control_flow_lowered: bool = false,
|
||||||
|
|
||||||
|
regions_legalized: bool = false,
|
||||||
|
types_legalized: bool = false,
|
||||||
|
|
||||||
|
registers_allocated: bool = false,
|
||||||
|
flags_allocated: bool = false,
|
||||||
|
branches_resolved: bool = false,
|
||||||
|
|
||||||
|
_padding: u20 = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const VertexPayload = struct {
|
||||||
|
first_attribute_grf: operand.PhysicalGrf,
|
||||||
|
attribute_grf_count: u16,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const PayloadLayout = struct {
|
||||||
|
header_grf: ?operand.PhysicalGrf = null,
|
||||||
|
vertex: ?VertexPayload = null,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const ProgramData = struct {
|
||||||
|
payload_grf_count: u16 = 0,
|
||||||
|
total_grf_count: u16 = 0,
|
||||||
|
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);
|
||||||
|
pub const VirtualFlagStore = ids.Store(ids.VirtualFlagId, operand.VirtualFlag);
|
||||||
|
|
||||||
|
pub const Program = struct {
|
||||||
|
arena: std.heap.ArenaAllocator,
|
||||||
|
|
||||||
|
stage: Stage,
|
||||||
|
device_info: device.DeviceInfo,
|
||||||
|
dispatch_width: device.DispatchWidth,
|
||||||
|
|
||||||
|
entry_function: ?ids.FunctionId = null,
|
||||||
|
|
||||||
|
functions: FunctionStore = .{},
|
||||||
|
blocks: BlockStore = .{},
|
||||||
|
instructions: InstructionStore = .{},
|
||||||
|
virtual_registers: VirtualRegisterStore = .{},
|
||||||
|
virtual_flags: VirtualFlagStore = .{},
|
||||||
|
|
||||||
|
payload: PayloadLayout = .{},
|
||||||
|
program_data: ProgramData = .{},
|
||||||
|
properties: Properties = .{},
|
||||||
|
|
||||||
|
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,
|
||||||
|
.device_info = device_info,
|
||||||
|
.dispatch_width = dispatch_width,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Program) void {
|
||||||
|
self.arena.deinit();
|
||||||
|
self.* = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn allocator(self: *Program) std.mem.Allocator {
|
||||||
|
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|
|
||||||
|
owned.name = try self.allocator().dupe(u8, name);
|
||||||
|
return self.virtual_registers.add(self.allocator(), owned);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn addVirtualFlag(self: *Program, flag: operand.VirtualFlag) !ids.VirtualFlagId {
|
||||||
|
var owned = flag;
|
||||||
|
if (flag.name) |name|
|
||||||
|
owned.name = try self.allocator().dupe(u8, name);
|
||||||
|
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;
|
||||||
|
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;
|
||||||
|
return 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,
|
||||||
|
});
|
||||||
|
try block.instructions.append(self.allocator(), instruction_id);
|
||||||
|
return instruction_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setTerminator(self: *Program, block_id: ids.BlockId, terminator: instructions.Terminator) !void {
|
||||||
|
const block = self.blocks.getMut(block_id) orelse return error.InvalidBlock;
|
||||||
|
if (block.terminator != null)
|
||||||
|
return error.TerminatorAlreadySet;
|
||||||
|
block.terminator = terminator;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,266 @@
|
|||||||
|
//! 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.?);
|
||||||
|
}
|
||||||
@@ -0,0 +1,282 @@
|
|||||||
|
const ids = @import("id.zig");
|
||||||
|
const instruction = @import("instruction.zig");
|
||||||
|
const operand = @import("operand.zig");
|
||||||
|
const program_ir = @import("program.zig");
|
||||||
|
|
||||||
|
pub const Error = error{
|
||||||
|
UnsupportedGeneration,
|
||||||
|
UnsupportedStage,
|
||||||
|
UnsupportedDispatchWidth,
|
||||||
|
UnsupportedExecutionSize,
|
||||||
|
UnsupportedDataType,
|
||||||
|
MissingEntryFunction,
|
||||||
|
InvalidFunction,
|
||||||
|
MissingEntryBlock,
|
||||||
|
InvalidBlock,
|
||||||
|
WrongParentFunction,
|
||||||
|
CrossFunctionBranch,
|
||||||
|
MissingTerminator,
|
||||||
|
InvalidInstruction,
|
||||||
|
InvalidVirtualRegister,
|
||||||
|
InvalidVirtualFlag,
|
||||||
|
InvalidPhysicalRegister,
|
||||||
|
InvalidPhysicalFlag,
|
||||||
|
InvalidRegisterSize,
|
||||||
|
InvalidRegisterAlignment,
|
||||||
|
InvalidLaneCount,
|
||||||
|
InvalidRegion,
|
||||||
|
InvalidDestination,
|
||||||
|
InvalidImmediateType,
|
||||||
|
InvalidRegisterSpan,
|
||||||
|
WrongArgumentCount,
|
||||||
|
WrongArgumentType,
|
||||||
|
WrongReturnType,
|
||||||
|
InvalidEntryTerminator,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn validate(program: *const program_ir.Program) Error!void {
|
||||||
|
if (program.device_info.generation != .gen9)
|
||||||
|
return error.UnsupportedGeneration;
|
||||||
|
if (program.stage != .vertex)
|
||||||
|
return error.UnsupportedStage;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (program.virtual_registers.entries.items) |entry| {
|
||||||
|
const register = entry orelse continue;
|
||||||
|
if (register.size_bytes == 0)
|
||||||
|
return error.InvalidRegisterSize;
|
||||||
|
if (register.alignment_bytes == 0 or
|
||||||
|
(register.alignment_bytes & (register.alignment_bytes - 1)) != 0)
|
||||||
|
return error.InvalidRegisterAlignment;
|
||||||
|
if (register.lane_count == 0)
|
||||||
|
return error.InvalidLaneCount;
|
||||||
|
try validateType(register.element_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
const block_id = ids.BlockId.fromIndex(block_index);
|
||||||
|
for (block.instructions.items) |instruction_id| {
|
||||||
|
const inst = program.instructions.get(instruction_id) orelse return error.InvalidInstruction;
|
||||||
|
if (inst.parent_block != block_id)
|
||||||
|
return error.InvalidInstruction;
|
||||||
|
try validateInstruction(program, inst.*);
|
||||||
|
}
|
||||||
|
|
||||||
|
try validateStructuredControl(program, block.parent_function, block.structured_control);
|
||||||
|
try validateTerminator(program, block.parent_function, block.terminator.?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validateInstruction(program: *const program_ir.Program, inst: instruction.Instruction) Error!void {
|
||||||
|
switch (inst.execution_size) {
|
||||||
|
.simd1, .simd8 => {},
|
||||||
|
else => return error.UnsupportedExecutionSize,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inst.predicate) |predicate|
|
||||||
|
try validateFlag(program, predicate.flag);
|
||||||
|
|
||||||
|
switch (inst.operation) {
|
||||||
|
.load_input => |op| try validateDestination(program, op.destination),
|
||||||
|
.store_output => |op| try validateSource(program, op.source),
|
||||||
|
.move => |op| {
|
||||||
|
try validateDestination(program, op.destination);
|
||||||
|
try validateSource(program, op.source);
|
||||||
|
},
|
||||||
|
.binary => |op| {
|
||||||
|
try validateDestination(program, op.destination);
|
||||||
|
try validateSource(program, op.lhs);
|
||||||
|
try validateSource(program, op.rhs);
|
||||||
|
},
|
||||||
|
.compare => |op| {
|
||||||
|
try validateFlag(program, op.destination);
|
||||||
|
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|
|
||||||
|
try validateSpan(program, response);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validateSource(program: *const program_ir.Program, source: operand.Source) Error!void {
|
||||||
|
try validateType(source.type);
|
||||||
|
if (source.region.width == 0)
|
||||||
|
return error.InvalidRegion;
|
||||||
|
try validateRegisterRef(program, source.register);
|
||||||
|
|
||||||
|
if (source.register == .immediate) {
|
||||||
|
const matches = switch (source.register.immediate) {
|
||||||
|
.u32 => source.type == .u32,
|
||||||
|
.i32 => source.type == .i32,
|
||||||
|
.f32 => source.type == .f32,
|
||||||
|
};
|
||||||
|
if (!matches)
|
||||||
|
return error.InvalidImmediateType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validateDestination(program: *const program_ir.Program, destination: operand.Destination) Error!void {
|
||||||
|
try validateType(destination.type);
|
||||||
|
if (destination.region.horizontal_stride == 0)
|
||||||
|
return error.InvalidRegion;
|
||||||
|
switch (destination.register) {
|
||||||
|
.immediate, .null => return error.InvalidDestination,
|
||||||
|
else => try validateRegisterRef(program, destination.register),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validateRegisterRef(program: *const program_ir.Program, register: operand.RegisterRef) Error!void {
|
||||||
|
switch (register) {
|
||||||
|
.virtual => |id| if (!program.virtual_registers.isLive(id))
|
||||||
|
return error.InvalidVirtualRegister,
|
||||||
|
.physical_grf => |physical| {
|
||||||
|
if (physical.number >= program.device_info.grf_count or
|
||||||
|
physical.byte_offset >= program.device_info.grf_size_bytes)
|
||||||
|
return error.InvalidPhysicalRegister;
|
||||||
|
},
|
||||||
|
.architecture, .immediate, .null => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validateFlag(program: *const program_ir.Program, flag: operand.FlagRef) Error!void {
|
||||||
|
switch (flag) {
|
||||||
|
.virtual => |id| if (!program.virtual_flags.isLive(id))
|
||||||
|
return error.InvalidVirtualFlag,
|
||||||
|
.physical => |physical| if (physical.register != 0 or physical.subregister > 1)
|
||||||
|
return error.InvalidPhysicalFlag,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validateSpan(program: *const program_ir.Program, span: operand.RegisterSpan) Error!void {
|
||||||
|
if (span.register_count == 0)
|
||||||
|
return error.InvalidRegisterSpan;
|
||||||
|
switch (span.base) {
|
||||||
|
.virtual, .physical_grf => try validateRegisterRef(program, span.base),
|
||||||
|
else => return error.InvalidRegisterSpan,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
switch (terminator) {
|
||||||
|
.jump => |target| try validateBlockTarget(program, function_id, 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);
|
||||||
|
},
|
||||||
|
.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" => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validateStructuredControl(
|
||||||
|
program: *const program_ir.Program,
|
||||||
|
function_id: ids.FunctionId,
|
||||||
|
control: instruction.StructuredControl,
|
||||||
|
) Error!void {
|
||||||
|
switch (control) {
|
||||||
|
.none => {},
|
||||||
|
.selection => |selection| try validateBlockTarget(program, function_id, selection.merge_block),
|
||||||
|
.loop => |loop| {
|
||||||
|
try validateBlockTarget(program, function_id, loop.merge_block);
|
||||||
|
try validateBlockTarget(program, function_id, 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;
|
||||||
|
}
|
||||||
@@ -32,6 +32,7 @@ pub const FlintQueryPool = @import("FlintQueryPool.zig");
|
|||||||
pub const FlintRenderPass = @import("FlintRenderPass.zig");
|
pub const FlintRenderPass = @import("FlintRenderPass.zig");
|
||||||
pub const FlintSampler = @import("FlintSampler.zig");
|
pub const FlintSampler = @import("FlintSampler.zig");
|
||||||
pub const FlintShaderModule = @import("FlintShaderModule.zig");
|
pub const FlintShaderModule = @import("FlintShaderModule.zig");
|
||||||
|
pub const compiler = @import("compiler/root.zig");
|
||||||
|
|
||||||
pub const Instance = FlintInstance;
|
pub const Instance = FlintInstance;
|
||||||
|
|
||||||
@@ -89,6 +90,7 @@ test {
|
|||||||
std.testing.refAllDecls(FlintRenderPass);
|
std.testing.refAllDecls(FlintRenderPass);
|
||||||
std.testing.refAllDecls(FlintSampler);
|
std.testing.refAllDecls(FlintSampler);
|
||||||
std.testing.refAllDecls(FlintShaderModule);
|
std.testing.refAllDecls(FlintShaderModule);
|
||||||
|
std.testing.refAllDecls(compiler);
|
||||||
std.testing.refAllDecls(kmd);
|
std.testing.refAllDecls(kmd);
|
||||||
std.testing.refAllDecls(base);
|
std.testing.refAllDecls(base);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user