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 {
451451 return self.todo("Implement {s} composite int type of {} bits", .{ @tagName(signedness), bits });
452452 };
453453
454 const payload = try self.spv.arena.create(SpvType.Payload.Int);
455 payload.* = .{
456 .width = backing_bits,
457 .signedness = signedness,
458 };
459 return try self.spv.resolveType(SpvType.initPayload(&payload.base));
454 return try self.spv.resolveType(try SpvType.int(self.spv.arena, signedness, backing_bits));
460455 }
461456
462457 /// Turn a Zig type into a SPIR-V Type, and return a reference to it.
......@@ -495,11 +490,7 @@ pub const DeclGen = struct {
495490 return self.fail("Floating point width of {} bits is not supported for the current SPIR-V feature set", .{bits});
496491 }
497492
498 const payload = try self.spv.arena.create(SpvType.Payload.Float);
499 payload.* = .{
500 .width = bits,
501 };
502 return try self.spv.resolveType(SpvType.initPayload(&payload.base));
493 return try self.spv.resolveType(SpvType.float(bits));
503494 },
504495 .Fn => {
505496 // TODO: Put this somewhere in Sema.zig
src/codegen/spirv/Assembler.zig+24-26
......@@ -266,27 +266,28 @@ fn processTypeInstruction(self: *Assembler) !AsmValue {
266266 .OpTypeVoid => SpvType.initTag(.void),
267267 .OpTypeBool => SpvType.initTag(.bool),
268268 .OpTypeInt => blk: {
269 const payload = try self.spv.arena.create(SpvType.Payload.Int);
270269 const signedness: std.builtin.Signedness = switch (operands[2].literal32) {
271270 0 => .unsigned,
272271 1 => .signed,
273272 else => {
274273 // 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});
276275 },
277276 };
278 payload.* = .{
279 .width = operands[1].literal32,
280 .signedness = signedness,
277 const width = std.math.cast(u16, operands[1].literal32) orelse {
278 return self.fail(0, "int type of {} bits is too large", .{operands[1].literal32});
281279 };
282 break :blk SpvType.initPayload(&payload.base);
280 break :blk try SpvType.int(self.spv.arena, signedness, width);
283281 },
284282 .OpTypeFloat => blk: {
285 const payload = try self.spv.arena.create(SpvType.Payload.Float);
286 payload.* = .{
287 .width = operands[1].literal32,
288 };
289 break :blk SpvType.initPayload(&payload.base);
283 const bits = operands[1].literal32;
284 switch (bits) {
285 16, 32, 64 => {},
286 else => {
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));
290291 },
291292 .OpTypeVector => blk: {
292293 const payload = try self.spv.arena.create(SpvType.Payload.Vector);
......@@ -754,21 +755,18 @@ fn parseContextDependentNumber(self: *Assembler) !void {
754755 const tok = self.currentToken();
755756 const result_type_ref = try self.resolveTypeRef(self.inst.operands.items[0].ref_id);
756757 const result_type = self.spv.type_cache.keys()[@enumToInt(result_type_ref)];
757 switch (result_type.tag()) {
758 .int => {
759 const int = result_type.castTag(.int).?;
760 try self.parseContextDependentInt(int.signedness, int.width);
761 },
762 .float => {
763 const width = result_type.castTag(.float).?.width;
764 switch (width) {
765 16 => try self.parseContextDependentFloat(16),
766 32 => try self.parseContextDependentFloat(32),
767 64 => try self.parseContextDependentFloat(64),
768 else => return self.fail(tok.start, "cannot parse {}-bit float literal", .{width}),
769 }
770 },
771 else => return self.fail(tok.start, "cannot parse literal constant {s}", .{@tagName(result_type.tag())}),
758 if (result_type.isInt()) {
759 try self.parseContextDependentInt(result_type.intSignedness(), result_type.intFloatBits());
760 } else if (result_type.isFloat()) {
761 const width = result_type.intFloatBits();
762 switch (width) {
763 16 => try self.parseContextDependentFloat(16),
764 32 => try self.parseContextDependentFloat(32),
765 64 => try self.parseContextDependentFloat(64),
766 else => return self.fail(tok.start, "cannot parse {}-bit float literal", .{width}),
767 }
768 } else {
769 return self.fail(tok.start, "cannot parse literal constant {s}", .{@tagName(result_type.tag())});
772770 }
773771}
774772
src/codegen/spirv/Module.zig+14-5
......@@ -250,21 +250,30 @@ pub fn emitType(self: *Module, ty: Type) !IdResultType {
250250 switch (ty.tag()) {
251251 .void => try types.emit(self.gpa, .OpTypeVoid, result_id_operand),
252252 .bool => try types.emit(self.gpa, .OpTypeBool, result_id_operand),
253 .int => {
254 const signedness: spec.LiteralInteger = switch (ty.payload(.int).signedness) {
253 .u8,
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()) {
255264 .unsigned => 0,
256265 .signed => 1,
257266 };
258267
259268 try types.emit(self.gpa, .OpTypeInt, .{
260269 .id_result = result_id,
261 .width = ty.payload(.int).width,
270 .width = ty.intFloatBits(),
262271 .signedness = signedness,
263272 });
264273 },
265 .float => try types.emit(self.gpa, .OpTypeFloat, .{
274 .f16, .f32, .f64 => try types.emit(self.gpa, .OpTypeFloat, .{
266275 .id_result = result_id,
267 .width = ty.payload(.float).width,
276 .width = ty.intFloatBits(),
268277 }),
269278 .vector => try types.emit(self.gpa, .OpTypeVector, .{
270279 .id_result = result_id,
src/codegen/spirv/type.zig+141-12
......@@ -3,6 +3,8 @@
33
44const std = @import("std");
55const assert = std.debug.assert;
6const Signedness = std.builtin.Signedness;
7const Allocator = std.mem.Allocator;
68
79const spec = @import("spec.zig");
810
......@@ -23,6 +25,41 @@ pub const Type = extern union {
2325 return .{ .ptr_otherwise = pl };
2426 }
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
2663 pub fn tag(self: Type) Tag {
2764 if (@enumToInt(self.tag_if_small_enough) < Tag.no_payload_count) {
2865 return self.tag_if_small_enough;
......@@ -80,9 +117,19 @@ pub const Type = extern union {
80117 .queue,
81118 .pipe_storage,
82119 .named_barrier,
120 .u8,
121 .u16,
122 .u32,
123 .u64,
124 .i8,
125 .i16,
126 .i32,
127 .i64,
128 .f16,
129 .f32,
130 .f64,
83131 => return true,
84132 .int,
85 .float,
86133 .vector,
87134 .matrix,
88135 .sampled_image,
......@@ -132,6 +179,17 @@ pub const Type = extern union {
132179 .queue,
133180 .pipe_storage,
134181 .named_barrier,
182 .u8,
183 .u16,
184 .u32,
185 .u64,
186 .i8,
187 .i16,
188 .i32,
189 .i64,
190 .f16,
191 .f32,
192 .f64,
135193 => {},
136194 else => self.hashPayload(@field(Tag, field.name), &hasher),
137195 }
......@@ -185,6 +243,53 @@ pub const Type = extern union {
185243 };
186244 }
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
188293 pub const Tag = enum(usize) {
189294 void,
190295 bool,
......@@ -195,10 +300,20 @@ pub const Type = extern union {
195300 queue,
196301 pipe_storage,
197302 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
199315 // After this, the tag requires a payload.
200316 int,
201 float,
202317 vector,
203318 matrix,
204319 image,
......@@ -211,14 +326,33 @@ pub const Type = extern union {
211326 function,
212327 pipe,
213328
214 pub const last_no_payload_tag = Tag.named_barrier;
329 pub const last_no_payload_tag = Tag.f64;
215330 pub const no_payload_count = @enumToInt(last_no_payload_tag) + 1;
216331
217332 pub fn Type(comptime t: Tag) type {
218333 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"),
220355 .int => Payload.Int,
221 .float => Payload.Float,
222356 .vector => Payload.Vector,
223357 .matrix => Payload.Matrix,
224358 .image => Payload.Image,
......@@ -239,13 +373,8 @@ pub const Type = extern union {
239373
240374 pub const Int = struct {
241375 base: Payload = .{ .tag = .int },
242 width: u32,
243 signedness: std.builtin.Signedness,
244 };
245
246 pub const Float = struct {
247 base: Payload = .{ .tag = .float },
248 width: u32,
376 width: u16,
377 signedness: Signedness,
249378 };
250379
251380 pub const Vector = struct {