Commit 98ee39d1b0
Changed files (4)
src
codegen
src/codegen/spirv/Module.zig
@@ -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;
+ }
+ }
+}
src/codegen/spirv/Section.zig
@@ -32,11 +32,7 @@ pub fn toWords(section: Section) []Word {
}
/// Append the instructions from another section into this section.
-pub fn append(
- section: *Section,
- allocator: Allocator,
- other_section: Section
-) !void {
+pub fn append(section: *Section, allocator: Allocator, other_section: Section) !void {
try section.instructions.appendSlice(allocator, other_section.instructions.items);
}
@@ -64,6 +60,34 @@ pub fn emit(
section.writeOperands(opcode.Operands(), operands);
}
+/// Decorate a result-id.
+pub fn decorate(
+ section: *Section,
+ allocator: Allocator,
+ target: spec.IdRef,
+ decoration: spec.Decoration.Extended,
+) !void {
+ try section.emit(allocator, .OpDecorate, .{
+ .target = target,
+ .decoration = decoration,
+ });
+}
+
+/// Decorate a result-id which is a member of some struct.
+pub fn decorateMember(
+ section: *Section,
+ allocator: Allocator,
+ structure_type: spec.IdRef,
+ member: u32,
+ decoration: spec.Decoration.Extended,
+) !void {
+ try section.emit(allocator, .OpMemberDecorate, .{
+ .structure_type = structure_type,
+ .member = member,
+ .decoration = decoration,
+ });
+}
+
pub fn writeWord(section: *Section, word: Word) void {
section.instructions.appendAssumeCapacity(word);
}
@@ -93,10 +117,7 @@ fn writeOperands(section: *Section, comptime Operands: type, operands: Operands)
pub fn writeOperand(section: *Section, comptime Operand: type, operand: Operand) void {
switch (Operand) {
- spec.IdResultType,
- spec.IdResult,
- spec.IdRef
- => section.writeWord(operand.id),
+ spec.IdResultType, spec.IdResult, spec.IdRef => section.writeWord(operand.id),
spec.LiteralInteger => section.writeWord(operand),
@@ -320,8 +341,8 @@ test "SPIR-V Section emit() - simple" {
defer section.deinit(std.testing.allocator);
try section.emit(std.testing.allocator, .OpUndef, .{
- .id_result_type = .{.id = 0},
- .id_result = .{.id = 1},
+ .id_result_type = .{ .id = 0 },
+ .id_result = .{ .id = 1 },
});
try testing.expectEqualSlices(Word, &.{
@@ -338,7 +359,7 @@ test "SPIR-V Section emit() - string" {
try section.emit(std.testing.allocator, .OpSource, .{
.source_language = .Unknown,
.version = 123,
- .file = .{.id = 456},
+ .file = .{ .id = 456 },
.source = "pub fn main() void {}",
});
@@ -361,8 +382,8 @@ test "SPIR-V Section emit()- extended mask" {
defer section.deinit(std.testing.allocator);
try section.emit(std.testing.allocator, .OpLoopMerge, .{
- .merge_block = .{.id = 10},
- .continue_target = .{.id = 20},
+ .merge_block = .{ .id = 10 },
+ .continue_target = .{ .id = 20 },
.loop_control = .{
.Unroll = true,
.DependencyLength = .{
@@ -375,7 +396,7 @@ test "SPIR-V Section emit()- extended mask" {
(@as(Word, 5) << 16) | @enumToInt(Opcode.OpLoopMerge),
10,
20,
- @bitCast(Word, spec.LoopControl{.Unroll = true, .DependencyLength = true}),
+ @bitCast(Word, spec.LoopControl{ .Unroll = true, .DependencyLength = true }),
2,
}, section.instructions.items);
}
@@ -385,9 +406,9 @@ test "SPIR-V Section emit() - extended union" {
defer section.deinit(std.testing.allocator);
try section.emit(std.testing.allocator, .OpExecutionMode, .{
- .entry_point = .{.id = 888},
+ .entry_point = .{ .id = 888 },
.mode = .{
- .LocalSize = .{.x_size = 4, .y_size = 8, .z_size = 16},
+ .LocalSize = .{ .x_size = 4, .y_size = 8, .z_size = 16 },
},
});
src/codegen/spirv/type.zig
@@ -0,0 +1,433 @@
+//! This module models a SPIR-V Type. These are distinct from Zig types, with some types
+//! which are not representable by Zig directly.
+
+const std = @import("std");
+const assert = std.debug.assert;
+
+const spec = @import("spec.zig");
+
+pub const Type = extern union {
+ tag_if_small_enough: Tag,
+ ptr_otherwise: *Payload,
+
+ /// A reference to another SPIR-V type.
+ pub const Ref = usize;
+
+ pub fn initTag(comptime small_tag: Tag) Type {
+ comptime assert(@enumToInt(small_tag) < Tag.no_payload_count);
+ return .{ .tag_if_small_enough = small_tag };
+ }
+
+ pub fn initPayload(pl: *Payload) Type {
+ assert(@enumToInt(pl.tag) >= Tag.no_payload_count);
+ return .{ .ptr_otherwise = pl };
+ }
+
+ pub fn tag(self: Type) Tag {
+ if (@enumToInt(self.tag_if_small_enough) < Tag.no_payload_count) {
+ return self.tag_if_small_enough;
+ } else {
+ return self.ptr_otherwise.tag;
+ }
+ }
+
+ pub fn castTag(self: Type, comptime t: Tag) ?*t.Type() {
+ if (@enumToInt(self.tag_if_small_enough) < Tag.no_payload_count)
+ return null;
+
+ if (self.ptr_otherwise.tag == t)
+ return self.payload(t);
+
+ return null;
+ }
+
+ /// Access the payload of a type directly.
+ pub fn payload(self: Type, comptime t: Tag) *t.Type() {
+ assert(self.tag() == t);
+ return @fieldParentPtr(t.Type(), "base", self.ptr_otherwise);
+ }
+
+ /// Perform a shallow equality test, comparing two types while assuming that any child types
+ /// are equal only if their references are equal.
+ pub fn eqlShallow(a: Type, b: Type) bool {
+ if (a.tag_if_small_enough == b.tag_if_small_enough)
+ return true;
+
+ const tag_a = a.tag();
+ const tag_b = b.tag();
+ if (tag_a != tag_b)
+ return false;
+
+ inline for (@typeInfo(Tag).Enum.fields) |field| {
+ const t = @field(Tag, field.name);
+ if (t == tag_a) {
+ return eqlPayloads(t, a, b);
+ }
+ }
+
+ unreachable;
+ }
+
+ /// Compare the payload of two compatible tags, given that we already know the tag of both types.
+ fn eqlPayloads(comptime t: Tag, a: Type, b: Type) bool {
+ switch (t) {
+ .void,
+ .bool,
+ .sampler,
+ .event,
+ .device_event,
+ .reserve_id,
+ .queue,
+ .pipe_storage,
+ .named_barrier,
+ => return true,
+ .int,
+ .float,
+ .vector,
+ .matrix,
+ .sampled_image,
+ .array,
+ .runtime_array,
+ .@"opaque",
+ .pointer,
+ .pipe,
+ .image,
+ => return std.meta.eql(a.payload(t).*, b.payload(t).*),
+ .@"struct" => {
+ const struct_a = a.payload(.@"struct");
+ const struct_b = b.payload(.@"struct");
+ if (struct_a.members.len != struct_b.members.len)
+ return false;
+ for (struct_a.members) |mem_a, i| {
+ if (!std.meta.eql(mem_a, struct_b.members[i]))
+ return false;
+ }
+ return true;
+ },
+ .@"function" => {
+ const fn_a = a.payload(.function);
+ const fn_b = b.payload(.function);
+ if (fn_a.return_type != fn_b.return_type)
+ return false;
+ return std.mem.eql(Ref, fn_a.parameters, fn_b.parameters);
+ },
+ }
+ }
+
+ /// Perform a shallow hash, which hashes the reference value of child types instead of recursing.
+ pub fn hashShallow(self: Type) u64 {
+ var hasher = std.hash.Wyhash.init(0);
+ const t = self.tag();
+ std.hash.autoHash(&hasher, t);
+
+ inline for (@typeInfo(Tag).Enum.fields) |field| {
+ if (@field(Tag, field.name) == t) {
+ switch (@field(Tag, field.name)) {
+ .void,
+ .bool,
+ .sampler,
+ .event,
+ .device_event,
+ .reserve_id,
+ .queue,
+ .pipe_storage,
+ .named_barrier,
+ => {},
+ else => self.hashPayload(@field(Tag, field.name), &hasher),
+ }
+ }
+ }
+
+ return hasher.final();
+ }
+
+ /// Perform a shallow hash, given that we know the tag of the field ahead of time.
+ fn hashPayload(self: Type, comptime t: Tag, hasher: *std.hash.Wyhash) void {
+ const fields = @typeInfo(t.Type()).Struct.fields;
+ const pl = self.payload(t);
+ comptime assert(std.mem.eql(u8, fields[0].name, "base"));
+ inline for (fields[1..]) |field| { // Skip the 'base' field.
+ std.hash.autoHashStrat(hasher, @field(pl, field.name), .DeepRecursive);
+ }
+ }
+
+ /// Hash context that hashes and compares types in a shallow fashion, useful for type caches.
+ pub const ShallowHashContext32 = struct {
+ pub fn hash(self: @This(), t: Type) u32 {
+ _ = self;
+ return @truncate(u32, t.hashShallow());
+ }
+ pub fn eql(self: @This(), a: Type, b: Type) bool {
+ _ = self;
+ return a.eqlShallow(b);
+ }
+ };
+
+ /// Return the reference to any child type. Asserts the type is one of:
+ /// - Vectors
+ /// - Matrices
+ /// - Images
+ /// - SampledImages,
+ /// - Arrays
+ /// - RuntimeArrays
+ /// - Pointers
+ pub fn childType(self: Type) Ref {
+ return switch (self.tag()) {
+ .vector => self.payload(.vector).component_type,
+ .matrix => self.payload(.matrix).column_type,
+ .image => self.payload(.image).sampled_type,
+ .sampled_image => self.payload(.sampled_image).image_type,
+ .array => self.payload(.array).element_type,
+ .runtime_array => self.payload(.runtime_array).element_type,
+ .pointer => self.payload(.pointer).child_type,
+ else => unreachable,
+ };
+ }
+
+ pub const Tag = enum(usize) {
+ void,
+ bool,
+ sampler,
+ event,
+ device_event,
+ reserve_id,
+ queue,
+ pipe_storage,
+ named_barrier,
+
+ // After this, the tag requires a payload.
+ int,
+ float,
+ vector,
+ matrix,
+ image,
+ sampled_image,
+ array,
+ runtime_array,
+ @"struct",
+ @"opaque",
+ pointer,
+ function,
+ pipe,
+
+ pub const last_no_payload_tag = Tag.named_barrier;
+ pub const no_payload_count = @enumToInt(last_no_payload_tag) + 1;
+
+ pub fn Type(comptime t: Tag) type {
+ return switch (t) {
+ .void, .bool, .sampler, .event, .device_event, .reserve_id, .queue, .pipe_storage, .named_barrier => @compileError("Type Tag " ++ @tagName(t) ++ " has no payload"),
+ .int => Payload.Int,
+ .float => Payload.Float,
+ .vector => Payload.Vector,
+ .matrix => Payload.Matrix,
+ .image => Payload.Image,
+ .sampled_image => Payload.SampledImage,
+ .array => Payload.Array,
+ .runtime_array => Payload.RuntimeArray,
+ .@"struct" => Payload.Struct,
+ .@"opaque" => Payload.Opaque,
+ .pointer => Payload.Pointer,
+ .function => Payload.Function,
+ .pipe => Payload.Pipe,
+ };
+ }
+ };
+
+ pub const Payload = struct {
+ tag: Tag,
+
+ pub const Int = struct {
+ base: Payload = .{ .tag = .int },
+ width: u32,
+ signedness: std.builtin.Signedness,
+ };
+
+ pub const Float = struct {
+ base: Payload = .{ .tag = .float },
+ width: u32,
+ };
+
+ pub const Vector = struct {
+ base: Payload = .{ .tag = .vector },
+ component_type: Ref,
+ component_count: u32,
+ };
+
+ pub const Matrix = struct {
+ base: Payload = .{ .tag = .matrix },
+ column_type: Ref,
+ column_count: u32,
+ };
+
+ pub const Image = struct {
+ base: Payload = .{ .tag = .image },
+ sampled_type: Ref,
+ dim: spec.Dim,
+ depth: enum(u2) {
+ no = 0,
+ yes = 1,
+ maybe = 2,
+ },
+ arrayed: bool,
+ multisampled: bool,
+ sampled: enum(u2) {
+ known_at_runtime = 0,
+ with_sampler = 1,
+ without_sampler = 2,
+ },
+ format: spec.ImageFormat,
+ access_qualifier: ?spec.AccessQualifier,
+ };
+
+ pub const SampledImage = struct {
+ base: Payload = .{ .tag = .sampled_image },
+ image_type: Ref,
+ };
+
+ pub const Array = struct {
+ base: Payload = .{ .tag = .array },
+ element_type: Ref,
+ /// Note: Must be emitted as constant, not as literal!
+ length: u32,
+ /// Type has the 'ArrayStride' decoration.
+ /// If zero, no stride is present.
+ array_stride: u32,
+ };
+
+ pub const RuntimeArray = struct {
+ base: Payload = .{ .tag = .runtime_array },
+ element_type: Ref,
+ /// Type has the 'ArrayStride' decoration.
+ /// If zero, no stride is present.
+ array_stride: u32,
+ };
+
+ pub const Struct = struct {
+ base: Payload = .{ .tag = .@"struct" },
+ members: []Member,
+ decorations: StructDecorations,
+
+ /// Extra information for decorations, packed for efficiency. Fields are stored sequentially by
+ /// order of the `members` slice and `MemberDecorations` struct.
+ member_decoration_extra: []u32,
+
+ pub const Member = struct {
+ ty: Ref,
+ offset: u32,
+ decorations: MemberDecorations,
+ };
+
+ pub const StructDecorations = packed struct {
+ /// Type has the 'Block' decoration.
+ block: bool,
+ /// Type has the 'BufferBlock' decoration.
+ buffer_block: bool,
+ /// Type has the 'GLSLShared' decoration.
+ glsl_shared: bool,
+ /// Type has the 'GLSLPacked' decoration.
+ glsl_packed: bool,
+ /// Type has the 'CPacked' decoration.
+ c_packed: bool,
+ };
+
+ pub const MemberDecorations = packed struct {
+ /// Matrix layout for (arrays of) matrices. If this field is not .none,
+ /// then there is also an extra field containing the matrix stride corresponding
+ /// to the 'MatrixStride' decoration.
+ matrix_layout: enum(u2) {
+ /// Member has the 'RowMajor' decoration. The member type
+ /// must be a matrix or an array of matrices.
+ row_major,
+ /// Member has the 'ColMajor' decoration. The member type
+ /// must be a matrix or an array of matrices.
+ col_major,
+ /// Member is not a matrix or array of matrices.
+ none,
+ },
+
+ // Regular decorations, these do not imply extra fields.
+
+ /// Member has the 'NoPerspective' decoration.
+ no_perspective: bool,
+ /// Member has the 'Flat' decoration.
+ flat: bool,
+ /// Member has the 'Patch' decoration.
+ patch: bool,
+ /// Member has the 'Centroid' decoration.
+ centroid: bool,
+ /// Member has the 'Sample' decoration.
+ sample: bool,
+ /// Member has the 'Invariant' decoration.
+ /// Note: requires parent struct to have 'Block'.
+ invariant: bool,
+ /// Member has the 'Volatile' decoration.
+ @"volatile": bool,
+ /// Member has the 'Coherent' decoration.
+ coherent: bool,
+ /// Member has the 'NonWritable' decoration.
+ non_writable: bool,
+ /// Member has the 'NonReadable' decoration.
+ non_readable: bool,
+
+ // The following decorations all imply extra field(s).
+
+ /// Member has the 'BuiltIn' decoration.
+ /// This decoration has an extra field of type `spec.BuiltIn`.
+ /// Note: If any member of a struct has the BuiltIn decoration, all members must have one.
+ /// Note: Each builtin may only be reachable once for a particular entry point.
+ /// Note: The member type may be constrained by a particular built-in, defined in the client API specification.
+ builtin: bool,
+ /// Member has the 'Stream' decoration.
+ /// This member has an extra field of type `u32`.
+ stream: bool,
+ /// Member has the 'Location' decoration.
+ /// This member has an extra field of type `u32`.
+ location: bool,
+ /// Member has the 'Component' decoration.
+ /// This member has an extra field of type `u32`.
+ component: bool,
+ /// Member has the 'XfbBuffer' decoration.
+ /// This member has an extra field of type `u32`.
+ xfb_buffer: bool,
+ /// Member has the 'XfbStride' decoration.
+ /// This member has an extra field of type `u32`.
+ xfb_stride: bool,
+ /// Member has the 'UserSemantic' decoration.
+ /// This member has an extra field of type `[]u8`, which is encoded
+ /// by an `u32` containing the number of chars exactly, and then the string padded to
+ /// a multiple of 4 bytes with zeroes.
+ user_semantic: bool,
+ };
+ };
+
+ pub const Opaque = struct {
+ base: Payload = .{ .tag = .@"opaque" },
+ name: []u8,
+ };
+
+ pub const Pointer = struct {
+ base: Payload = .{ .tag = .pointer },
+ storage_class: spec.StorageClass,
+ child_type: Ref,
+ /// Type has the 'ArrayStride' decoration.
+ /// This is valid for pointers to elements of an array.
+ /// If zero, no stride is present.
+ array_stride: u32,
+ /// Type has the 'Alignment' decoration.
+ alignment: ?u32,
+ /// Type has the 'MaxByteOffset' decoration.
+ max_byte_offset: ?u32,
+ };
+
+ pub const Function = struct {
+ base: Payload = .{ .tag = .function },
+ return_type: Ref,
+ parameters: []Ref,
+ };
+
+ pub const Pipe = struct {
+ base: Payload = .{ .tag = .pipe },
+ qualifier: spec.AccessQualifier,
+ };
+ };
+};
src/codegen/spirv.zig
@@ -21,8 +21,8 @@ const IdResultType = spec.IdResultType;
const SpvModule = @import("spirv/Module.zig");
const SpvSection = @import("spirv/Section.zig");
+const SpvType = @import("spirv/type.zig").Type;
-const TypeCache = std.HashMapUnmanaged(Type, IdResultType, Type.HashContext64, std.hash_map.default_max_load_percentage);
const InstMap = std.AutoHashMapUnmanaged(Air.Inst.Index, IdRef);
const IncomingBlock = struct {
@@ -61,10 +61,6 @@ pub const DeclGen = struct {
/// A counter to keep track of how many `arg` instructions we've seen yet.
next_arg_index: u32,
- /// A cache for zig types to prevent having to re-process a particular type. This structure is kept around
- /// after a call to `gen` so that they don't have to be re-resolved for different decls.
- type_cache: TypeCache = .{},
-
/// A map keeping track of which instruction generated which result-id.
inst_results: InstMap = .{},
@@ -159,7 +155,6 @@ pub const DeclGen = struct {
self.liveness = liveness;
self.args.items.len = 0;
self.next_arg_index = 0;
- // Note: don't clear type_cache.
self.inst_results.clearRetainingCapacity();
self.blocks.clearRetainingCapacity();
self.current_block_label_id = undefined;
@@ -177,7 +172,6 @@ pub const DeclGen = struct {
/// Free resources owned by the DeclGen.
pub fn deinit(self: *DeclGen) void {
self.args.deinit(self.spv.gpa);
- self.type_cache.deinit(self.spv.gpa);
self.inst_results.deinit(self.spv.gpa);
self.blocks.deinit(self.spv.gpa);
self.code.deinit(self.spv.gpa);
@@ -220,7 +214,7 @@ pub const DeclGen = struct {
/// Note that there is no such thing as nested blocks like in ZIR or AIR, so we don't need to
/// keep track of the previous block.
fn beginSpvBlock(self: *DeclGen, label_id: IdResult) !void {
- try self.code.emit(self.spv.gpa, .OpLabel, .{.id_result = label_id});
+ try self.code.emit(self.spv.gpa, .OpLabel, .{ .id_result = label_id });
self.current_block_label_id = label_id.toRef();
}
@@ -317,9 +311,9 @@ pub const DeclGen = struct {
};
},
// As of yet, there is no vector support in the self-hosted compiler.
- .Vector => self.fail("TODO: SPIR-V backend: implement arithmeticTypeInfo for Vector", .{}),
+ .Vector => self.todo("implement arithmeticTypeInfo for Vector", .{}),
// TODO: For which types is this the case?
- else => self.fail("TODO: SPIR-V backend: implement arithmeticTypeInfo for {}", .{ty}),
+ else => self.todo("implement arithmeticTypeInfo for {}", .{ty}),
};
}
@@ -329,7 +323,7 @@ pub const DeclGen = struct {
const target = self.getTarget();
const section = &self.spv.sections.types_globals_constants;
const result_id = self.spv.allocId();
- const result_type_id = try self.genType(ty);
+ const result_type_id = try self.resolveTypeId(ty);
if (val.isUndef()) {
try section.emit(self.spv.gpa, .OpUndef, .{ .id_result_type = result_type_id, .id_result = result_id });
@@ -341,7 +335,7 @@ pub const DeclGen = struct {
const int_info = ty.intInfo(target);
const backing_bits = self.backingIntBits(int_info.bits) orelse {
// Integers too big for any native type are represented as "composite integers": An array of largestSupportedIntBits.
- return self.fail("TODO: SPIR-V backend: implement composite int constants for {}", .{ty});
+ return self.todo("implement composite int constants for {}", .{ty});
};
// We can just use toSignedInt/toUnsignedInt here as it returns u64 - a type large enough to hold any
@@ -354,8 +348,8 @@ pub const DeclGen = struct {
var int_bits = if (ty.isSignedInt()) @bitCast(u64, val.toSignedInt()) else val.toUnsignedInt();
const value: spec.LiteralContextDependentNumber = switch (backing_bits) {
- 1...32 => .{.uint32 = @truncate(u32, int_bits)},
- 33...64 => .{.uint64 = int_bits},
+ 1...32 => .{ .uint32 = @truncate(u32, int_bits) },
+ 33...64 => .{ .uint64 = int_bits },
else => unreachable,
};
@@ -375,14 +369,14 @@ pub const DeclGen = struct {
},
.Float => {
// At this point we are guaranteed that the target floating point type is supported, otherwise the function
- // would have exited at genType(ty).
+ // would have exited at resolveTypeId(ty).
const value: spec.LiteralContextDependentNumber = switch (ty.floatBits(target)) {
// Prevent upcasting to f32 by bitcasting and writing as a uint32.
- 16 => .{.uint32 = @bitCast(u16, val.toFloat(f16))},
- 32 => .{.float32 = val.toFloat(f32)},
- 64 => .{.float64 = val.toFloat(f64)},
- 128 => unreachable, // Filtered out in the call to genType.
+ 16 => .{ .uint32 = @bitCast(u16, val.toFloat(f16)) },
+ 32 => .{ .float32 = val.toFloat(f32) },
+ 64 => .{ .float64 = val.toFloat(f64) },
+ 128 => unreachable, // Filtered out in the call to resolveTypeId.
// TODO: Insert case for long double when the layout for that is determined?
else => unreachable,
};
@@ -394,43 +388,43 @@ pub const DeclGen = struct {
});
},
.Void => unreachable,
- else => return self.fail("TODO: SPIR-V backend: constant generation of type {}", .{ty}),
+ else => return self.todo("constant generation of type {}", .{ty}),
}
return result_id.toRef();
}
- fn genType(self: *DeclGen, ty: Type) Error!IdResultType {
- // We can't use getOrPut here so we can recursively generate types.
- if (self.type_cache.get(ty)) |already_generated| {
- return already_generated;
- }
+ /// Turn a Zig type into a SPIR-V Type, and return its type result-id.
+ fn resolveTypeId(self: *DeclGen, ty: Type) !IdResultType {
+ return self.spv.typeResultId(try self.resolveType(ty));
+ }
+ /// Turn a Zig type into a SPIR-V Type, and return a reference to it.
+ fn resolveType(self: *DeclGen, ty: Type) Error!SpvType.Ref {
const target = self.getTarget();
- const section = &self.spv.sections.types_globals_constants;
- const result_id = self.spv.allocId();
-
- switch (ty.zigTypeTag()) {
- .Void => try section.emit(self.spv.gpa, .OpTypeVoid, .{.id_result = result_id}),
- .Bool => try section.emit(self.spv.gpa, .OpTypeBool, .{.id_result = result_id}),
- .Int => {
+ return switch (ty.zigTypeTag()) {
+ .Void => try self.spv.resolveType(SpvType.initTag(.void)),
+ .Bool => blk: {
+ // TODO: SPIR-V booleans are opaque. For local variables this is fine, but for structs
+ // members we want to use integer types instead.
+ break :blk try self.spv.resolveType(SpvType.initTag(.bool));
+ },
+ .Int => blk: {
const int_info = ty.intInfo(target);
const backing_bits = self.backingIntBits(int_info.bits) orelse {
- // Integers too big for any native type are represented as "composite integers": An array of largestSupportedIntBits.
- return self.fail("TODO: SPIR-V backend: implement composite int {}", .{ty});
+ // TODO: Integers too big for any native type are represented as "composite integers":
+ // An array of largestSupportedIntBits.
+ return self.todo("Implement composite int type {}", .{ty});
};
- // TODO: If backing_bits != int_info.bits, a duplicate type might be generated here.
- try section.emit(self.spv.gpa, .OpTypeInt, .{
- .id_result = result_id,
+ const payload = try self.spv.arena.create(SpvType.Payload.Int);
+ payload.* = .{
.width = backing_bits,
- .signedness = switch (int_info.signedness) {
- .unsigned => @as(spec.LiteralInteger, 0),
- .signed => 1,
- },
- });
+ .signedness = int_info.signedness,
+ };
+ break :blk try self.spv.resolveType(SpvType.initPayload(&payload.base));
},
- .Float => {
+ .Float => blk: {
// We can (and want) not really emulate floating points with other floating point types like with the integer types,
// so if the float is not supported, just return an error.
const bits = ty.floatBits(target);
@@ -446,39 +440,34 @@ pub const DeclGen = struct {
return self.fail("Floating point width of {} bits is not supported for the current SPIR-V feature set", .{bits});
}
- try section.emit(self.spv.gpa, .OpTypeFloat, .{.id_result = result_id, .width = bits});
+ const payload = try self.spv.arena.create(SpvType.Payload.Float);
+ payload.* = .{
+ .width = bits,
+ };
+ break :blk try self.spv.resolveType(SpvType.initPayload(&payload.base));
},
- .Fn => {
+ .Fn => blk: {
// We only support zig-calling-convention functions, no varargs.
if (ty.fnCallingConvention() != .Unspecified)
return self.fail("Unsupported calling convention for SPIR-V", .{});
if (ty.fnIsVarArgs())
- return self.fail("VarArgs unsupported for SPIR-V", .{});
-
- // In order to avoid a temporary here, first generate all the required types and then simply look them up
- // when generating the function type.
- const params = ty.fnParamLen();
- var i: usize = 0;
- while (i < params) : (i += 1) {
- _ = try self.genType(ty.fnParamType(i));
- }
-
- const return_type_id = try self.genType(ty.fnReturnType());
+ return self.fail("VarArgs functions are unsupported for SPIR-V", .{});
- try section.emitRaw(self.spv.gpa, .OpTypeFunction, 2 + @intCast(u16, ty.fnParamLen()));
+ const param_types = try self.spv.arena.alloc(SpvType.Ref, ty.fnParamLen());
+ for (param_types) |*param, i| {
+ param.* = try self.resolveType(ty.fnParamType(i));
+ }
- // result id + result type id + parameter type ids.
- section.writeOperand(IdResult, result_id);
- section.writeOperand(IdResultType, return_type_id);
+ const return_type = try self.resolveType(ty.fnReturnType());
- i = 0;
- while (i < params) : (i += 1) {
- const param_type_id = self.type_cache.get(ty.fnParamType(i)).?;
- section.writeOperand(IdRef, param_type_id.toRef());
- }
+ const payload = try self.spv.arena.create(SpvType.Payload.Function);
+ payload.* = .{ .return_type = return_type, .parameters = param_types };
+ break :blk try self.spv.resolveType(SpvType.initPayload(&payload.base));
+ },
+ .Pointer => {
+ // This type can now be properly implemented, but we still need to implement the storage classes as proper address spaces.
+ return self.todo("Implement type Pointer properly", .{});
},
- // When recursively generating a type, we cannot infer the pointer's storage class. See genPointerType.
- .Pointer => return self.fail("Cannot create pointer with unknown storage class", .{}),
.Vector => {
// Although not 100% the same, Zig vectors map quite neatly to SPIR-V vectors (including many integer and float operations
// which work on them), so simply use those.
@@ -488,23 +477,21 @@ pub const DeclGen = struct {
// is adequate at all for this.
// TODO: Vectors are not yet supported by the self-hosted compiler itself it seems.
- return self.fail("TODO: SPIR-V backend: implement type Vector", .{});
+ return self.todo("Implement type Vector", .{});
},
+
.Null,
.Undefined,
.EnumLiteral,
.ComptimeFloat,
.ComptimeInt,
.Type,
- => unreachable, // Must be const or comptime.
+ => unreachable, // Must be comptime.
.BoundFn => unreachable, // this type will be deleted from the language.
- else => |tag| return self.fail("TODO: SPIR-V backend: implement type {}s", .{tag}),
- }
-
- try self.type_cache.putNoClobber(self.spv.gpa, ty, result_id.toResultType());
- return result_id.toResultType();
+ else => |tag| return self.todo("Implement zig type '{}'", .{tag}),
+ };
}
/// SPIR-V requires pointers to have a storage class (address space), and so we have a special function for that.
@@ -517,7 +504,7 @@ pub const DeclGen = struct {
// TODO: There are many constraints which are ignored for now: We may only create pointers to certain types, and to other types
// if more capabilities are enabled. For example, we may only create pointers to f16 if Float16Buffer is enabled.
// These also relates to the pointer's address space.
- const child_id = try self.genType(ty.elemType());
+ const child_id = try self.resolveTypeId(ty.elemType());
try self.spv.sections.types_globals_constants.emit(self.spv.gpa, .OpTypePointer, .{
.id_result = result_id,
@@ -534,9 +521,9 @@ pub const DeclGen = struct {
if (decl.val.castTag(.function)) |_| {
assert(decl.ty.zigTypeTag() == .Fn);
- const prototype_id = try self.genType(decl.ty);
+ const prototype_id = try self.resolveTypeId(decl.ty);
try self.spv.sections.functions.emit(self.spv.gpa, .OpFunction, .{
- .id_result_type = self.type_cache.get(decl.ty.fnReturnType()).?, // This type should be generated along with the prototype.
+ .id_result_type = try self.resolveTypeId(decl.ty.fnReturnType()),
.id_result = result_id,
.function_control = .{}, // TODO: We can set inline here if the type requires it.
.function_type = prototype_id.toRef(),
@@ -547,7 +534,7 @@ pub const DeclGen = struct {
try self.args.ensureUnusedCapacity(self.spv.gpa, params);
while (i < params) : (i += 1) {
- const param_type_id = self.type_cache.get(decl.ty.fnParamType(i)).?;
+ const param_type_id = try self.resolveTypeId(decl.ty.fnParamType(i));
const arg_result_id = self.spv.allocId();
try self.spv.sections.functions.emit(self.spv.gpa, .OpFunctionParameter, .{
.id_result_type = param_type_id,
@@ -573,7 +560,8 @@ pub const DeclGen = struct {
try self.spv.sections.functions.append(self.spv.gpa, self.code);
try self.spv.sections.functions.emit(self.spv.gpa, .OpFunctionEnd, {});
} else {
- return self.fail("TODO: SPIR-V backend: generate decl type {}", .{decl.ty.zigTypeTag()});
+ // TODO
+ // return self.todo("generate decl type {}", .{decl.ty.zigTypeTag()});
}
}
@@ -622,7 +610,7 @@ pub const DeclGen = struct {
.unreach => return self.airUnreach(),
// zig fmt: on
- else => |tag| return self.fail("TODO: SPIR-V backend: implement AIR tag {s}", .{
+ else => |tag| return self.todo("implement AIR tag {s}", .{
@tagName(tag),
}),
};
@@ -635,7 +623,7 @@ pub const DeclGen = struct {
const lhs_id = try self.resolve(bin_op.lhs);
const rhs_id = try self.resolve(bin_op.rhs);
const result_id = self.spv.allocId();
- const result_type_id = try self.genType(self.air.typeOfIndex(inst));
+ const result_type_id = try self.resolveTypeId(self.air.typeOfIndex(inst));
try self.code.emit(self.spv.gpa, opcode, .{
.id_result_type = result_type_id,
.id_result = result_id,
@@ -654,7 +642,7 @@ pub const DeclGen = struct {
const rhs_id = try self.resolve(bin_op.rhs);
const result_id = self.spv.allocId();
- const result_type_id = try self.genType(ty);
+ const result_type_id = try self.resolveTypeId(ty);
assert(self.air.typeOf(bin_op.lhs).eql(ty));
assert(self.air.typeOf(bin_op.rhs).eql(ty));
@@ -665,10 +653,10 @@ pub const DeclGen = struct {
const opcode_index: usize = switch (info.class) {
.composite_integer => {
- return self.fail("TODO: SPIR-V backend: binary operations for composite integers", .{});
+ return self.todo("binary operations for composite integers", .{});
},
.strange_integer => {
- return self.fail("TODO: SPIR-V backend: binary operations for strange integers", .{});
+ return self.todo("binary operations for strange integers", .{});
},
.integer => switch (info.signedness) {
.signed => @as(usize, 1),
@@ -702,7 +690,7 @@ pub const DeclGen = struct {
const lhs_id = try self.resolve(bin_op.lhs);
const rhs_id = try self.resolve(bin_op.rhs);
const result_id = self.spv.allocId();
- const result_type_id = try self.genType(Type.initTag(.bool));
+ const result_type_id = try self.resolveTypeId(Type.initTag(.bool));
const op_ty = self.air.typeOf(bin_op.lhs);
assert(op_ty.eql(self.air.typeOf(bin_op.rhs)));
@@ -712,10 +700,10 @@ pub const DeclGen = struct {
const opcode_index: usize = switch (info.class) {
.composite_integer => {
- return self.fail("TODO: SPIR-V backend: binary operations for composite integers", .{});
+ return self.todo("binary operations for composite integers", .{});
},
.strange_integer => {
- return self.fail("TODO: SPIR-V backend: comparison for strange integers", .{});
+ return self.todo("comparison for strange integers", .{});
},
.float => 0,
.bool => 1,
@@ -746,7 +734,7 @@ pub const DeclGen = struct {
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
const operand_id = try self.resolve(ty_op.operand);
const result_id = self.spv.allocId();
- const result_type_id = try self.genType(Type.initTag(.bool));
+ const result_type_id = try self.resolveTypeId(Type.initTag(.bool));
try self.code.emit(self.spv.gpa, .OpLogicalNot, .{
.id_result_type = result_type_id,
.id_result = result_id,
@@ -813,9 +801,9 @@ pub const DeclGen = struct {
const result_id = self.spv.allocId();
// TODO: OpPhi is limited in the types that it may produce, such as pointers. Figure out which other types
- // are not allowed to be created from a phi node, and throw an error for those. For now, genType already throws
+ // are not allowed to be created from a phi node, and throw an error for those. For now, resolveTypeId already throws
// an error for pointers.
- const result_type_id = try self.genType(ty);
+ const result_type_id = try self.resolveTypeId(ty);
_ = result_type_id;
try self.code.emitRaw(self.spv.gpa, .OpPhi, 2 + @intCast(u16, incoming_blocks.items.len * 2)); // result type + result + variable/parent...
@@ -838,7 +826,7 @@ pub const DeclGen = struct {
try block.incoming_blocks.append(self.spv.gpa, .{ .src_label_id = self.current_block_label_id, .break_value_id = operand_id });
}
- try self.code.emit(self.spv.gpa, .OpBranch, .{.target_label = block.label_id});
+ try self.code.emit(self.spv.gpa, .OpBranch, .{ .target_label = block.label_id });
}
fn airCondBr(self: *DeclGen, inst: Air.Inst.Index) !void {
@@ -882,7 +870,7 @@ pub const DeclGen = struct {
const operand_id = try self.resolve(ty_op.operand);
const ty = self.air.typeOfIndex(inst);
- const result_type_id = try self.genType(ty);
+ const result_type_id = try self.resolveTypeId(ty);
const result_id = self.spv.allocId();
const access = spec.MemoryAccess.Extended{
@@ -906,13 +894,13 @@ pub const DeclGen = struct {
const loop_label_id = self.spv.allocId();
// Jump to the loop entry point
- try self.code.emit(self.spv.gpa, .OpBranch, .{.target_label = loop_label_id.toRef()});
+ try self.code.emit(self.spv.gpa, .OpBranch, .{ .target_label = loop_label_id.toRef() });
// TODO: Look into OpLoopMerge.
try self.beginSpvBlock(loop_label_id);
try self.genBody(body);
- try self.code.emit(self.spv.gpa, .OpBranch, .{.target_label = loop_label_id.toRef()});
+ try self.code.emit(self.spv.gpa, .OpBranch, .{ .target_label = loop_label_id.toRef() });
}
fn airRet(self: *DeclGen, inst: Air.Inst.Index) !void {
@@ -920,7 +908,7 @@ pub const DeclGen = struct {
const operand_ty = self.air.typeOf(operand);
if (operand_ty.hasRuntimeBits()) {
const operand_id = try self.resolve(operand);
- try self.code.emit(self.spv.gpa, .OpReturnValue, .{.value = operand_id});
+ try self.code.emit(self.spv.gpa, .OpReturnValue, .{ .value = operand_id });
} else {
try self.code.emit(self.spv.gpa, .OpReturn, {});
}