1488 lines
63 KiB
Zig
1488 lines
63 KiB
Zig
const std = @import("std");
|
|
const shader_compiler = @import("shader_ir");
|
|
const shader_ir = shader_compiler.ir;
|
|
const device = @import("../device.zig");
|
|
const Builder = @import("../ir/Builder.zig");
|
|
const ids = @import("../ir/id.zig");
|
|
const instruction = @import("../ir/instruction.zig");
|
|
const operand = @import("../ir/operand.zig");
|
|
const printer = @import("../ir/printer.zig");
|
|
const pseudo = @import("../ir/pseudo.zig");
|
|
const program_ir = @import("../ir/program.zig");
|
|
const validator = @import("../ir/validator.zig");
|
|
|
|
pub const block_arguments = @import("block_arguments.zig");
|
|
|
|
pub const Options = struct {
|
|
dispatch_width: device.DispatchWidth = .simd8,
|
|
};
|
|
|
|
pub const Error = std.mem.Allocator.Error || error{
|
|
MissingEntryPoint,
|
|
InvalidEntryPoint,
|
|
InvalidModule,
|
|
InvalidLoweredProgram,
|
|
SanitizationFailed,
|
|
UnsanitizedModule,
|
|
UnsupportedStage,
|
|
MissingWorkgroupSize,
|
|
UnsupportedType,
|
|
UnsupportedOperation,
|
|
UnsupportedTerminator,
|
|
};
|
|
|
|
const PredicateValue = pseudo.PredicateValue;
|
|
|
|
const LoweredType = struct {
|
|
element_type: operand.DataType,
|
|
component_count: usize,
|
|
};
|
|
|
|
const ValueLocation = union(enum) {
|
|
components: []const operand.Source,
|
|
predicate: PredicateValue,
|
|
};
|
|
|
|
const LoweringState = struct {
|
|
lowerer: *Lowerer,
|
|
builder: Builder,
|
|
storage: std.mem.Allocator,
|
|
block_map: []?ids.BlockId,
|
|
value_locations: []?ValueLocation,
|
|
storage_buffer_map: []?ids.StorageBufferId,
|
|
|
|
fn lowerScalarType(self: *const LoweringState, type_id: shader_ir.id.TypeId) Error!operand.DataType {
|
|
const ty = self.lowerer.module.types.get(type_id) orelse return Error.InvalidModule;
|
|
return switch (ty.*) {
|
|
.integer => |integer| if (integer.bits == 32)
|
|
switch (integer.signedness) {
|
|
.unsigned => .u32,
|
|
.signed => .i32,
|
|
}
|
|
else
|
|
Error.UnsupportedType,
|
|
.floating => |floating| if (floating.bits == 32) .f32 else Error.UnsupportedType,
|
|
else => Error.UnsupportedType,
|
|
};
|
|
}
|
|
|
|
fn lowerType(self: *const LoweringState, type_id: shader_ir.id.TypeId) Error!LoweredType {
|
|
const ty = self.lowerer.module.types.get(type_id) orelse return Error.InvalidModule;
|
|
return switch (ty.*) {
|
|
.integer, .floating => .{
|
|
.element_type = try self.lowerScalarType(type_id),
|
|
.component_count = 1,
|
|
},
|
|
.vector => |vector| if (vector.length >= 2 and vector.length <= 4)
|
|
.{
|
|
.element_type = try self.lowerScalarType(vector.element_type),
|
|
.component_count = vector.length,
|
|
}
|
|
else
|
|
Error.UnsupportedType,
|
|
else => Error.UnsupportedType,
|
|
};
|
|
}
|
|
|
|
fn isBoolean(self: *const LoweringState, type_id: shader_ir.id.TypeId) Error!bool {
|
|
const ty = self.lowerer.module.types.get(type_id) orelse return Error.InvalidModule;
|
|
return ty.* == .boolean;
|
|
}
|
|
|
|
fn mappedBlock(self: *const LoweringState, source_id: shader_ir.id.BlockId) Error!ids.BlockId {
|
|
if (source_id.index() >= self.block_map.len)
|
|
return Error.InvalidModule;
|
|
return self.block_map[source_id.index()] orelse Error.InvalidModule;
|
|
}
|
|
|
|
fn storageBuffer(self: *LoweringState, source_id: shader_ir.id.ResourceId) Error!ids.StorageBufferId {
|
|
if (source_id.index() >= self.storage_buffer_map.len)
|
|
return Error.InvalidModule;
|
|
if (self.storage_buffer_map[source_id.index()]) |existing|
|
|
return existing;
|
|
|
|
const resource = self.lowerer.module.resources.get(source_id) orelse return Error.InvalidModule;
|
|
if (resource.kind != .storage_buffer)
|
|
return Error.UnsupportedOperation;
|
|
const buffer_id = self.builder.addStorageBuffer(.{
|
|
.set = resource.set,
|
|
.binding = resource.binding,
|
|
.name = resource.name,
|
|
}) catch |err| return mapProgramError(err);
|
|
self.storage_buffer_map[source_id.index()] = buffer_id;
|
|
return buffer_id;
|
|
}
|
|
|
|
fn putLocation(self: *LoweringState, value_id: shader_ir.id.ValueId, new_location: ValueLocation) Error!void {
|
|
if (value_id.index() >= self.value_locations.len or self.value_locations[value_id.index()] != null)
|
|
return Error.InvalidModule;
|
|
self.value_locations[value_id.index()] = new_location;
|
|
}
|
|
|
|
fn addRegister(self: *LoweringState, data_type: operand.DataType, class: operand.RegisterClass, name: ?[]const u8) Error!ids.VirtualRegisterId {
|
|
return self.builder.addVirtualRegister(.{
|
|
.size_bytes = @as(u32, data_type.sizeBytes()) * @intFromEnum(self.lowerer.options.dispatch_width),
|
|
.alignment_bytes = self.lowerer.device_info.grf_size_bytes,
|
|
.element_type = data_type,
|
|
.lane_count = @intFromEnum(self.lowerer.options.dispatch_width),
|
|
.class = class,
|
|
.name = name,
|
|
}) catch |err| return mapProgramError(err);
|
|
}
|
|
|
|
fn executionSize(self: *const LoweringState) device.ExecutionSize {
|
|
return @enumFromInt(@intFromEnum(self.lowerer.options.dispatch_width));
|
|
}
|
|
|
|
fn registerSource(self: *const LoweringState, register_id: ids.VirtualRegisterId, data_type: operand.DataType) operand.Source {
|
|
return .{
|
|
.register = .{ .virtual = register_id },
|
|
.type = data_type,
|
|
.region = operand.Region.contiguous(self.executionSize()),
|
|
};
|
|
}
|
|
|
|
fn componentName(self: *LoweringState, name: ?[]const u8, component_index: usize, component_count: usize) Error!?[]const u8 {
|
|
if (name == null or component_count == 1)
|
|
return name;
|
|
const suffixes = "xyzw";
|
|
const formatted = try std.fmt.allocPrint(self.storage, "{s}_{c}", .{ name.?, suffixes[component_index] });
|
|
return @as([]const u8, formatted);
|
|
}
|
|
|
|
fn addRegisterLocation(self: *LoweringState, value_id: shader_ir.id.ValueId, class: operand.RegisterClass) Error![]const operand.Source {
|
|
const value = self.lowerer.module.values.get(value_id) orelse return Error.InvalidModule;
|
|
const lowered_type = try self.lowerType(value.type);
|
|
const result = try self.storage.alloc(operand.Source, lowered_type.component_count);
|
|
for (result, 0..) |*component, component_index| {
|
|
const register_id = try self.addRegister(
|
|
lowered_type.element_type,
|
|
class,
|
|
try self.componentName(value.name, component_index, lowered_type.component_count),
|
|
);
|
|
component.* = self.registerSource(register_id, lowered_type.element_type);
|
|
}
|
|
try self.putLocation(value_id, .{ .components = result });
|
|
return result;
|
|
}
|
|
|
|
fn location(self: *LoweringState, value_id: shader_ir.id.ValueId) Error!ValueLocation {
|
|
if (value_id.index() >= self.value_locations.len)
|
|
return Error.InvalidModule;
|
|
|
|
if (self.value_locations[value_id.index()]) |existing|
|
|
return existing;
|
|
|
|
const value = self.lowerer.module.values.get(value_id) orelse return Error.InvalidModule;
|
|
switch (value.definition) {
|
|
.constant => |constant_id| {
|
|
const constant = self.lowerer.module.constants.get(constant_id) orelse return Error.InvalidModule;
|
|
if (constant.type != value.type)
|
|
return Error.InvalidModule;
|
|
|
|
const result: ValueLocation = if (try self.isBoolean(value.type)) switch (constant.value) {
|
|
.boolean => |boolean| .{ .predicate = .{ .constant = boolean } },
|
|
else => return Error.UnsupportedType,
|
|
} else .{
|
|
.components = try self.constantComponents(value.type, constant.value),
|
|
};
|
|
self.value_locations[value_id.index()] = result;
|
|
return result;
|
|
},
|
|
.undef => {
|
|
if (try self.isBoolean(value.type))
|
|
return Error.UnsupportedType;
|
|
_ = try self.addRegisterLocation(value_id, .temporary);
|
|
return self.value_locations[value_id.index()].?;
|
|
},
|
|
else => return Error.InvalidModule,
|
|
}
|
|
}
|
|
|
|
fn components(self: *LoweringState, value_id: shader_ir.id.ValueId) Error![]const operand.Source {
|
|
return switch (try self.location(value_id)) {
|
|
.components => |values| values,
|
|
.predicate => Error.UnsupportedType,
|
|
};
|
|
}
|
|
|
|
fn source(self: *LoweringState, value_id: shader_ir.id.ValueId) Error!operand.Source {
|
|
const values = try self.components(value_id);
|
|
if (values.len != 1)
|
|
return Error.UnsupportedType;
|
|
return values[0];
|
|
}
|
|
|
|
fn predicate(self: *LoweringState, value_id: shader_ir.id.ValueId) Error!PredicateValue {
|
|
return switch (try self.location(value_id)) {
|
|
.components => Error.UnsupportedType,
|
|
.predicate => |value| value,
|
|
};
|
|
}
|
|
|
|
fn destinationFromSource(source_value: operand.Source) Error!operand.Destination {
|
|
if (source_value.negate or source_value.absolute)
|
|
return Error.InvalidLoweredProgram;
|
|
|
|
return switch (source_value.register) {
|
|
.virtual => .{
|
|
.register = source_value.register,
|
|
.type = source_value.type,
|
|
.region = .{ .byte_offset = source_value.region.byte_offset },
|
|
},
|
|
else => Error.InvalidLoweredProgram,
|
|
};
|
|
}
|
|
|
|
fn destination(self: *LoweringState, value_id: shader_ir.id.ValueId) Error!operand.Destination {
|
|
return destinationFromSource(try self.source(value_id));
|
|
}
|
|
|
|
fn constantComponents(self: *LoweringState, type_id: shader_ir.id.TypeId, value: shader_ir.constant.ConstantValue) Error![]const operand.Source {
|
|
const lowered_type = try self.lowerType(type_id);
|
|
const result = try self.storage.alloc(operand.Source, lowered_type.component_count);
|
|
if (lowered_type.component_count == 1) {
|
|
result[0] = try self.constantScalarSource(type_id, value);
|
|
return result;
|
|
}
|
|
|
|
const ty = self.lowerer.module.types.get(type_id) orelse return Error.InvalidModule;
|
|
const vector = switch (ty.*) {
|
|
.vector => |vector| vector,
|
|
else => return Error.InvalidModule,
|
|
};
|
|
switch (value) {
|
|
.composite => |elements| {
|
|
if (elements.len != lowered_type.component_count)
|
|
return Error.InvalidModule;
|
|
for (elements, result) |constant_id, *component| {
|
|
const element = self.lowerer.module.constants.get(constant_id) orelse return Error.InvalidModule;
|
|
if (element.type != vector.element_type)
|
|
return Error.InvalidModule;
|
|
component.* = try self.constantScalarSource(element.type, element.value);
|
|
}
|
|
},
|
|
.null => {
|
|
const zero: shader_ir.constant.ConstantValue = switch (lowered_type.element_type) {
|
|
.u32, .i32 => .{ .integer_bits = 0 },
|
|
.f32 => .{ .float_bits = 0 },
|
|
else => unreachable,
|
|
};
|
|
for (result) |*component|
|
|
component.* = try self.constantScalarSource(vector.element_type, zero);
|
|
},
|
|
else => return Error.UnsupportedType,
|
|
}
|
|
return result;
|
|
}
|
|
|
|
fn constantScalarSource(self: *const LoweringState, type_id: shader_ir.id.TypeId, value: shader_ir.constant.ConstantValue) Error!operand.Source {
|
|
const data_type = try self.lowerScalarType(type_id);
|
|
const immediate: operand.Immediate = switch (data_type) {
|
|
.u32 => switch (value) {
|
|
.integer_bits => |bits| .{ .u32 = @truncate(bits) },
|
|
else => return Error.UnsupportedType,
|
|
},
|
|
.i32 => switch (value) {
|
|
.integer_bits => |bits| .{ .i32 = @bitCast(@as(u32, @truncate(bits))) },
|
|
else => return Error.UnsupportedType,
|
|
},
|
|
.f32 => switch (value) {
|
|
.float_bits => |bits| .{ .f32 = @bitCast(@as(u32, @truncate(bits))) },
|
|
else => return Error.UnsupportedType,
|
|
},
|
|
else => unreachable,
|
|
};
|
|
|
|
return .{
|
|
.register = .{ .immediate = immediate },
|
|
.type = data_type,
|
|
.region = operand.Region.broadcast(),
|
|
};
|
|
}
|
|
|
|
fn appendInstruction(self: *LoweringState, block_id: ids.BlockId, predicate_value: ?operand.Predicate, operation: instruction.Operation) Error!void {
|
|
_ = self.builder.appendInstruction(block_id, self.executionSize(), predicate_value, operation) catch |err|
|
|
return mapProgramError(err);
|
|
}
|
|
|
|
fn appendMove(self: *LoweringState, block_id: ids.BlockId, predicate_value: ?operand.Predicate, destination_value: operand.Destination, source_value: operand.Source) Error!void {
|
|
try self.appendInstruction(block_id, predicate_value, .{
|
|
.move = .{
|
|
.destination = destination_value,
|
|
.source = source_value,
|
|
},
|
|
});
|
|
}
|
|
|
|
fn sourceEntryFunction(self: *const LoweringState) Error!struct { shader_ir.id.FunctionId, *const shader_ir.module.Function } {
|
|
const source_entry = self.lowerer.module.entry_point orelse return Error.MissingEntryPoint;
|
|
const function = self.lowerer.module.functions.get(source_entry) orelse return Error.InvalidEntryPoint;
|
|
const return_type = self.lowerer.module.types.get(function.return_type) orelse return Error.InvalidModule;
|
|
if (return_type.* != .void or function.parameters.items.len != 0)
|
|
return Error.InvalidEntryPoint;
|
|
return .{ source_entry, function };
|
|
}
|
|
|
|
fn lowerBlocks(self: *LoweringState) Error!void {
|
|
const source_function_id, const function = try self.sourceEntryFunction();
|
|
|
|
for (function.blocks.items) |source_block_id| {
|
|
const source_block = self.lowerer.module.blocks.get(source_block_id) orelse return Error.InvalidModule;
|
|
if (source_block.parent_function != source_function_id)
|
|
return Error.InvalidModule;
|
|
const target_block_id = self.builder.addBlock(source_block.name) catch |err|
|
|
return mapProgramError(err);
|
|
if (source_block_id.index() >= self.block_map.len or self.block_map[source_block_id.index()] != null)
|
|
return Error.InvalidModule;
|
|
self.block_map[source_block_id.index()] = target_block_id;
|
|
}
|
|
|
|
const source_entry = function.entry_block orelse return Error.InvalidModule;
|
|
self.builder.setEntryBlock(try self.mappedBlock(source_entry)) catch |err| return mapProgramError(err);
|
|
}
|
|
|
|
fn lowerParameters(self: *LoweringState) Error!void {
|
|
const source_entry = try self.sourceEntryFunction();
|
|
const function = source_entry[1];
|
|
|
|
for (function.blocks.items) |source_block_id| {
|
|
const source_block = self.lowerer.module.blocks.get(source_block_id) orelse return Error.InvalidModule;
|
|
const target_block_id = try self.mappedBlock(source_block_id);
|
|
|
|
for (source_block.parameters.items) |parameter_id| {
|
|
const value = self.lowerer.module.values.get(parameter_id) orelse return Error.InvalidModule;
|
|
if (try self.isBoolean(value.type)) {
|
|
const flag_id = self.builder.addVirtualFlag(.{ .name = value.name }) catch |err|
|
|
return mapProgramError(err);
|
|
const predicate_value: operand.Predicate = .{ .flag = .{ .virtual = flag_id } };
|
|
try self.putLocation(parameter_id, .{ .predicate = .{ .dynamic = predicate_value } });
|
|
self.builder.addBlockParameter(target_block_id, .{ .flag = flag_id }) catch |err|
|
|
return mapProgramError(err);
|
|
} else {
|
|
const parameter_components = try self.addRegisterLocation(parameter_id, .temporary);
|
|
for (parameter_components) |parameter_source| {
|
|
const register_id = switch (parameter_source.register) {
|
|
.virtual => |id| id,
|
|
else => return Error.InvalidLoweredProgram,
|
|
};
|
|
self.builder.addBlockParameter(target_block_id, .{ .register = register_id }) catch |err|
|
|
return mapProgramError(err);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn lowerInstructions(self: *LoweringState, allocator: std.mem.Allocator) Error!void {
|
|
const visited = try allocator.alloc(bool, self.lowerer.module.blocks.entries.items.len);
|
|
defer allocator.free(visited);
|
|
@memset(visited, false);
|
|
|
|
const source_entry = try self.sourceEntryFunction();
|
|
const function = source_entry[1];
|
|
try self.lowerBlockInstructions(function.entry_block orelse return Error.InvalidModule, visited);
|
|
|
|
for (function.blocks.items) |source_block_id| {
|
|
if (!visited[source_block_id.index()])
|
|
try self.lowerBlockInstructions(source_block_id, visited);
|
|
}
|
|
}
|
|
|
|
fn lowerBlockInstructions(self: *LoweringState, source_block_id: shader_ir.id.BlockId, visited: []bool) Error!void {
|
|
if (source_block_id.index() >= visited.len)
|
|
return Error.InvalidModule;
|
|
|
|
if (visited[source_block_id.index()])
|
|
return;
|
|
|
|
visited[source_block_id.index()] = true;
|
|
|
|
const block = self.lowerer.module.blocks.get(source_block_id) orelse return Error.InvalidModule;
|
|
const target_block_id = try self.mappedBlock(source_block_id);
|
|
for (block.instructions.items) |instruction_id| {
|
|
const source_instruction = self.lowerer.module.instructions.get(instruction_id) orelse return Error.InvalidModule;
|
|
if (source_instruction.parent_block != source_block_id)
|
|
return Error.InvalidModule;
|
|
try self.lowerInstruction(target_block_id, source_instruction.*);
|
|
}
|
|
|
|
switch (block.terminator orelse return Error.InvalidModule) {
|
|
.branch => |edge| try self.lowerBlockInstructions(edge.target, visited),
|
|
.conditional_branch => |branch| {
|
|
try self.lowerBlockInstructions(branch.true_edge.target, visited);
|
|
try self.lowerBlockInstructions(branch.false_edge.target, visited);
|
|
},
|
|
else => {},
|
|
}
|
|
}
|
|
|
|
fn lowerInstruction(self: *LoweringState, block_id: ids.BlockId, source_instruction: shader_ir.instruction.Instruction) Error!void {
|
|
switch (source_instruction.operation) {
|
|
.unary => |operation| try self.lowerUnary(block_id, source_instruction.result, operation),
|
|
.binary => |operation| try self.lowerBinary(block_id, source_instruction.result, operation),
|
|
.compare => |operation| try self.lowerCompare(block_id, source_instruction.result, operation),
|
|
.select => |operation| try self.lowerSelect(block_id, source_instruction.result, operation),
|
|
.bitcast => |value_id| try self.lowerBitcast(block_id, source_instruction.result, value_id),
|
|
.load_interface => |operation| try self.lowerLoadInterface(block_id, source_instruction.result, operation),
|
|
.store_interface => |operation| try self.lowerStoreInterface(block_id, source_instruction.result, operation),
|
|
.composite_construct => |operation| try self.lowerCompositeConstruct(source_instruction.result, operation),
|
|
.composite_extract => |operation| try self.lowerCompositeExtract(source_instruction.result, operation),
|
|
.load_buffer => |operation| try self.lowerLoadBuffer(block_id, source_instruction.result, operation),
|
|
.store_buffer => |operation| try self.lowerStoreBuffer(block_id, source_instruction.result, operation),
|
|
.call => return Error.UnsanitizedModule,
|
|
}
|
|
}
|
|
|
|
fn requireResult(result: ?shader_ir.id.ValueId) Error!shader_ir.id.ValueId {
|
|
return result orelse Error.InvalidModule;
|
|
}
|
|
|
|
fn requireNoResult(result: ?shader_ir.id.ValueId) Error!void {
|
|
if (result != null)
|
|
return Error.InvalidModule;
|
|
}
|
|
|
|
fn lowerUnary(self: *LoweringState, block_id: ids.BlockId, result: ?shader_ir.id.ValueId, operation: shader_ir.instruction.Unary) Error!void {
|
|
const result_id = try requireResult(result);
|
|
if (operation.opcode == .logical_not) {
|
|
const source_predicate = try self.predicate(operation.operand);
|
|
const inverted: PredicateValue = switch (source_predicate) {
|
|
.constant => |value| .{ .constant = !value },
|
|
.dynamic => |value| .{ .dynamic = .{
|
|
.flag = value.flag,
|
|
.inverse = !value.inverse,
|
|
} },
|
|
};
|
|
try self.putLocation(result_id, .{ .predicate = inverted });
|
|
return;
|
|
}
|
|
|
|
const source_components = try self.components(operation.operand);
|
|
const result_components = try self.addRegisterLocation(result_id, .temporary);
|
|
if (source_components.len != result_components.len)
|
|
return Error.InvalidModule;
|
|
|
|
for (source_components, result_components) |source_component, result_component| {
|
|
if (source_component.type != result_component.type)
|
|
return Error.InvalidModule;
|
|
switch (operation.opcode) {
|
|
.negate => {
|
|
if (source_component.type != .i32 and source_component.type != .f32)
|
|
return Error.UnsupportedOperation;
|
|
var negated = source_component;
|
|
negated.negate = !negated.negate;
|
|
try self.appendMove(block_id, null, try destinationFromSource(result_component), negated);
|
|
},
|
|
.bitwise_not => {
|
|
const all_ones: operand.Immediate = switch (source_component.type) {
|
|
.u32 => .{ .u32 = std.math.maxInt(u32) },
|
|
.i32 => .{ .i32 = -1 },
|
|
else => return Error.UnsupportedOperation,
|
|
};
|
|
try self.appendInstruction(block_id, null, .{
|
|
.binary = .{
|
|
.opcode = .bitwise_xor,
|
|
.destination = try destinationFromSource(result_component),
|
|
.lhs = source_component,
|
|
.rhs = .{
|
|
.register = .{ .immediate = all_ones },
|
|
.type = source_component.type,
|
|
.region = operand.Region.broadcast(),
|
|
},
|
|
},
|
|
});
|
|
},
|
|
.logical_not => unreachable,
|
|
}
|
|
}
|
|
}
|
|
|
|
fn lowerBinary(self: *LoweringState, block_id: ids.BlockId, result: ?shader_ir.id.ValueId, operation: shader_ir.instruction.Binary) Error!void {
|
|
const result_id = try requireResult(result);
|
|
const lhs_components = try self.components(operation.lhs);
|
|
const rhs_components = try self.components(operation.rhs);
|
|
const result_components = try self.addRegisterLocation(result_id, .temporary);
|
|
if (lhs_components.len == 0 or lhs_components.len != rhs_components.len or lhs_components.len != result_components.len)
|
|
return Error.InvalidModule;
|
|
|
|
const data_type = lhs_components[0].type;
|
|
const opcode: instruction.BinaryOpcode = switch (operation.opcode) {
|
|
.integer_add => if (data_type == .u32 or data_type == .i32) .add else return Error.UnsupportedOperation,
|
|
.float_add => if (data_type == .f32) .add else return Error.UnsupportedOperation,
|
|
.integer_subtract => if (data_type == .u32 or data_type == .i32) .add else return Error.UnsupportedOperation,
|
|
.float_subtract => if (data_type == .f32) .add else return Error.UnsupportedOperation,
|
|
.integer_multiply => if (data_type == .u32 or data_type == .i32) .multiply else return Error.UnsupportedOperation,
|
|
.float_multiply => if (data_type == .f32) .multiply else return Error.UnsupportedOperation,
|
|
.shift_left => if (data_type == .u32 or data_type == .i32) .shift_left else return Error.UnsupportedOperation,
|
|
.logical_shift_right => if (data_type == .u32) .shift_right else return Error.UnsupportedOperation,
|
|
.arithmetic_shift_right => if (data_type == .i32) .shift_right else return Error.UnsupportedOperation,
|
|
.bitwise_and => if (data_type == .u32 or data_type == .i32) .bitwise_and else return Error.UnsupportedOperation,
|
|
.bitwise_or => if (data_type == .u32 or data_type == .i32) .bitwise_or else return Error.UnsupportedOperation,
|
|
.bitwise_xor => if (data_type == .u32 or data_type == .i32) .bitwise_xor else return Error.UnsupportedOperation,
|
|
.unsigned_divide,
|
|
.signed_divide,
|
|
.unsigned_modulo,
|
|
.signed_modulo,
|
|
.float_divide,
|
|
.float_modulo,
|
|
.logical_and,
|
|
.logical_or,
|
|
=> return Error.UnsupportedOperation,
|
|
};
|
|
|
|
for (lhs_components, rhs_components, result_components) |lhs, rhs_value, result_component| {
|
|
if (lhs.type != data_type or rhs_value.type != data_type or result_component.type != data_type)
|
|
return Error.InvalidModule;
|
|
var rhs = rhs_value;
|
|
if (operation.opcode == .integer_subtract or operation.opcode == .float_subtract)
|
|
rhs.negate = !rhs.negate;
|
|
try self.appendInstruction(block_id, null, .{
|
|
.binary = .{
|
|
.opcode = opcode,
|
|
.destination = try destinationFromSource(result_component),
|
|
.lhs = lhs,
|
|
.rhs = rhs,
|
|
},
|
|
});
|
|
}
|
|
}
|
|
|
|
fn lowerCompare(self: *LoweringState, block_id: ids.BlockId, result: ?shader_ir.id.ValueId, operation: shader_ir.instruction.Compare) Error!void {
|
|
const result_id = try requireResult(result);
|
|
const result_value = self.lowerer.module.values.get(result_id) orelse return Error.InvalidModule;
|
|
|
|
if (!try self.isBoolean(result_value.type))
|
|
return Error.InvalidModule;
|
|
|
|
const lhs_components = try self.components(operation.lhs);
|
|
const rhs_components = try self.components(operation.rhs);
|
|
if (lhs_components.len != 1 or rhs_components.len != 1)
|
|
return Error.UnsupportedOperation;
|
|
const lhs = lhs_components[0];
|
|
const rhs = rhs_components[0];
|
|
if (lhs.type != rhs.type)
|
|
return Error.InvalidModule;
|
|
|
|
const opcode: instruction.CompareOpcode = switch (operation.opcode) {
|
|
.equal => if (lhs.type == .u32 or lhs.type == .i32) .equal else return Error.UnsupportedOperation,
|
|
.not_equal => if (lhs.type == .u32 or lhs.type == .i32) .not_equal else return Error.UnsupportedOperation,
|
|
.unsigned_less => if (lhs.type == .u32) .less_than else return Error.UnsupportedOperation,
|
|
.signed_less => if (lhs.type == .i32) .less_than else return Error.UnsupportedOperation,
|
|
.ordered_float_equal,
|
|
.unordered_float_equal,
|
|
.ordered_float_not_equal,
|
|
.unordered_float_not_equal,
|
|
.ordered_float_less,
|
|
.unordered_float_less,
|
|
=> return Error.UnsupportedOperation,
|
|
};
|
|
|
|
const flag_id = self.builder.addVirtualFlag(.{ .name = result_value.name }) catch |err|
|
|
return mapProgramError(err);
|
|
|
|
const predicate_value: operand.Predicate = .{ .flag = .{ .virtual = flag_id } };
|
|
try self.putLocation(result_id, .{ .predicate = .{ .dynamic = predicate_value } });
|
|
try self.appendInstruction(block_id, null, .{
|
|
.compare = .{
|
|
.opcode = opcode,
|
|
.destination = predicate_value.flag,
|
|
.lhs = lhs,
|
|
.rhs = rhs,
|
|
},
|
|
});
|
|
}
|
|
|
|
fn lowerSelect(self: *LoweringState, block_id: ids.BlockId, result: ?shader_ir.id.ValueId, operation: shader_ir.instruction.Select) Error!void {
|
|
const result_id = try requireResult(result);
|
|
const true_components = try self.components(operation.true_value);
|
|
const false_components = try self.components(operation.false_value);
|
|
const result_components = try self.addRegisterLocation(result_id, .temporary);
|
|
if (true_components.len != false_components.len or true_components.len != result_components.len)
|
|
return Error.InvalidModule;
|
|
|
|
const condition = try self.predicate(operation.condition);
|
|
for (true_components, false_components, result_components) |true_value, false_value, result_component| {
|
|
const destination_value = try destinationFromSource(result_component);
|
|
if (true_value.type != destination_value.type or false_value.type != destination_value.type)
|
|
return Error.InvalidModule;
|
|
|
|
switch (condition) {
|
|
.constant => |constant| try self.appendMove(
|
|
block_id,
|
|
null,
|
|
destination_value,
|
|
if (constant) true_value else false_value,
|
|
),
|
|
.dynamic => |dynamic| {
|
|
try self.appendMove(block_id, .{
|
|
.flag = dynamic.flag,
|
|
.inverse = !dynamic.inverse,
|
|
}, destination_value, false_value);
|
|
try self.appendMove(block_id, dynamic, destination_value, true_value);
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
fn lowerBitcast(self: *LoweringState, block_id: ids.BlockId, result: ?shader_ir.id.ValueId, source_id: shader_ir.id.ValueId) Error!void {
|
|
const result_id = try requireResult(result);
|
|
const result_value = self.lowerer.module.values.get(result_id) orelse return Error.InvalidModule;
|
|
const target_type = try self.lowerType(result_value.type);
|
|
const source_components = try self.components(source_id);
|
|
const result_components = try self.addRegisterLocation(result_id, .temporary);
|
|
if (source_components.len != target_type.component_count or source_components.len != result_components.len)
|
|
return Error.UnsupportedOperation;
|
|
|
|
for (source_components, result_components) |source_component, result_component| {
|
|
// The source operand type selects the reinterpretation used by the
|
|
// move; the target-typed register materializes it before any CFG edge.
|
|
var cast_source = source_component;
|
|
cast_source.register = switch (cast_source.register) {
|
|
.immediate => |immediate| .{ .immediate = bitcastImmediate(immediate, target_type.element_type) },
|
|
else => cast_source.register,
|
|
};
|
|
cast_source.type = target_type.element_type;
|
|
try self.appendMove(block_id, null, try destinationFromSource(result_component), cast_source);
|
|
}
|
|
}
|
|
|
|
fn lowerCompositeConstruct(self: *LoweringState, result: ?shader_ir.id.ValueId, operation: shader_ir.instruction.CompositeConstruct) Error!void {
|
|
const result_id = try requireResult(result);
|
|
const result_value = self.lowerer.module.values.get(result_id) orelse return Error.InvalidModule;
|
|
const result_type = try self.lowerType(result_value.type);
|
|
if (result_type.component_count < 2 or operation.elements.len != result_type.component_count)
|
|
return Error.UnsupportedOperation;
|
|
|
|
const result_components = try self.storage.alloc(operand.Source, result_type.component_count);
|
|
for (operation.elements, result_components) |element_id, *component| {
|
|
const element_components = try self.components(element_id);
|
|
if (element_components.len != 1 or element_components[0].type != result_type.element_type)
|
|
return Error.InvalidModule;
|
|
component.* = element_components[0];
|
|
}
|
|
try self.putLocation(result_id, .{ .components = result_components });
|
|
}
|
|
|
|
fn lowerCompositeExtract(self: *LoweringState, result: ?shader_ir.id.ValueId, operation: shader_ir.instruction.CompositeExtract) Error!void {
|
|
const result_id = try requireResult(result);
|
|
if (operation.indices.len != 1)
|
|
return Error.UnsupportedOperation;
|
|
const source_components = try self.components(operation.composite);
|
|
const component_index: usize = operation.indices[0];
|
|
if (component_index >= source_components.len)
|
|
return Error.InvalidModule;
|
|
|
|
const result_value = self.lowerer.module.values.get(result_id) orelse return Error.InvalidModule;
|
|
const result_type = try self.lowerType(result_value.type);
|
|
if (result_type.component_count != 1 or result_type.element_type != source_components[component_index].type)
|
|
return Error.InvalidModule;
|
|
try self.putLocation(result_id, .{ .components = source_components[component_index .. component_index + 1] });
|
|
}
|
|
|
|
fn lowerLoadInterface(self: *LoweringState, block_id: ids.BlockId, result: ?shader_ir.id.ValueId, operation: shader_ir.instruction.LoadInterface) Error!void {
|
|
const result_id = try requireResult(result);
|
|
if (operation.element_index != null)
|
|
return Error.UnsupportedOperation;
|
|
|
|
const variable = self.lowerer.module.interface_variables.get(operation.variable) orelse return Error.InvalidModule;
|
|
if (variable.direction != .input)
|
|
return Error.InvalidModule;
|
|
switch (variable.semantic) {
|
|
.builtin => |builtin| if (builtin != .global_invocation_id)
|
|
return Error.UnsupportedOperation,
|
|
.location => return Error.UnsupportedOperation,
|
|
}
|
|
|
|
const result_value = self.lowerer.module.values.get(result_id) orelse return Error.InvalidModule;
|
|
if (result_value.type != variable.type)
|
|
return Error.InvalidModule;
|
|
const result_components = try self.addRegisterLocation(result_id, .temporary);
|
|
if (result_components.len != 3)
|
|
return Error.UnsupportedOperation;
|
|
for (result_components, 0..) |result_component, component_index| {
|
|
if (result_component.type != .u32)
|
|
return Error.UnsupportedOperation;
|
|
try self.appendInstruction(block_id, null, .{
|
|
.load_global_invocation_id = .{
|
|
.destination = try destinationFromSource(result_component),
|
|
.component = @intCast(component_index),
|
|
},
|
|
});
|
|
}
|
|
}
|
|
|
|
fn lowerStoreInterface(self: *LoweringState, block_id: ids.BlockId, result: ?shader_ir.id.ValueId, operation: shader_ir.instruction.StoreInterface) Error!void {
|
|
_ = self;
|
|
_ = block_id;
|
|
_ = operation;
|
|
try requireNoResult(result);
|
|
return Error.UnsupportedOperation;
|
|
}
|
|
|
|
fn lowerLoadBuffer(self: *LoweringState, block_id: ids.BlockId, result: ?shader_ir.id.ValueId, operation: shader_ir.instruction.LoadBuffer) Error!void {
|
|
const result_id = try requireResult(result);
|
|
const byte_offset = try self.source(operation.byte_offset);
|
|
if (byte_offset.type != .u32)
|
|
return Error.UnsupportedType;
|
|
const buffer = try self.storageBuffer(operation.resource);
|
|
const result_components = try self.addRegisterLocation(result_id, .temporary);
|
|
for (result_components, 0..) |result_component, component_index| {
|
|
try self.appendInstruction(block_id, null, .{
|
|
.load_buffer = .{
|
|
.destination = try destinationFromSource(result_component),
|
|
.buffer = .{ .logical = buffer },
|
|
.byte_offset = byte_offset,
|
|
.immediate_offset = @intCast(component_index * result_component.type.sizeBytes()),
|
|
},
|
|
});
|
|
}
|
|
}
|
|
|
|
fn lowerStoreBuffer(self: *LoweringState, block_id: ids.BlockId, result: ?shader_ir.id.ValueId, operation: shader_ir.instruction.StoreBuffer) Error!void {
|
|
try requireNoResult(result);
|
|
const byte_offset = try self.source(operation.byte_offset);
|
|
if (byte_offset.type != .u32)
|
|
return Error.UnsupportedType;
|
|
const buffer = try self.storageBuffer(operation.resource);
|
|
const source_components = try self.components(operation.value);
|
|
for (source_components, 0..) |source_component, component_index| {
|
|
try self.appendInstruction(block_id, null, .{
|
|
.store_buffer = .{
|
|
.buffer = .{ .logical = buffer },
|
|
.byte_offset = byte_offset,
|
|
.immediate_offset = @intCast(component_index * source_component.type.sizeBytes()),
|
|
.source = source_component,
|
|
},
|
|
});
|
|
}
|
|
}
|
|
|
|
fn lowerControlAndTerminators(self: *LoweringState, allocator: std.mem.Allocator) Error!void {
|
|
const source_entry = try self.sourceEntryFunction();
|
|
const function = source_entry[1];
|
|
|
|
for (function.blocks.items) |source_block_id| {
|
|
const source_block = self.lowerer.module.blocks.get(source_block_id) orelse return Error.InvalidModule;
|
|
const target_block_id = try self.mappedBlock(source_block_id);
|
|
const structured_control: instruction.StructuredControl = switch (source_block.structured_control) {
|
|
.none => .none,
|
|
.selection => |selection| .{ .selection = .{
|
|
.merge_block = try self.mappedBlock(selection.merge_block),
|
|
} },
|
|
.loop => |loop| .{ .loop = .{
|
|
.merge_block = try self.mappedBlock(loop.merge_block),
|
|
.continue_block = try self.mappedBlock(loop.continue_block),
|
|
} },
|
|
};
|
|
self.builder.setStructuredControl(target_block_id, structured_control) catch |err|
|
|
return mapProgramError(err);
|
|
|
|
const source_terminator = source_block.terminator orelse return Error.InvalidModule;
|
|
const target_terminator: instruction.Terminator = switch (source_terminator) {
|
|
.branch => |edge| .{ .jump = try self.lowerEdge(allocator, edge) },
|
|
.conditional_branch => |branch| conditional: {
|
|
switch (try self.predicate(branch.condition)) {
|
|
.constant => |condition| {
|
|
const edge = if (condition) branch.true_edge else branch.false_edge;
|
|
break :conditional .{ .jump = try self.lowerEdge(allocator, edge) };
|
|
},
|
|
.dynamic => |condition| {
|
|
const true_edge = try self.lowerEdge(allocator, branch.true_edge);
|
|
errdefer allocator.free(true_edge.arguments);
|
|
const false_edge = try self.lowerEdge(allocator, branch.false_edge);
|
|
break :conditional .{ .conditional_branch = .{
|
|
.predicate = condition,
|
|
.true_edge = true_edge,
|
|
.false_edge = false_edge,
|
|
} };
|
|
},
|
|
}
|
|
},
|
|
.return_void => .end_thread,
|
|
.return_value => return Error.InvalidEntryPoint,
|
|
.discard => return Error.UnsupportedTerminator,
|
|
.@"unreachable" => .@"unreachable",
|
|
};
|
|
defer freeTerminatorArguments(allocator, target_terminator);
|
|
self.builder.setTerminator(target_block_id, target_terminator) catch |err|
|
|
return mapProgramError(err);
|
|
}
|
|
}
|
|
|
|
fn lowerEdge(self: *LoweringState, allocator: std.mem.Allocator, edge: shader_ir.module.Edge) Error!instruction.Edge {
|
|
const target_source_block = self.lowerer.module.blocks.get(edge.target) orelse return Error.InvalidModule;
|
|
if (edge.arguments.len != target_source_block.parameters.items.len)
|
|
return Error.InvalidModule;
|
|
|
|
var arguments: std.ArrayList(pseudo.EdgeArgument) = .empty;
|
|
defer arguments.deinit(allocator);
|
|
for (edge.arguments) |argument_id| {
|
|
switch (try self.location(argument_id)) {
|
|
.components => |bundle| for (bundle) |component|
|
|
try arguments.append(allocator, .{ .source = component }),
|
|
.predicate => |predicate_value| try arguments.append(allocator, .{ .predicate = predicate_value }),
|
|
}
|
|
}
|
|
|
|
return .{
|
|
.target = try self.mappedBlock(edge.target),
|
|
.arguments = try arguments.toOwnedSlice(allocator),
|
|
};
|
|
}
|
|
};
|
|
|
|
fn freeTerminatorArguments(allocator: std.mem.Allocator, terminator: instruction.Terminator) void {
|
|
switch (terminator) {
|
|
.jump => |edge| allocator.free(edge.arguments),
|
|
.conditional_branch => |branch| {
|
|
allocator.free(branch.true_edge.arguments);
|
|
allocator.free(branch.false_edge.arguments);
|
|
},
|
|
else => {},
|
|
}
|
|
}
|
|
|
|
pub const Lowerer = struct {
|
|
module: *shader_ir.module.Module,
|
|
device_info: device.DeviceInfo,
|
|
options: Options,
|
|
|
|
pub fn init(module: *shader_ir.module.Module, device_info: device.DeviceInfo, options: Options) Lowerer {
|
|
return .{
|
|
.module = module,
|
|
.device_info = device_info,
|
|
.options = options,
|
|
};
|
|
}
|
|
|
|
pub fn lower(self: *Lowerer, allocator: std.mem.Allocator) Error!program_ir.Program {
|
|
shader_ir.validator.validate(self.module) catch |err| return switch (err) {
|
|
error.OutOfMemory => Error.OutOfMemory,
|
|
error.MissingEntryPoint => Error.MissingEntryPoint,
|
|
error.InvalidEntryPoint => Error.InvalidEntryPoint,
|
|
else => Error.InvalidModule,
|
|
};
|
|
|
|
var transformer_manager = shader_ir.transformer_manager.Manager.init(allocator);
|
|
defer transformer_manager.deinit();
|
|
transformer_manager.add(shader_ir.inline_all_functions.transformer) catch return Error.OutOfMemory;
|
|
|
|
var transformer_context: shader_ir.transformer_manager.Context = .{ .allocator = allocator };
|
|
_ = transformer_manager.run(self.module, &transformer_context) catch |err| return switch (err) {
|
|
error.OutOfMemory => Error.OutOfMemory,
|
|
else => Error.SanitizationFailed,
|
|
};
|
|
if (!self.module.properties.no_function_calls)
|
|
return Error.UnsanitizedModule;
|
|
|
|
if (self.module.stage != .compute)
|
|
return Error.UnsupportedStage;
|
|
const workgroup_size = self.module.execution_modes.workgroup_size orelse return Error.MissingWorkgroupSize;
|
|
if (workgroup_size[0] == 0 or workgroup_size[1] == 0 or workgroup_size[2] == 0)
|
|
return Error.InvalidModule;
|
|
|
|
var program = program_ir.Program.init(allocator, workgroup_size, self.device_info, self.options.dispatch_width);
|
|
errdefer program.deinit();
|
|
|
|
const block_map = try allocator.alloc(?ids.BlockId, self.module.blocks.entries.items.len);
|
|
defer allocator.free(block_map);
|
|
@memset(block_map, null);
|
|
|
|
const value_locations = try allocator.alloc(?ValueLocation, self.module.values.entries.items.len);
|
|
defer allocator.free(value_locations);
|
|
@memset(value_locations, null);
|
|
|
|
const storage_buffer_map = try allocator.alloc(?ids.StorageBufferId, self.module.resources.entries.items.len);
|
|
defer allocator.free(storage_buffer_map);
|
|
@memset(storage_buffer_map, null);
|
|
|
|
var state: LoweringState = .{
|
|
.lowerer = self,
|
|
.builder = Builder.init(&program),
|
|
.storage = program.allocator(),
|
|
.block_map = block_map,
|
|
.value_locations = value_locations,
|
|
.storage_buffer_map = storage_buffer_map,
|
|
};
|
|
|
|
try state.lowerBlocks();
|
|
try state.lowerParameters();
|
|
try state.lowerInstructions(allocator);
|
|
try state.lowerControlAndTerminators(allocator);
|
|
|
|
program.properties.common_ir_lowered = true;
|
|
validator.validate(&program) catch return Error.InvalidLoweredProgram;
|
|
return program;
|
|
}
|
|
};
|
|
|
|
fn bitcastImmediate(immediate: operand.Immediate, target_type: operand.DataType) operand.Immediate {
|
|
const bits: u32 = switch (immediate) {
|
|
.u32 => |value| value,
|
|
.i32 => |value| @bitCast(value),
|
|
.f32 => |value| @bitCast(value),
|
|
};
|
|
return switch (target_type) {
|
|
.u32 => .{ .u32 = bits },
|
|
.i32 => .{ .i32 = @bitCast(bits) },
|
|
.f32 => .{ .f32 = @bitCast(bits) },
|
|
else => unreachable,
|
|
};
|
|
}
|
|
|
|
fn mapProgramError(err: anyerror) Error {
|
|
return switch (err) {
|
|
Error.OutOfMemory => Error.OutOfMemory,
|
|
else => Error.InvalidLoweredProgram,
|
|
};
|
|
}
|
|
|
|
/// Convenience entry point for callers that do not need to retain a lowerer.
|
|
pub inline fn lower(allocator: std.mem.Allocator, module: *shader_ir.module.Module, device_info: device.DeviceInfo, options: Options) Error!program_ir.Program {
|
|
var lowerer = Lowerer.init(module, device_info, options);
|
|
return lowerer.lower(allocator);
|
|
}
|
|
|
|
const test_device: device.DeviceInfo = .{
|
|
.generation = .gen9,
|
|
.platform = .skylake,
|
|
.pci_device_id = 0x1912,
|
|
.grf_count = 128,
|
|
};
|
|
|
|
fn expectLowered(source: []const u8, expected: []const u8) !void {
|
|
var module = try shader_ir.parser.parseString(std.testing.allocator, source);
|
|
defer module.deinit();
|
|
module.execution_modes.workgroup_size = .{ 1, 1, 1 };
|
|
|
|
var program = try lower(std.testing.allocator, &module, test_device, .{});
|
|
defer program.deinit();
|
|
try block_arguments.run(std.testing.allocator, &program);
|
|
try std.testing.expect(program.properties.common_ir_lowered);
|
|
try std.testing.expect(!program.properties.instructions_selected);
|
|
|
|
const actual = try printer.allocPrint(std.testing.allocator, &program);
|
|
defer std.testing.allocator.free(actual);
|
|
try std.testing.expectEqualStrings(expected, actual);
|
|
}
|
|
|
|
fn expectLoweredFragments(source: []const u8, expected: []const []const u8, unexpected: []const []const u8) !void {
|
|
var module = try shader_ir.parser.parseString(std.testing.allocator, source);
|
|
defer module.deinit();
|
|
module.execution_modes.workgroup_size = .{ 1, 1, 1 };
|
|
|
|
var program = try lower(std.testing.allocator, &module, test_device, .{});
|
|
defer program.deinit();
|
|
try block_arguments.run(std.testing.allocator, &program);
|
|
try std.testing.expect(program.properties.common_ir_lowered);
|
|
try std.testing.expect(!program.properties.instructions_selected);
|
|
|
|
const actual = try printer.allocPrint(std.testing.allocator, &program);
|
|
defer std.testing.allocator.free(actual);
|
|
|
|
for (expected) |fragment|
|
|
try std.testing.expect(std.mem.indexOf(u8, actual, fragment) != null);
|
|
for (unexpected) |fragment|
|
|
try std.testing.expect(std.mem.indexOf(u8, actual, fragment) == null);
|
|
}
|
|
|
|
fn expectLoweringError(source: []const u8, expected: Error) !void {
|
|
var module = try shader_ir.parser.parseString(std.testing.allocator, source);
|
|
defer module.deinit();
|
|
module.execution_modes.workgroup_size = .{ 1, 1, 1 };
|
|
|
|
var program = lower(std.testing.allocator, &module, test_device, .{}) catch |actual| {
|
|
try std.testing.expectEqual(expected, actual);
|
|
return;
|
|
};
|
|
defer program.deinit();
|
|
return error.TestExpectedError;
|
|
}
|
|
|
|
test "[ir] Lower: basic shader" {
|
|
const source =
|
|
\\shader compute @main
|
|
\\{
|
|
\\ %one: constant u32 = bits(0x1)
|
|
\\ %two: constant u32 = bits(0x2)
|
|
\\ fn @main() -> void
|
|
\\ {
|
|
\\ .entry():
|
|
\\ %sum: u32 = integer_add %one, %two
|
|
\\ %condition: bool = cmp_unsigned_less %one, %two
|
|
\\ conditional_branch %condition, .left(), .right()
|
|
\\ .left():
|
|
\\ branch .merge(%sum)
|
|
\\ .right():
|
|
\\ branch .merge(%two)
|
|
\\ .merge(%value: u32):
|
|
\\ return
|
|
\\ }
|
|
\\}
|
|
;
|
|
|
|
const expected =
|
|
\\; Flint compute program:
|
|
\\; .workgroup_size: [1, 1, 1]
|
|
\\; .generation: gen9
|
|
\\; .platform: skylake
|
|
\\; .dispatch_width: simd8
|
|
\\
|
|
\\%value: vgrf u32[8], class(temporary), size(32), alignment(32), spillable
|
|
\\%sum: vgrf u32[8], class(temporary), size(32), alignment(32), spillable
|
|
\\%condition: vflag
|
|
\\
|
|
\\.entry:
|
|
\\ [simd8] add %sum:u32, 1:u32, 2:u32
|
|
\\ [simd8] cmp_less_than %condition, 1:u32, 2:u32
|
|
\\ conditional_branch (+%condition), .left, .right
|
|
\\
|
|
\\.left:
|
|
\\ jump .b4
|
|
\\
|
|
\\.right:
|
|
\\ jump .b5
|
|
\\
|
|
\\.merge:
|
|
\\ end_thread
|
|
\\
|
|
\\.b4:
|
|
\\ [simd8] parallel_copy [%value:u32 <- %sum:u32]
|
|
\\ jump .merge
|
|
\\
|
|
\\.b5:
|
|
\\ [simd8] parallel_copy [%value:u32 <- 2:u32]
|
|
\\ jump .merge
|
|
\\
|
|
\\
|
|
;
|
|
|
|
try expectLowered(source, expected);
|
|
}
|
|
|
|
test "[ir] Lower: control flow" {
|
|
const source =
|
|
\\shader compute @main
|
|
\\{
|
|
\\ %one: constant u32 = bits(0x1)
|
|
\\ fn @main() -> void
|
|
\\ {
|
|
\\ .entry():
|
|
\\ branch .producer()
|
|
\\ .producer():
|
|
\\ %sum: u32 = integer_add %one, %one
|
|
\\ branch .merge()
|
|
\\ .merge():
|
|
\\ %doubled: u32 = integer_add %sum, %one
|
|
\\ return
|
|
\\ }
|
|
\\}
|
|
;
|
|
|
|
const expected =
|
|
\\; Flint compute program:
|
|
\\; .workgroup_size: [1, 1, 1]
|
|
\\; .generation: gen9
|
|
\\; .platform: skylake
|
|
\\; .dispatch_width: simd8
|
|
\\
|
|
\\%sum: vgrf u32[8], class(temporary), size(32), alignment(32), spillable
|
|
\\%doubled: vgrf u32[8], class(temporary), size(32), alignment(32), spillable
|
|
\\
|
|
\\.entry:
|
|
\\ jump .producer
|
|
\\
|
|
\\.producer:
|
|
\\ [simd8] add %sum:u32, 1:u32, 1:u32
|
|
\\ jump .merge
|
|
\\
|
|
\\.merge:
|
|
\\ [simd8] add %doubled:u32, %sum:u32, 1:u32
|
|
\\ end_thread
|
|
\\
|
|
\\
|
|
;
|
|
|
|
try expectLowered(source, expected);
|
|
}
|
|
|
|
test "[ir] Lower: function call" {
|
|
const source =
|
|
\\shader compute @main
|
|
\\{
|
|
\\ %one: constant u32 = bits(0x1)
|
|
\\ fn @main() -> void
|
|
\\ {
|
|
\\ .entry():
|
|
\\ %result: u32 = call @identity(%one)
|
|
\\ return
|
|
\\ }
|
|
\\ fn @identity(%value: u32) -> u32
|
|
\\ {
|
|
\\ .entry():
|
|
\\ return %value
|
|
\\ }
|
|
\\}
|
|
;
|
|
|
|
const expected =
|
|
\\; Flint compute program:
|
|
\\; .workgroup_size: [1, 1, 1]
|
|
\\; .generation: gen9
|
|
\\; .platform: skylake
|
|
\\; .dispatch_width: simd8
|
|
\\
|
|
\\%result: vgrf u32[8], class(temporary), size(32), alignment(32), spillable
|
|
\\
|
|
\\.entry:
|
|
\\ jump .b2
|
|
\\
|
|
\\.b1:
|
|
\\ end_thread
|
|
\\
|
|
\\.b2:
|
|
\\ jump .b3
|
|
\\
|
|
\\.b3:
|
|
\\ [simd8] parallel_copy [%result:u32 <- 1:u32]
|
|
\\ jump .b1
|
|
\\
|
|
\\
|
|
;
|
|
|
|
try expectLowered(source, expected);
|
|
}
|
|
|
|
test "[ir] Lower: unary/binary operations" {
|
|
const source =
|
|
\\shader compute @main
|
|
\\{
|
|
\\ %u_one: constant u32 = bits(0x1)
|
|
\\ %u_two: constant u32 = bits(0x2)
|
|
\\ %i_one: constant i32 = bits(0x1)
|
|
\\ %i_two: constant i32 = bits(0x2)
|
|
\\ %f_one: constant f32 = bits(0x3f800000)
|
|
\\ %f_two: constant f32 = bits(0x40000000)
|
|
\\
|
|
\\ fn @main() -> void
|
|
\\ {
|
|
\\ .entry():
|
|
\\ %integer_negated: i32 = negate %i_one
|
|
\\ %float_negated: f32 = negate %f_one
|
|
\\ %inverted: u32 = bitwise_not %u_one
|
|
\\ %integer_difference: i32 = integer_subtract %i_one, %i_two
|
|
\\ %float_difference: f32 = float_subtract %f_one, %f_two
|
|
\\ %integer_product: u32 = integer_multiply %u_one, %u_two
|
|
\\ %float_product: f32 = float_multiply %f_one, %f_two
|
|
\\ %shifted_left: u32 = shift_left %u_one, %u_two
|
|
\\ %logical_right: u32 = logical_shift_right %u_two, %u_one
|
|
\\ %arithmetic_right: i32 = arithmetic_shift_right %i_two, %i_one
|
|
\\ %masked: u32 = bitwise_and %u_one, %u_two
|
|
\\ %combined: u32 = bitwise_or %u_one, %u_two
|
|
\\ %toggled: u32 = bitwise_xor %u_one, %u_two
|
|
\\ return
|
|
\\ }
|
|
\\}
|
|
;
|
|
|
|
try expectLoweredFragments(source, &.{
|
|
"[simd8] mov %integer_negated:i32, -1:i32",
|
|
"[simd8] mov %float_negated:f32, -1:f32",
|
|
"[simd8] bitwise_xor %inverted:u32, 1:u32, 4294967295:u32",
|
|
"[simd8] add %integer_difference:i32, 1:i32, -2:i32",
|
|
"[simd8] add %float_difference:f32, 1:f32, -2:f32",
|
|
"[simd8] multiply %integer_product:u32, 1:u32, 2:u32",
|
|
"[simd8] multiply %float_product:f32, 1:f32, 2:f32",
|
|
"[simd8] shift_left %shifted_left:u32, 1:u32, 2:u32",
|
|
"[simd8] shift_right %logical_right:u32, 2:u32, 1:u32",
|
|
"[simd8] shift_right %arithmetic_right:i32, 2:i32, 1:i32",
|
|
"[simd8] bitwise_and %masked:u32, 1:u32, 2:u32",
|
|
"[simd8] bitwise_or %combined:u32, 1:u32, 2:u32",
|
|
"[simd8] bitwise_xor %toggled:u32, 1:u32, 2:u32",
|
|
}, &.{});
|
|
}
|
|
|
|
test "[ir] Lower: selects and bitcasts" {
|
|
const source =
|
|
\\shader compute @main
|
|
\\{
|
|
\\ %always: constant bool = true
|
|
\\ %one: constant u32 = bits(0x1)
|
|
\\ %two: constant u32 = bits(0x2)
|
|
\\ %float_one: constant f32 = bits(0x3f800000)
|
|
\\ fn @main() -> void
|
|
\\ {
|
|
\\ .entry():
|
|
\\ %condition: bool = cmp_unsigned_less %one, %two
|
|
\\ %dynamic_choice: u32 = select %condition, %one, %two
|
|
\\ %inverted_condition: bool = logical_not %condition
|
|
\\ %inverted_choice: u32 = select %inverted_condition, %one, %two
|
|
\\ %constant_choice: u32 = select %always, %one, %two
|
|
\\ %one_bits: u32 = bitcast %float_one
|
|
\\ %constant_sum: u32 = integer_add %one_bits, %one
|
|
\\ %negative: f32 = negate %float_one
|
|
\\ %negative_bits: u32 = bitcast %negative
|
|
\\ %register_sum: u32 = integer_add %negative_bits, %one
|
|
\\ return
|
|
\\ }
|
|
\\}
|
|
;
|
|
|
|
try expectLoweredFragments(source, &.{
|
|
"[simd8] cmp_less_than %condition, 1:u32, 2:u32",
|
|
"[simd8] (-%condition) mov %dynamic_choice:u32, 2:u32",
|
|
"[simd8] (+%condition) mov %dynamic_choice:u32, 1:u32",
|
|
"[simd8] (+%condition) mov %inverted_choice:u32, 2:u32",
|
|
"[simd8] (-%condition) mov %inverted_choice:u32, 1:u32",
|
|
"[simd8] mov %constant_choice:u32, 1:u32",
|
|
"[simd8] mov %one_bits:u32, 1065353216:u32",
|
|
"[simd8] add %constant_sum:u32, %one_bits:u32, 1:u32",
|
|
"[simd8] mov %negative:f32, -1:f32",
|
|
"[simd8] mov %negative_bits:u32, %negative:u32",
|
|
"[simd8] add %register_sum:u32, %negative_bits:u32, 1:u32",
|
|
}, &.{});
|
|
}
|
|
|
|
test "[ir] Lower: global invocation ID" {
|
|
const source =
|
|
\\shader compute @main
|
|
\\{
|
|
\\ @global_id: vec3[u32] = input[builtin(global_invocation_id)]
|
|
\\ fn @main() -> void
|
|
\\ {
|
|
\\ .entry():
|
|
\\ %id: vec3[u32] = load_interface @global_id
|
|
\\ %x: u32 = composite_extract %id[0]
|
|
\\ return
|
|
\\ }
|
|
\\}
|
|
;
|
|
|
|
try expectLoweredFragments(source, &.{
|
|
"%id_x: vgrf u32[8], class(temporary)",
|
|
"%id_z: vgrf u32[8], class(temporary)",
|
|
"[simd8] load_global_invocation_id %id_x:u32, component(0)",
|
|
"[simd8] load_global_invocation_id %id_y:u32, component(1)",
|
|
"[simd8] load_global_invocation_id %id_z:u32, component(2)",
|
|
}, &.{});
|
|
}
|
|
|
|
test "[ir] Lower: vector storage-buffer operations" {
|
|
const source =
|
|
\\shader compute @main
|
|
\\{
|
|
\\ @source: vec4[u32] = storage_buffer[set(0), binding(1)]
|
|
\\ @destination: vec4[u32] = storage_buffer[set(0), binding(2)]
|
|
\\ %offset: constant u32 = 16
|
|
\\ fn @main() -> void
|
|
\\ {
|
|
\\ .entry():
|
|
\\ %value: vec4[u32] = load_buffer @source, %offset
|
|
\\ store_buffer @destination, %offset, %value
|
|
\\ return
|
|
\\ }
|
|
\\}
|
|
;
|
|
|
|
try expectLoweredFragments(source, &.{
|
|
"@source = storage_buffer[set(0), binding(1)]",
|
|
"@destination = storage_buffer[set(0), binding(2)]",
|
|
"[simd8] load_buffer %value_x:u32, @source, 16:u32",
|
|
"[simd8] load_buffer %value_y:u32, @source, 16:u32, offset(4)",
|
|
"[simd8] load_buffer %value_w:u32, @source, 16:u32, offset(12)",
|
|
"[simd8] store_buffer @destination, 16:u32, %value_x:u32",
|
|
"[simd8] store_buffer @destination, 16:u32, offset(12), %value_w:u32",
|
|
}, &.{});
|
|
}
|
|
|
|
test "[ir] Lower: vector block parameter" {
|
|
const source =
|
|
\\shader compute @main
|
|
\\{
|
|
\\ %one: constant u32 = bits(0x1)
|
|
\\ %two: constant u32 = bits(0x2)
|
|
\\ fn @main() -> void
|
|
\\ {
|
|
\\ .entry():
|
|
\\ %pair: vec2[u32] = composite_construct %one, %two
|
|
\\ branch .merge(%pair)
|
|
\\ .merge(%merged: vec2[u32]):
|
|
\\ %first: u32 = composite_extract %merged[0]
|
|
\\ return
|
|
\\ }
|
|
\\}
|
|
;
|
|
|
|
try expectLoweredFragments(source, &.{
|
|
"%merged_x: vgrf u32[8]",
|
|
"%merged_y: vgrf u32[8]",
|
|
"parallel_copy [%merged_x:u32 <- 1:u32, %merged_y:u32 <- 2:u32]",
|
|
}, &.{
|
|
".merge(",
|
|
});
|
|
}
|
|
|
|
test "[ir] Lower: reject non-compute interfaces" {
|
|
try expectLoweringError(
|
|
\\shader compute @main
|
|
\\{
|
|
\\ @input: u32 = input[location(0), component(0), index(0)]
|
|
\\ fn @main() -> void
|
|
\\ {
|
|
\\ .entry():
|
|
\\ %value: u32 = load_interface @input
|
|
\\ return
|
|
\\ }
|
|
\\}
|
|
, Error.UnsupportedOperation);
|
|
}
|
|
|
|
test "[ir] Lower: constant conditional branch" {
|
|
const source =
|
|
\\shader compute @main
|
|
\\{
|
|
\\ %always: constant bool = true
|
|
\\ fn @main() -> void
|
|
\\ {
|
|
\\ .entry():
|
|
\\ conditional_branch %always, .taken(), .untaken()
|
|
\\ .taken():
|
|
\\ return
|
|
\\ .untaken():
|
|
\\ return
|
|
\\ }
|
|
\\}
|
|
;
|
|
|
|
try expectLoweredFragments(source, &.{
|
|
".entry:\n jump .taken",
|
|
".taken:\n end_thread",
|
|
".untaken:\n end_thread",
|
|
}, &.{
|
|
"conditional_branch",
|
|
"vflag",
|
|
});
|
|
}
|
|
|
|
test "[ir] Lower: boolean block parameter" {
|
|
const source =
|
|
\\shader compute @main
|
|
\\{
|
|
\\ %one: constant u32 = bits(0x1)
|
|
\\ %two: constant u32 = bits(0x2)
|
|
\\ %never: constant bool = false
|
|
\\ fn @main() -> void
|
|
\\ {
|
|
\\ .entry():
|
|
\\ %condition: bool = cmp_unsigned_less %one, %two
|
|
\\ conditional_branch %condition, .left(), .right()
|
|
\\ .left():
|
|
\\ branch .merge(%condition)
|
|
\\ .right():
|
|
\\ branch .merge(%never)
|
|
\\ .merge(%merged: bool):
|
|
\\ conditional_branch %merged, .taken(), .not_taken()
|
|
\\ .taken():
|
|
\\ return
|
|
\\ .not_taken():
|
|
\\ return
|
|
\\ }
|
|
\\}
|
|
;
|
|
|
|
try expectLoweredFragments(source, &.{
|
|
"%condition: vflag",
|
|
"%merged: vflag",
|
|
"parallel_copy [%merged <- (+%condition)]",
|
|
"parallel_copy [%merged <- false]",
|
|
".merge:\n conditional_branch (+%merged), .taken, .not_taken",
|
|
}, &.{});
|
|
}
|
|
|
|
test "[ir] Lower: unsupported operations" {
|
|
try expectLoweringError(
|
|
\\shader compute @main
|
|
\\{
|
|
\\ %one: constant u32 = bits(0x1)
|
|
\\ %two: constant u32 = bits(0x2)
|
|
\\ fn @main() -> void
|
|
\\ {
|
|
\\ .entry():
|
|
\\ %quotient: u32 = unsigned_divide %one, %two
|
|
\\ return
|
|
\\ }
|
|
\\}
|
|
, Error.UnsupportedOperation);
|
|
|
|
try expectLoweringError(
|
|
\\shader compute @main
|
|
\\{
|
|
\\ %one: constant u32 = bits(0x1)
|
|
\\ %two: constant u32 = bits(0x2)
|
|
\\ fn @main() -> void
|
|
\\ {
|
|
\\ .entry():
|
|
\\ %wide: vec5[u32] = composite_construct %one, %two, %one, %two, %one
|
|
\\ return
|
|
\\ }
|
|
\\}
|
|
, Error.UnsupportedType);
|
|
|
|
try expectLoweringError(
|
|
\\shader compute @main
|
|
\\{
|
|
\\ %one: constant u16 = bits(0x1)
|
|
\\ fn @main() -> void
|
|
\\ {
|
|
\\ .entry():
|
|
\\ %sum: u16 = integer_add %one, %one
|
|
\\ return
|
|
\\ }
|
|
\\}
|
|
, Error.UnsupportedType);
|
|
}
|
|
|
|
test "[ir] Lower: unreachable terminator" {
|
|
const source =
|
|
\\shader compute @main
|
|
\\{
|
|
\\ fn @main() -> void
|
|
\\ {
|
|
\\ .entry():
|
|
\\ unreachable
|
|
\\ }
|
|
\\}
|
|
;
|
|
|
|
try expectLoweredFragments(source, &.{
|
|
".entry:\n unreachable",
|
|
}, &.{});
|
|
}
|
|
|
|
test "[ir] Lower: common lowering is generation and dispatch-width agnostic" {
|
|
var module = try shader_ir.parser.parseString(std.testing.allocator,
|
|
\\shader compute @main
|
|
\\{
|
|
\\ fn @main() -> void
|
|
\\ {
|
|
\\ .entry():
|
|
\\ return
|
|
\\ }
|
|
\\}
|
|
);
|
|
defer module.deinit();
|
|
module.execution_modes.workgroup_size = .{ 1, 1, 1 };
|
|
|
|
var other_target = test_device;
|
|
other_target.generation = .gen11;
|
|
other_target.grf_size_bytes = 64;
|
|
other_target.supports_simd16 = true;
|
|
var program = try lower(std.testing.allocator, &module, other_target, .{ .dispatch_width = .simd16 });
|
|
defer program.deinit();
|
|
|
|
try std.testing.expectEqual(device.Generation.gen11, program.device_info.generation);
|
|
try std.testing.expectEqual(device.DispatchWidth.simd16, program.dispatch_width);
|
|
try validator.validate(&program);
|
|
}
|