authorgravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2022-11-26 12:23:07+01:00
committergravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2023-04-09 01:51:48+02:00
log3eafe3033ef83e5b34e3ccbd6e803c7a046df390
treee62c8d20a421c95b7d145ea3da1d3a5c2cc0602d
parent5826a8a0640d79de83be131434ec91315e75087b
signaturelock-open Commit is signed but in an unrecognized format.

spirv: improve storage efficiency for integer and float types

In practice there are only a few variations of these types allowed, so it kind-of makes sense to write them all out. Because the types are hashed this does not actually save all that many bytes in the long run, though. Perhaps some of these types should be pre-registered?

4 files changed, 181 insertions(+), 54 deletions(-)

src/codegen/spirv.zig+2-11
...@@ -451,12 +451,7 @@ pub const DeclGen = struct {...@@ -451,12 +451,7 @@ pub const DeclGen = struct {
451 return self.todo("Implement {s} composite int type of {} bits", .{ @tagName(signedness), bits });451 return self.todo("Implement {s} composite int type of {} bits", .{ @tagName(signedness), bits });
452 };452 };
453453
454 const payload = try self.spv.arena.create(SpvType.Payload.Int);454 return try self.spv.resolveType(try SpvType.int(self.spv.arena, signedness, backing_bits));
455 payload.* = .{
456 .width = backing_bits,
457 .signedness = signedness,
458 };
459 return try self.spv.resolveType(SpvType.initPayload(&payload.base));
460 }455 }
461456
462 /// Turn a Zig type into a SPIR-V Type, and return a reference to it.457 /// Turn a Zig type into a SPIR-V Type, and return a reference to it.
...@@ -495,11 +490,7 @@ pub const DeclGen = struct {...@@ -495,11 +490,7 @@ pub const DeclGen = struct {
495 return self.fail("Floating point width of {} bits is not supported for the current SPIR-V feature set", .{bits});490 return self.fail("Floating point width of {} bits is not supported for the current SPIR-V feature set", .{bits});
496 }491 }
497492
498 const payload = try self.spv.arena.create(SpvType.Payload.Float);493 return try self.spv.resolveType(SpvType.float(bits));
499 payload.* = .{
500 .width = bits,
501 };
502 return try self.spv.resolveType(SpvType.initPayload(&payload.base));
503 },494 },
504 .Fn => {495 .Fn => {
505 // TODO: Put this somewhere in Sema.zig496 // TODO: Put this somewhere in Sema.zig
src/codegen/spirv/Assembler.zig+24-26
...@@ -266,27 +266,28 @@ fn processTypeInstruction(self: *Assembler) !AsmValue {...@@ -266,27 +266,28 @@ fn processTypeInstruction(self: *Assembler) !AsmValue {
266 .OpTypeVoid => SpvType.initTag(.void),266 .OpTypeVoid => SpvType.initTag(.void),
267 .OpTypeBool => SpvType.initTag(.bool),267 .OpTypeBool => SpvType.initTag(.bool),
268 .OpTypeInt => blk: {268 .OpTypeInt => blk: {
269 const payload = try self.spv.arena.create(SpvType.Payload.Int);
270 const signedness: std.builtin.Signedness = switch (operands[2].literal32) {269 const signedness: std.builtin.Signedness = switch (operands[2].literal32) {
271 0 => .unsigned,270 0 => .unsigned,
272 1 => .signed,271 1 => .signed,
273 else => {272 else => {
274 // TODO: Improve source location.273 // TODO: Improve source location.
275 return self.fail(0, "'{}' is not a valid signedness (expected 0 or 1)", .{operands[2].literal32});274 return self.fail(0, "{} is not a valid signedness (expected 0 or 1)", .{operands[2].literal32});
276 },275 },
277 };276 };
278 payload.* = .{277 const width = std.math.cast(u16, operands[1].literal32) orelse {
279 .width = operands[1].literal32,278 return self.fail(0, "int type of {} bits is too large", .{operands[1].literal32});
280 .signedness = signedness,
281 };279 };
282 break :blk SpvType.initPayload(&payload.base);280 break :blk try SpvType.int(self.spv.arena, signedness, width);
283 },281 },
284 .OpTypeFloat => blk: {282 .OpTypeFloat => blk: {
285 const payload = try self.spv.arena.create(SpvType.Payload.Float);283 const bits = operands[1].literal32;
286 payload.* = .{284 switch (bits) {
287 .width = operands[1].literal32,285 16, 32, 64 => {},
288 };286 else => {
289 break :blk SpvType.initPayload(&payload.base);287 return self.fail(0, "{} is not a valid bit count for floats (expected 16, 32 or 64)", .{bits});
288 },
289 }
290 break :blk SpvType.float(@intCast(u16, bits));
290 },291 },
291 .OpTypeVector => blk: {292 .OpTypeVector => blk: {
292 const payload = try self.spv.arena.create(SpvType.Payload.Vector);293 const payload = try self.spv.arena.create(SpvType.Payload.Vector);
...@@ -754,21 +755,18 @@ fn parseContextDependentNumber(self: *Assembler) !void {...@@ -754,21 +755,18 @@ fn parseContextDependentNumber(self: *Assembler) !void {
754 const tok = self.currentToken();755 const tok = self.currentToken();
755 const result_type_ref = try self.resolveTypeRef(self.inst.operands.items[0].ref_id);756 const result_type_ref = try self.resolveTypeRef(self.inst.operands.items[0].ref_id);
756 const result_type = self.spv.type_cache.keys()[@enumToInt(result_type_ref)];757 const result_type = self.spv.type_cache.keys()[@enumToInt(result_type_ref)];
757 switch (result_type.tag()) {758 if (result_type.isInt()) {
758 .int => {759 try self.parseContextDependentInt(result_type.intSignedness(), result_type.intFloatBits());
759 const int = result_type.castTag(.int).?;760 } else if (result_type.isFloat()) {
760 try self.parseContextDependentInt(int.signedness, int.width);761 const width = result_type.intFloatBits();
761 },762 switch (width) {
762 .float => {763 16 => try self.parseContextDependentFloat(16),
763 const width = result_type.castTag(.float).?.width;764 32 => try self.parseContextDependentFloat(32),
764 switch (width) {765 64 => try self.parseContextDependentFloat(64),
765 16 => try self.parseContextDependentFloat(16),766 else => return self.fail(tok.start, "cannot parse {}-bit float literal", .{width}),
766 32 => try self.parseContextDependentFloat(32),767 }
767 64 => try self.parseContextDependentFloat(64),768 } else {
768 else => return self.fail(tok.start, "cannot parse {}-bit float literal", .{width}),769 return self.fail(tok.start, "cannot parse literal constant {s}", .{@tagName(result_type.tag())});
769 }
770 },
771 else => return self.fail(tok.start, "cannot parse literal constant {s}", .{@tagName(result_type.tag())}),
772 }770 }
773}771}
774772
src/codegen/spirv/Module.zig+14-5
...@@ -250,21 +250,30 @@ pub fn emitType(self: *Module, ty: Type) !IdResultType {...@@ -250,21 +250,30 @@ pub fn emitType(self: *Module, ty: Type) !IdResultType {
250 switch (ty.tag()) {250 switch (ty.tag()) {
251 .void => try types.emit(self.gpa, .OpTypeVoid, result_id_operand),251 .void => try types.emit(self.gpa, .OpTypeVoid, result_id_operand),
252 .bool => try types.emit(self.gpa, .OpTypeBool, result_id_operand),252 .bool => try types.emit(self.gpa, .OpTypeBool, result_id_operand),
253 .int => {253 .u8,
254 const signedness: spec.LiteralInteger = switch (ty.payload(.int).signedness) {254 .u16,
255 .u32,
256 .u64,
257 .i8,
258 .i16,
259 .i32,
260 .i64,
261 .int,
262 => {
263 const signedness: spec.LiteralInteger = switch (ty.intSignedness()) {
255 .unsigned => 0,264 .unsigned => 0,
256 .signed => 1,265 .signed => 1,
257 };266 };
258267
259 try types.emit(self.gpa, .OpTypeInt, .{268 try types.emit(self.gpa, .OpTypeInt, .{
260 .id_result = result_id,269 .id_result = result_id,
261 .width = ty.payload(.int).width,270 .width = ty.intFloatBits(),
262 .signedness = signedness,271 .signedness = signedness,
263 });272 });
264 },273 },
265 .float => try types.emit(self.gpa, .OpTypeFloat, .{274 .f16, .f32, .f64 => try types.emit(self.gpa, .OpTypeFloat, .{
266 .id_result = result_id,275 .id_result = result_id,
267 .width = ty.payload(.float).width,276 .width = ty.intFloatBits(),
268 }),277 }),
269 .vector => try types.emit(self.gpa, .OpTypeVector, .{278 .vector => try types.emit(self.gpa, .OpTypeVector, .{
270 .id_result = result_id,279 .id_result = result_id,
src/codegen/spirv/type.zig+141-12
...@@ -3,6 +3,8 @@...@@ -3,6 +3,8 @@
33
4const std = @import("std");4const std = @import("std");
5const assert = std.debug.assert;5const assert = std.debug.assert;
6const Signedness = std.builtin.Signedness;
7const Allocator = std.mem.Allocator;
68
7const spec = @import("spec.zig");9const spec = @import("spec.zig");
810
...@@ -23,6 +25,41 @@ pub const Type = extern union {...@@ -23,6 +25,41 @@ pub const Type = extern union {
23 return .{ .ptr_otherwise = pl };25 return .{ .ptr_otherwise = pl };
24 }26 }
2527
28 pub fn int(arena: Allocator, signedness: Signedness, bits: u16) !Type {
29 const bits_and_signedness = switch (signedness) {
30 .signed => -@as(i32, bits),
31 .unsigned => @as(i32, bits),
32 };
33
34 return switch (bits_and_signedness) {
35 8 => initTag(.u8),
36 16 => initTag(.u16),
37 32 => initTag(.u32),
38 64 => initTag(.u64),
39 -8 => initTag(.i8),
40 -16 => initTag(.i16),
41 -32 => initTag(.i32),
42 -64 => initTag(.i64),
43 else => {
44 const int_payload = try arena.create(Payload.Int);
45 int_payload.* = .{
46 .width = bits,
47 .signedness = signedness,
48 };
49 return initPayload(&int_payload.base);
50 },
51 };
52 }
53
54 pub fn float(bits: u16) Type {
55 return switch (bits) {
56 16 => initTag(.f16),
57 32 => initTag(.f32),
58 64 => initTag(.f64),
59 else => unreachable, // Enable more types if required.
60 };
61 }
62
26 pub fn tag(self: Type) Tag {63 pub fn tag(self: Type) Tag {
27 if (@enumToInt(self.tag_if_small_enough) < Tag.no_payload_count) {64 if (@enumToInt(self.tag_if_small_enough) < Tag.no_payload_count) {
28 return self.tag_if_small_enough;65 return self.tag_if_small_enough;
...@@ -80,9 +117,19 @@ pub const Type = extern union {...@@ -80,9 +117,19 @@ pub const Type = extern union {
80 .queue,117 .queue,
81 .pipe_storage,118 .pipe_storage,
82 .named_barrier,119 .named_barrier,
120 .u8,
121 .u16,
122 .u32,
123 .u64,
124 .i8,
125 .i16,
126 .i32,
127 .i64,
128 .f16,
129 .f32,
130 .f64,
83 => return true,131 => return true,
84 .int,132 .int,
85 .float,
86 .vector,133 .vector,
87 .matrix,134 .matrix,
88 .sampled_image,135 .sampled_image,
...@@ -132,6 +179,17 @@ pub const Type = extern union {...@@ -132,6 +179,17 @@ pub const Type = extern union {
132 .queue,179 .queue,
133 .pipe_storage,180 .pipe_storage,
134 .named_barrier,181 .named_barrier,
182 .u8,
183 .u16,
184 .u32,
185 .u64,
186 .i8,
187 .i16,
188 .i32,
189 .i64,
190 .f16,
191 .f32,
192 .f64,
135 => {},193 => {},
136 else => self.hashPayload(@field(Tag, field.name), &hasher),194 else => self.hashPayload(@field(Tag, field.name), &hasher),
137 }195 }
...@@ -185,6 +243,53 @@ pub const Type = extern union {...@@ -185,6 +243,53 @@ pub const Type = extern union {
185 };243 };
186 }244 }
187245
246 pub fn isInt(self: Type) bool {
247 return switch (self.tag()) {
248 .u8,
249 .u16,
250 .u32,
251 .u64,
252 .i8,
253 .i16,
254 .i32,
255 .i64,
256 .int,
257 => true,
258 else => false,
259 };
260 }
261
262 pub fn isFloat(self: Type) bool {
263 return switch (self.tag()) {
264 .f16, .f32, .f64 => true,
265 else => false,
266 };
267 }
268
269 /// Returns the number of bits that make up an int or float type.
270 /// Asserts type is either int or float.
271 pub fn intFloatBits(self: Type) u16 {
272 return switch (self.tag()) {
273 .u8, .i8 => 8,
274 .u16, .i16, .f16 => 16,
275 .u32, .i32, .f32 => 32,
276 .u64, .i64, .f64 => 64,
277 .int => self.payload(.int).width,
278 else => unreachable,
279 };
280 }
281
282 /// Returns the signedness of an integer type.
283 /// Asserts that the type is an int.
284 pub fn intSignedness(self: Type) Signedness {
285 return switch (self.tag()) {
286 .u8, .u16, .u32, .u64 => .unsigned,
287 .i8, .i16, .i32, .i64 => .signed,
288 .int => self.payload(.int).signedness,
289 else => unreachable,
290 };
291 }
292
188 pub const Tag = enum(usize) {293 pub const Tag = enum(usize) {
189 void,294 void,
190 bool,295 bool,
...@@ -195,10 +300,20 @@ pub const Type = extern union {...@@ -195,10 +300,20 @@ pub const Type = extern union {
195 queue,300 queue,
196 pipe_storage,301 pipe_storage,
197 named_barrier,302 named_barrier,
303 u8,
304 u16,
305 u32,
306 u64,
307 i8,
308 i16,
309 i32,
310 i64,
311 f16,
312 f32,
313 f64,
198314
199 // After this, the tag requires a payload.315 // After this, the tag requires a payload.
200 int,316 int,
201 float,
202 vector,317 vector,
203 matrix,318 matrix,
204 image,319 image,
...@@ -211,14 +326,33 @@ pub const Type = extern union {...@@ -211,14 +326,33 @@ pub const Type = extern union {
211 function,326 function,
212 pipe,327 pipe,
213328
214 pub const last_no_payload_tag = Tag.named_barrier;329 pub const last_no_payload_tag = Tag.f64;
215 pub const no_payload_count = @enumToInt(last_no_payload_tag) + 1;330 pub const no_payload_count = @enumToInt(last_no_payload_tag) + 1;
216331
217 pub fn Type(comptime t: Tag) type {332 pub fn Type(comptime t: Tag) type {
218 return switch (t) {333 return switch (t) {
219 .void, .bool, .sampler, .event, .device_event, .reserve_id, .queue, .pipe_storage, .named_barrier => @compileError("Type Tag " ++ @tagName(t) ++ " has no payload"),334 .void,
335 .bool,
336 .sampler,
337 .event,
338 .device_event,
339 .reserve_id,
340 .queue,
341 .pipe_storage,
342 .named_barrier,
343 .u8,
344 .u16,
345 .u32,
346 .u64,
347 .i8,
348 .i16,
349 .i32,
350 .i64,
351 .f16,
352 .f32,
353 .f64,
354 => @compileError("Type Tag " ++ @tagName(t) ++ " has no payload"),
220 .int => Payload.Int,355 .int => Payload.Int,
221 .float => Payload.Float,
222 .vector => Payload.Vector,356 .vector => Payload.Vector,
223 .matrix => Payload.Matrix,357 .matrix => Payload.Matrix,
224 .image => Payload.Image,358 .image => Payload.Image,
...@@ -239,13 +373,8 @@ pub const Type = extern union {...@@ -239,13 +373,8 @@ pub const Type = extern union {
239373
240 pub const Int = struct {374 pub const Int = struct {
241 base: Payload = .{ .tag = .int },375 base: Payload = .{ .tag = .int },
242 width: u32,376 width: u16,
243 signedness: std.builtin.Signedness,377 signedness: Signedness,
244 };
245
246 pub const Float = struct {
247 base: Payload = .{ .tag = .float },
248 width: u32,
249 };378 };
250379
251 pub const Vector = struct {380 pub const Vector = struct {