spirv: spir-v dedicated type system

This commit is contained in:
Robin Voetter
2022-01-22 01:49:47 +01:00
parent 1b6ebce0da
commit 98ee39d1b0
4 changed files with 854 additions and 137 deletions

View File

@@ -9,14 +9,20 @@ const Module = @This();
const std = @import("std");
const Allocator = std.mem.Allocator;
const assert = std.debug.assert;
const ZigDecl = @import("../../Module.zig").Decl;
const spec = @import("spec.zig");
const Word = spec.Word;
const IdRef = spec.IdRef;
const IdResult = spec.IdResult;
const IdResultType = spec.IdResultType;
const Section = @import("Section.zig");
const Type = @import("type.zig").Type;
const TypeCache = std.ArrayHashMapUnmanaged(Type, IdResultType, Type.ShallowHashContext32, true);
/// A general-purpose allocator which may be used to allocate resources for this module
gpa: Allocator,
@@ -57,6 +63,12 @@ next_result_id: Word,
/// just the ones for OpLine. Note that OpLine needs the result of OpString, and not that of OpSource.
source_file_names: std.StringHashMapUnmanaged(IdRef) = .{},
/// SPIR-V type cache. Note that according to SPIR-V spec section 2.8, Types and Variables, non-pointer
/// non-aggrerate types (which includes matrices and vectors) must have a _unique_ representation in
/// the final binary.
/// Note: Uses ArrayHashMap which is insertion ordered, so that we may refer to other types by index (Type.Ref).
type_cache: TypeCache = .{},
pub fn init(gpa: Allocator, arena: Allocator) Module {
return .{
.gpa = gpa,
@@ -75,44 +87,20 @@ pub fn deinit(self: *Module) void {
self.sections.functions.deinit(self.gpa);
self.source_file_names.deinit(self.gpa);
self.type_cache.deinit(self.gpa);
self.* = undefined;
}
pub fn allocId(self: *Module) spec.IdResult {
defer self.next_result_id += 1;
return .{.id = self.next_result_id};
return .{ .id = self.next_result_id };
}
pub fn idBound(self: Module) Word {
return self.next_result_id;
}
/// Fetch the result-id of an OpString instruction that encodes the path of the source
/// file of the decl. This function may also emit an OpSource with source-level information regarding
/// the decl.
pub fn resolveSourceFileName(self: *Module, decl: *ZigDecl) !IdRef {
const path = decl.getFileScope().sub_file_path;
const result = try self.source_file_names.getOrPut(self.gpa, path);
if (!result.found_existing) {
const file_result_id = self.allocId();
result.value_ptr.* = file_result_id.toRef();
try self.sections.debug_strings.emit(self.gpa, .OpString, .{
.id_result = file_result_id,
.string = path,
});
try self.sections.debug_strings.emit(self.gpa, .OpSource, .{
.source_language = .Unknown, // TODO: Register Zig source language.
.version = 0, // TODO: Zig version as u32?
.file = file_result_id.toRef(),
.source = null, // TODO: Store actual source also?
});
}
return result.value_ptr.*;
}
/// Emit this module as a spir-v binary.
pub fn flush(self: Module, file: std.fs.File) !void {
// See SPIR-V Spec section 2.3, "Physical Layout of a SPIR-V Module and Instruction"
@@ -151,3 +139,290 @@ pub fn flush(self: Module, file: std.fs.File) !void {
try file.setEndPos(file_size);
try file.pwritevAll(&iovc_buffers, 0);
}
/// Fetch the result-id of an OpString instruction that encodes the path of the source
/// file of the decl. This function may also emit an OpSource with source-level information regarding
/// the decl.
pub fn resolveSourceFileName(self: *Module, decl: *ZigDecl) !IdRef {
const path = decl.getFileScope().sub_file_path;
const result = try self.source_file_names.getOrPut(self.gpa, path);
if (!result.found_existing) {
const file_result_id = self.allocId();
result.value_ptr.* = file_result_id.toRef();
try self.sections.debug_strings.emit(self.gpa, .OpString, .{
.id_result = file_result_id,
.string = path,
});
try self.sections.debug_strings.emit(self.gpa, .OpSource, .{
.source_language = .Unknown, // TODO: Register Zig source language.
.version = 0, // TODO: Zig version as u32?
.file = file_result_id.toRef(),
.source = null, // TODO: Store actual source also?
});
}
return result.value_ptr.*;
}
/// Fetch a result-id for a spir-v type. This function deduplicates the type as appropriate,
/// and returns a cached version if that exists.
/// Note: This function does not attempt to perform any validation on the type.
/// The type is emitted in a shallow fashion; any child types should already
/// be emitted at this point.
pub fn resolveType(self: *Module, ty: Type) !Type.Ref {
const result = try self.type_cache.getOrPut(self.gpa, ty);
if (!result.found_existing) {
result.value_ptr.* = try self.emitType(ty);
}
return result.index;
}
pub fn resolveTypeId(self: *Module, ty: Type) !IdRef {
return self.typeResultId(try self.resolveType(ty));
}
/// Get the result-id of a particular type, by reference. Asserts type_ref is valid.
pub fn typeResultId(self: Module, type_ref: Type.Ref) IdResultType {
return self.type_cache.values()[type_ref];
}
/// Get the result-id of a particular type as IdRef, by Type.Ref. Asserts type_ref is valid.
pub fn typeRefId(self: Module, type_ref: Type.Ref) IdRef {
return self.type_cache.values()[type_ref].toRef();
}
/// Unconditionally emit a spir-v type into the appropriate section.
/// Note: If this function is called with a type that is already generated, it may yield an invalid module
/// as non-pointer non-aggregrate types must me unique!
/// Note: This function does not attempt to perform any validation on the type.
/// The type is emitted in a shallow fashion; any child types should already
/// be emitted at this point.
pub fn emitType(self: *Module, ty: Type) !IdResultType {
const result_id = self.allocId();
const ref_id = result_id.toRef();
const types = &self.sections.types_globals_constants;
const annotations = &self.sections.annotations;
const result_id_operand = .{ .id_result = result_id };
switch (ty.tag()) {
.void => try types.emit(self.gpa, .OpTypeVoid, result_id_operand),
.bool => try types.emit(self.gpa, .OpTypeBool, result_id_operand),
.int => try types.emit(self.gpa, .OpTypeInt, .{
.id_result = result_id,
.width = ty.payload(.int).width,
.signedness = switch (ty.payload(.int).signedness) {
.unsigned => @as(spec.LiteralInteger, 0),
.signed => 1,
},
}),
.float => try types.emit(self.gpa, .OpTypeFloat, .{
.id_result = result_id,
.width = ty.payload(.float).width,
}),
.vector => try types.emit(self.gpa, .OpTypeVector, .{
.id_result = result_id,
.component_type = self.typeResultId(ty.childType()).toRef(),
.component_count = ty.payload(.vector).component_count,
}),
.matrix => try types.emit(self.gpa, .OpTypeMatrix, .{
.id_result = result_id,
.column_type = self.typeResultId(ty.childType()).toRef(),
.column_count = ty.payload(.matrix).column_count,
}),
.image => {
const info = ty.payload(.image);
try types.emit(self.gpa, .OpTypeImage, .{
.id_result = result_id,
.sampled_type = self.typeResultId(ty.childType()).toRef(),
.dim = info.dim,
.depth = @enumToInt(info.depth),
.arrayed = @boolToInt(info.arrayed),
.ms = @boolToInt(info.multisampled),
.sampled = @enumToInt(info.sampled),
.image_format = info.format,
.access_qualifier = info.access_qualifier,
});
},
.sampler => try types.emit(self.gpa, .OpTypeSampler, result_id_operand),
.sampled_image => try types.emit(self.gpa, .OpTypeSampledImage, .{
.id_result = result_id,
.image_type = self.typeResultId(ty.childType()).toRef(),
}),
.array => {
const info = ty.payload(.array);
assert(info.length != 0);
try types.emit(self.gpa, .OpTypeArray, .{
.id_result = result_id,
.element_type = self.typeResultId(ty.childType()).toRef(),
.length = .{ .id = 0 }, // TODO: info.length must be emitted as constant!
});
if (info.array_stride != 0) {
try annotations.decorate(self.gpa, ref_id, .{ .ArrayStride = .{ .array_stride = info.array_stride } });
}
},
.runtime_array => {
const info = ty.payload(.runtime_array);
try types.emit(self.gpa, .OpTypeRuntimeArray, .{
.id_result = result_id,
.element_type = self.typeResultId(ty.childType()).toRef(),
});
if (info.array_stride != 0) {
try annotations.decorate(self.gpa, ref_id, .{ .ArrayStride = .{ .array_stride = info.array_stride } });
}
},
.@"struct" => {
const info = ty.payload(.@"struct");
try types.emitRaw(self.gpa, .OpTypeStruct, 1 + info.members.len);
types.writeOperand(IdResult, result_id);
for (info.members) |member| {
types.writeOperand(IdRef, self.typeResultId(member.ty).toRef());
}
try self.decorateStruct(ref_id, info);
},
.@"opaque" => try types.emit(self.gpa, .OpTypeOpaque, .{
.id_result = result_id,
.literal_string = ty.payload(.@"opaque").name,
}),
.pointer => {
const info = ty.payload(.pointer);
try types.emit(self.gpa, .OpTypePointer, .{
.id_result = result_id,
.storage_class = info.storage_class,
.type = self.typeResultId(ty.childType()).toRef(),
});
if (info.array_stride != 0) {
try annotations.decorate(self.gpa, ref_id, .{ .ArrayStride = .{ .array_stride = info.array_stride } });
}
if (info.alignment) |alignment| {
try annotations.decorate(self.gpa, ref_id, .{ .Alignment = .{ .alignment = alignment } });
}
if (info.max_byte_offset) |max_byte_offset| {
try annotations.decorate(self.gpa, ref_id, .{ .MaxByteOffset = .{ .max_byte_offset = max_byte_offset } });
}
},
.function => {
const info = ty.payload(.function);
try types.emitRaw(self.gpa, .OpTypeFunction, 2 + info.parameters.len);
types.writeOperand(IdResult, result_id);
types.writeOperand(IdRef, self.typeResultId(info.return_type).toRef());
for (info.parameters) |parameter_type| {
types.writeOperand(IdRef, self.typeResultId(parameter_type).toRef());
}
},
.event => try types.emit(self.gpa, .OpTypeEvent, result_id_operand),
.device_event => try types.emit(self.gpa, .OpTypeDeviceEvent, result_id_operand),
.reserve_id => try types.emit(self.gpa, .OpTypeReserveId, result_id_operand),
.queue => try types.emit(self.gpa, .OpTypeQueue, result_id_operand),
.pipe => try types.emit(self.gpa, .OpTypePipe, .{
.id_result = result_id,
.qualifier = ty.payload(.pipe).qualifier,
}),
.pipe_storage => try types.emit(self.gpa, .OpTypePipeStorage, result_id_operand),
.named_barrier => try types.emit(self.gpa, .OpTypeNamedBarrier, result_id_operand),
}
return result_id.toResultType();
}
fn decorateStruct(self: *Module, target: IdRef, info: *const Type.Payload.Struct) !void {
const annotations = &self.sections.annotations;
// Decorations for the struct type itself.
if (info.decorations.block)
try annotations.decorate(self.gpa, target, .Block);
if (info.decorations.buffer_block)
try annotations.decorate(self.gpa, target, .BufferBlock);
if (info.decorations.glsl_shared)
try annotations.decorate(self.gpa, target, .GLSLShared);
if (info.decorations.glsl_packed)
try annotations.decorate(self.gpa, target, .GLSLPacked);
if (info.decorations.c_packed)
try annotations.decorate(self.gpa, target, .CPacked);
// Decorations for the struct members.
const extra = info.member_decoration_extra;
var extra_i: u32 = 0;
for (info.members) |member, i| {
const d = member.decorations;
const index = @intCast(Word, i);
switch (d.matrix_layout) {
.row_major => try annotations.decorateMember(self.gpa, target, index, .RowMajor),
.col_major => try annotations.decorateMember(self.gpa, target, index, .ColMajor),
.none => {},
}
if (d.matrix_layout != .none) {
try annotations.decorateMember(self.gpa, target, index, .{
.MatrixStride = .{ .matrix_stride = extra[extra_i] },
});
extra_i += 1;
}
if (d.no_perspective)
try annotations.decorateMember(self.gpa, target, index, .NoPerspective);
if (d.flat)
try annotations.decorateMember(self.gpa, target, index, .Flat);
if (d.patch)
try annotations.decorateMember(self.gpa, target, index, .Patch);
if (d.centroid)
try annotations.decorateMember(self.gpa, target, index, .Centroid);
if (d.sample)
try annotations.decorateMember(self.gpa, target, index, .Sample);
if (d.invariant)
try annotations.decorateMember(self.gpa, target, index, .Invariant);
if (d.@"volatile")
try annotations.decorateMember(self.gpa, target, index, .Volatile);
if (d.coherent)
try annotations.decorateMember(self.gpa, target, index, .Coherent);
if (d.non_writable)
try annotations.decorateMember(self.gpa, target, index, .NonWritable);
if (d.non_readable)
try annotations.decorateMember(self.gpa, target, index, .NonReadable);
if (d.builtin) {
try annotations.decorateMember(self.gpa, target, index, .{
.BuiltIn = .{ .built_in = @intToEnum(spec.BuiltIn, extra[extra_i]) },
});
extra_i += 1;
}
if (d.stream) {
try annotations.decorateMember(self.gpa, target, index, .{
.Stream = .{ .stream_number = extra[extra_i] },
});
extra_i += 1;
}
if (d.location) {
try annotations.decorateMember(self.gpa, target, index, .{
.Location = .{ .location = extra[extra_i] },
});
extra_i += 1;
}
if (d.component) {
try annotations.decorateMember(self.gpa, target, index, .{
.Component = .{ .component = extra[extra_i] },
});
extra_i += 1;
}
if (d.xfb_buffer) {
try annotations.decorateMember(self.gpa, target, index, .{
.XfbBuffer = .{ .xfb_buffer_number = extra[extra_i] },
});
extra_i += 1;
}
if (d.xfb_stride) {
try annotations.decorateMember(self.gpa, target, index, .{
.XfbStride = .{ .xfb_stride = extra[extra_i] },
});
extra_i += 1;
}
if (d.user_semantic) {
const len = extra[extra_i];
extra_i += 1;
const semantic = @ptrCast([*]const u8, &extra[extra_i])[0..len];
try annotations.decorateMember(self.gpa, target, index, .{
.UserSemantic = .{ .semantic = semantic },
});
extra_i += std.math.divCeil(u32, extra_i, @sizeOf(u32)) catch unreachable;
}
}
}