spirv: translate remaining types

This commit is contained in:
Robin Voetter
2023-05-29 23:54:09 +02:00
parent fcb422585c
commit 0552a8b11f
5 changed files with 322 additions and 1583 deletions

View File

@@ -20,12 +20,13 @@ const IdResult = spec.IdResult;
const IdResultType = spec.IdResultType;
const Section = @import("Section.zig");
const Type = @import("type.zig").Type;
pub const TypeConstantCache = @import("TypeConstantCache.zig");
const TypeCache = std.ArrayHashMapUnmanaged(Type, IdResultType, Type.ShallowHashContext32, true);
const Cache = @import("TypeConstantCache.zig");
pub const CacheKey = Cache.Key;
pub const CacheRef = Cache.Ref;
pub const CacheString = Cache.String;
/// This structure represents a function that is in-progress of being emitted.
/// This structure represents a function that isc in-progress of being emitted.
/// Commonly, the contents of this structure will be merged with the appropriate
/// sections of the module and re-used. Note that the SPIR-V module system makes
/// no attempt of compacting result-id's, so any Fn instance should ultimately
@@ -130,7 +131,7 @@ sections: struct {
/// From this section, OpLine and OpNoLine is allowed.
/// According to the SPIR-V documentation, this section normally
/// also holds type and constant instructions. These are managed
/// via the tc_cache instead, which is the sole structure that
/// via the cache instead, which is the sole structure that
/// manages that section. These will be inserted between this and
/// the previous section when emitting the final binary.
/// TODO: Do we need this section? Globals are also managed with another mechanism.
@@ -152,10 +153,9 @@ 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) = .{},
type_cache: TypeCache = .{},
/// SPIR-V type- and constant cache. This structure is used to store information about these in a more
/// efficient manner.
tc_cache: TypeConstantCache = .{},
cache: Cache = .{},
/// Set of Decls, referred to by Decl.Index.
decls: std.ArrayListUnmanaged(Decl) = .{},
@@ -196,7 +196,7 @@ pub fn deinit(self: *Module) void {
self.sections.functions.deinit(self.gpa);
self.source_file_names.deinit(self.gpa);
self.tc_cache.deinit(self);
self.cache.deinit(self);
self.decls.deinit(self.gpa);
self.decl_deps.deinit(self.gpa);
@@ -223,20 +223,20 @@ pub fn idBound(self: Module) Word {
return self.next_result_id;
}
pub fn resolve(self: *Module, key: TypeConstantCache.Key) !TypeConstantCache.Ref {
return self.tc_cache.resolve(self, key);
pub fn resolve(self: *Module, key: CacheKey) !CacheRef {
return self.cache.resolve(self, key);
}
pub fn resultId(self: *Module, ref: TypeConstantCache.Ref) IdResult {
return self.tc_cache.resultId(ref);
pub fn resultId(self: *const Module, ref: CacheRef) IdResult {
return self.cache.resultId(ref);
}
pub fn resolveId(self: *Module, key: TypeConstantCache.Key) !IdResult {
pub fn resolveId(self: *Module, key: CacheKey) !IdResult {
return self.resultId(try self.resolve(key));
}
pub fn resolveString(self: *Module, str: []const u8) !TypeConstantCache.String {
return try self.tc_cache.addString(self, str);
pub fn resolveString(self: *Module, str: []const u8) !CacheString {
return try self.cache.addString(self, str);
}
fn orderGlobalsInto(
@@ -350,7 +350,7 @@ pub fn flush(self: *Module, file: std.fs.File) !void {
var entry_points = try self.entryPoints();
defer entry_points.deinit(self.gpa);
var types_constants = try self.tc_cache.materialize(self);
var types_constants = try self.cache.materialize(self);
defer types_constants.deinit(self.gpa);
// Note: needs to be kept in order according to section 2.3!
@@ -364,6 +364,7 @@ pub fn flush(self: *Module, file: std.fs.File) !void {
self.sections.debug_names.toWords(),
self.sections.annotations.toWords(),
types_constants.toWords(),
self.sections.types_globals_constants.toWords(),
self.sections.globals.toWords(),
globals.toWords(),
self.sections.functions.toWords(),
@@ -416,364 +417,14 @@ pub fn resolveSourceFileName(self: *Module, decl: *ZigDecl) !IdRef {
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);
const index = @intToEnum(Type.Ref, result.index);
if (!result.found_existing) {
const ref = try self.emitType(ty);
self.type_cache.values()[result.index] = ref;
}
return index;
pub fn intType(self: *Module, signedness: std.builtin.Signedness, bits: u16) !CacheRef {
return try self.resolve(.{ .int_type = .{
.signedness = signedness,
.bits = bits,
} });
}
pub fn resolveTypeId(self: *Module, ty: Type) !IdResultType {
const ty_ref = try self.resolveType(ty);
return self.typeId(ty_ref);
}
pub fn typeRefType(self: Module, ty_ref: Type.Ref) Type {
return self.type_cache.keys()[@enumToInt(ty_ref)];
}
/// Get the result-id of a particular type, by reference. Asserts type_ref is valid.
pub fn typeId(self: Module, ty_ref: Type.Ref) IdResultType {
return self.type_cache.values()[@enumToInt(ty_ref)];
}
/// 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) error{OutOfMemory}!IdResultType {
const result_id = self.allocId();
const ref_id = result_id;
const types = &self.sections.types_globals_constants;
const debug_names = &self.sections.debug_names;
const result_id_operand = .{ .id_result = result_id };
switch (ty.tag()) {
.void => {
try types.emit(self.gpa, .OpTypeVoid, result_id_operand);
try debug_names.emit(self.gpa, .OpName, .{
.target = result_id,
.name = "void",
});
},
.bool => {
try types.emit(self.gpa, .OpTypeBool, result_id_operand);
try debug_names.emit(self.gpa, .OpName, .{
.target = result_id,
.name = "bool",
});
},
.u8,
.u16,
.u32,
.u64,
.i8,
.i16,
.i32,
.i64,
.int,
=> {
// TODO: Kernels do not support OpTypeInt that is signed. We can probably
// can get rid of the signedness all together, in Shaders also.
const bits = ty.intFloatBits();
const signedness: spec.LiteralInteger = switch (ty.intSignedness()) {
.unsigned => 0,
.signed => 1,
};
try types.emit(self.gpa, .OpTypeInt, .{
.id_result = result_id,
.width = bits,
.signedness = signedness,
});
const ui: []const u8 = switch (signedness) {
0 => "u",
1 => "i",
else => unreachable,
};
const name = try std.fmt.allocPrint(self.gpa, "{s}{}", .{ ui, bits });
defer self.gpa.free(name);
try debug_names.emit(self.gpa, .OpName, .{
.target = result_id,
.name = name,
});
},
.f16, .f32, .f64 => {
const bits = ty.intFloatBits();
try types.emit(self.gpa, .OpTypeFloat, .{
.id_result = result_id,
.width = bits,
});
const name = try std.fmt.allocPrint(self.gpa, "f{}", .{bits});
defer self.gpa.free(name);
try debug_names.emit(self.gpa, .OpName, .{
.target = result_id,
.name = name,
});
},
.vector => try types.emit(self.gpa, .OpTypeVector, .{
.id_result = result_id,
.component_type = self.typeId(ty.childType()),
.component_count = ty.payload(.vector).component_count,
}),
.matrix => try types.emit(self.gpa, .OpTypeMatrix, .{
.id_result = result_id,
.column_type = self.typeId(ty.childType()),
.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.typeId(ty.childType()),
.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.typeId(ty.childType()),
}),
.array => {
const info = ty.payload(.array);
assert(info.length != 0);
const size_type = Type.initTag(.u32);
const size_type_id = try self.resolveTypeId(size_type);
const length_id = self.allocId();
try self.emitConstant(size_type_id, length_id, .{ .uint32 = info.length });
try types.emit(self.gpa, .OpTypeArray, .{
.id_result = result_id,
.element_type = self.typeId(ty.childType()),
.length = length_id,
});
if (info.array_stride != 0) {
try self.decorate(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.typeId(ty.childType()),
});
if (info.array_stride != 0) {
try self.decorate(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.typeId(member.ty));
}
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.typeId(ty.childType()),
});
if (info.array_stride != 0) {
try self.decorate(ref_id, .{ .ArrayStride = .{ .array_stride = info.array_stride } });
}
if (info.alignment != 0) {
try self.decorate(ref_id, .{ .Alignment = .{ .alignment = info.alignment } });
}
if (info.max_byte_offset) |max_byte_offset| {
try self.decorate(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.typeId(info.return_type));
for (info.parameters) |parameter_type| {
types.writeOperand(IdRef, self.typeId(parameter_type));
}
},
.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;
}
fn decorateStruct(self: *Module, target: IdRef, info: *const Type.Payload.Struct) !void {
const debug_names = &self.sections.debug_names;
if (info.name.len != 0) {
try debug_names.emit(self.gpa, .OpName, .{
.target = target,
.name = info.name,
});
}
// Decorations for the struct type itself.
if (info.decorations.block)
try self.decorate(target, .Block);
if (info.decorations.buffer_block)
try self.decorate(target, .BufferBlock);
if (info.decorations.glsl_shared)
try self.decorate(target, .GLSLShared);
if (info.decorations.glsl_packed)
try self.decorate(target, .GLSLPacked);
if (info.decorations.c_packed)
try self.decorate(target, .CPacked);
// Decorations for the struct members.
const extra = info.member_decoration_extra;
var extra_i: u32 = 0;
for (info.members, 0..) |member, i| {
const d = member.decorations;
const index = @intCast(Word, i);
if (member.name.len != 0) {
try debug_names.emit(self.gpa, .OpMemberName, .{
.type = target,
.member = index,
.name = member.name,
});
}
switch (member.offset) {
.none => {},
else => try self.decorateMember(
target,
index,
.{ .Offset = .{ .byte_offset = @enumToInt(member.offset) } },
),
}
switch (d.matrix_layout) {
.row_major => try self.decorateMember(target, index, .RowMajor),
.col_major => try self.decorateMember(target, index, .ColMajor),
.none => {},
}
if (d.matrix_layout != .none) {
try self.decorateMember(target, index, .{
.MatrixStride = .{ .matrix_stride = extra[extra_i] },
});
extra_i += 1;
}
if (d.no_perspective)
try self.decorateMember(target, index, .NoPerspective);
if (d.flat)
try self.decorateMember(target, index, .Flat);
if (d.patch)
try self.decorateMember(target, index, .Patch);
if (d.centroid)
try self.decorateMember(target, index, .Centroid);
if (d.sample)
try self.decorateMember(target, index, .Sample);
if (d.invariant)
try self.decorateMember(target, index, .Invariant);
if (d.@"volatile")
try self.decorateMember(target, index, .Volatile);
if (d.coherent)
try self.decorateMember(target, index, .Coherent);
if (d.non_writable)
try self.decorateMember(target, index, .NonWritable);
if (d.non_readable)
try self.decorateMember(target, index, .NonReadable);
if (d.builtin) {
try self.decorateMember(target, index, .{
.BuiltIn = .{ .built_in = @intToEnum(spec.BuiltIn, extra[extra_i]) },
});
extra_i += 1;
}
if (d.stream) {
try self.decorateMember(target, index, .{
.Stream = .{ .stream_number = extra[extra_i] },
});
extra_i += 1;
}
if (d.location) {
try self.decorateMember(target, index, .{
.Location = .{ .location = extra[extra_i] },
});
extra_i += 1;
}
if (d.component) {
try self.decorateMember(target, index, .{
.Component = .{ .component = extra[extra_i] },
});
extra_i += 1;
}
if (d.xfb_buffer) {
try self.decorateMember(target, index, .{
.XfbBuffer = .{ .xfb_buffer_number = extra[extra_i] },
});
extra_i += 1;
}
if (d.xfb_stride) {
try self.decorateMember(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 self.decorateMember(target, index, .{
.UserSemantic = .{ .semantic = semantic },
});
extra_i += std.math.divCeil(u32, extra_i, @sizeOf(u32)) catch unreachable;
}
}
}
pub fn simpleStructType(self: *Module, members: []const Type.Payload.Struct.Member) !Type.Ref {
const payload = try self.arena.create(Type.Payload.Struct);
payload.* = .{
.members = try self.arena.dupe(Type.Payload.Struct.Member, members),
.decorations = .{},
};
return try self.resolveType(Type.initPayload(&payload.base));
}
pub fn arrayType2(self: *Module, len: u32, elem_ty_ref: TypeConstantCache.Ref) !TypeConstantCache.Ref {
pub fn arrayType(self: *Module, len: u32, elem_ty_ref: CacheRef) !CacheRef {
const len_ty_ref = try self.resolve(.{ .int_type = .{
.signedness = .unsigned,
.bits = 32,
@@ -788,41 +439,45 @@ pub fn arrayType2(self: *Module, len: u32, elem_ty_ref: TypeConstantCache.Ref) !
} });
}
pub fn arrayType(self: *Module, len: u32, ty: Type.Ref) !Type.Ref {
const payload = try self.arena.create(Type.Payload.Array);
payload.* = .{
.element_type = ty,
.length = len,
};
return try self.resolveType(Type.initPayload(&payload.base));
}
pub fn ptrType(
self: *Module,
child: Type.Ref,
child: CacheRef,
storage_class: spec.StorageClass,
alignment: u32,
) !Type.Ref {
const ptr_payload = try self.arena.create(Type.Payload.Pointer);
ptr_payload.* = .{
) !CacheRef {
return try self.resolve(.{ .ptr_type = .{
.storage_class = storage_class,
.child_type = child,
.alignment = alignment,
};
return try self.resolveType(Type.initPayload(&ptr_payload.base));
} });
}
pub fn changePtrStorageClass(self: *Module, ptr_ty_ref: Type.Ref, new_storage_class: spec.StorageClass) !Type.Ref {
const payload = try self.arena.create(Type.Payload.Pointer);
payload.* = self.typeRefType(ptr_ty_ref).payload(.pointer).*;
payload.storage_class = new_storage_class;
return try self.resolveType(Type.initPayload(&payload.base));
pub fn constInt(self: *Module, ty_ref: CacheRef, value: anytype) !IdRef {
const ty = self.cache.lookup(ty_ref).int_type;
const Value = Cache.Key.Int.Value;
return try self.resolveId(.{ .int = .{
.ty = ty_ref,
.value = switch (ty.signedness) {
.signed => Value{ .int64 = @intCast(i64, value) },
.unsigned => Value{ .uint64 = @intCast(u64, value) },
},
} });
}
pub fn constComposite(self: *Module, ty_ref: Type.Ref, members: []const IdRef) !IdRef {
pub fn constUndef(self: *Module, ty_ref: CacheRef) !IdRef {
return try self.resolveId(.{ .undef = .{ .ty = ty_ref } });
}
pub fn constNull(self: *Module, ty_ref: CacheRef) !IdRef {
return try self.resolveId(.{ .null = .{ .ty = ty_ref } });
}
pub fn constBool(self: *Module, ty_ref: CacheRef, value: bool) !IdRef {
return try self.resolveId(.{ .bool = .{ .ty = ty_ref, .value = value } });
}
pub fn constComposite(self: *Module, ty_ref: CacheRef, members: []const IdRef) !IdRef {
const result_id = self.allocId();
try self.sections.types_globals_constants.emit(self.gpa, .OpSpecConstantComposite, .{
.id_result_type = self.typeId(ty_ref),
.id_result_type = self.resultId(ty_ref),
.id_result = result_id,
.constituents = members,
});