authorgravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2022-01-21 15:05:02+01:00
committergravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2022-01-28 14:38:57+01:00
logff042e800662b7407da38aab3e5b163819aba61d
treebfe505d19b373ebbb9f49f1b5be2f0c9d585d71d
parent0e6d2184cacf2dd1fad7508b2f9ae99d78763148

spirv: improve generator

The spirv spec generator now also generates some support information: Opcode gains a function to query a Zig type representing the operands of the opcode. The idea is that this will enable a richer interface for emitting spirv instructions.

1 files changed, 427 insertions(+), 55 deletions(-)

tools/gen_spirv_spec.zig+427-55
......@@ -1,5 +1,8 @@
11const std = @import("std");
22const g = @import("spirv/grammar.zig");
3const Allocator = std.mem.Allocator;
4
5const ExtendedStructSet = std.StringHashMap(void);
36
47pub fn main() !void {
58 var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
......@@ -20,101 +23,308 @@ pub fn main() !void {
2023 var tokens = std.json.TokenStream.init(spec);
2124 var registry = try std.json.parse(g.Registry, &tokens, .{ .allocator = allocator });
2225
26 const core_reg = switch (registry) {
27 .core => |core_reg| core_reg,
28 .extension => return error.TODOSpirVExtensionSpec,
29 };
30
2331 var bw = std.io.bufferedWriter(std.io.getStdOut().writer());
24 try render(bw.writer(), registry);
32 try render(bw.writer(), allocator, core_reg);
2533 try bw.flush();
2634}
2735
28fn render(writer: anytype, registry: g.Registry) !void {
36/// Returns a set with types that require an extra struct for the `Instruction` interface
37/// to the spir-v spec, or whether the original type can be used.
38fn extendedStructs(
39 arena: Allocator,
40 kinds: []const g.OperandKind,
41) !ExtendedStructSet {
42 var map = ExtendedStructSet.init(arena);
43 try map.ensureTotalCapacity(@intCast(u32, kinds.len));
44
45 for (kinds) |kind| {
46 const enumerants = kind.enumerants orelse continue;
47
48 for (enumerants) |enumerant| {
49 if (enumerant.parameters.len > 0) {
50 break;
51 }
52 } else continue;
53
54 map.putAssumeCapacity(kind.kind, {});
55 }
56
57 return map;
58}
59
60// Return a score for a particular priority. Duplicate instruction/operand enum values are
61// removed by picking the tag with the lowest score to keep, and by making an alias for the
62// other. Note that the tag does not need to be just a tag at this point, in which case it
63// gets the lowest score automatically anyway.
64fn tagPriorityScore(tag: []const u8) usize {
65 if (tag.len == 0) {
66 return 1;
67 } else if (std.mem.eql(u8, tag, "EXT")) {
68 return 2;
69 } else if (std.mem.eql(u8, tag, "KHR")) {
70 return 3;
71 } else {
72 return 4;
73 }
74}
75
76fn render(writer: anytype, allocator: Allocator, registry: g.CoreRegistry) !void {
2977 try writer.writeAll(
3078 \\//! This file is auto-generated by tools/gen_spirv_spec.zig.
3179 \\
3280 \\const Version = @import("std").builtin.Version;
3381 \\
82 \\pub const Word = u32;
83 \\pub const IdResultType = struct{
84 \\ id: Word,
85 \\ pub fn toRef(self: IdResultType) IdRef {
86 \\ return .{.id = self.id};
87 \\ }
88 \\};
89 \\pub const IdResult = struct{
90 \\ id: Word,
91 \\ pub fn toRef(self: IdResult) IdRef {
92 \\ return .{.id = self.id};
93 \\ }
94 \\ pub fn toResultType(self: IdResult) IdResultType {
95 \\ return .{.id = self.id};
96 \\ }
97 \\};
98 \\pub const IdRef = struct{ id: Word };
99 \\
100 \\pub const IdMemorySemantics = IdRef;
101 \\pub const IdScope = IdRef;
102 \\
103 \\pub const LiteralInteger = Word;
104 \\pub const LiteralString = []const u8;
105 \\pub const LiteralContextDependentNumber = union(enum) {
106 \\ int32: i32,
107 \\ uint32: u32,
108 \\ int64: i64,
109 \\ uint64: u64,
110 \\ float32: f32,
111 \\ float64: f64,
112 \\};
113 \\pub const LiteralExtInstInteger = struct{ inst: Word };
114 \\pub const LiteralSpecConstantOpInteger = struct { opcode: Opcode };
115 \\pub const PairLiteralIntegerIdRef = struct { value: LiteralInteger, label: IdRef };
116 \\pub const PairIdRefLiteralInteger = struct { target: IdRef, member: LiteralInteger };
117 \\pub const PairIdRefIdRef = [2]IdRef;
118 \\
119 \\
34120 );
35121
36 switch (registry) {
37 .core => |core_reg| {
38 try writer.print(
39 \\pub const version = Version{{ .major = {}, .minor = {}, .patch = {} }};
40 \\pub const magic_number: u32 = {s};
41 \\
42 ,
43 .{ core_reg.major_version, core_reg.minor_version, core_reg.revision, core_reg.magic_number },
44 );
45 try renderOpcodes(writer, core_reg.instructions);
46 try renderOperandKinds(writer, core_reg.operand_kinds);
47 },
48 .extension => |ext_reg| {
49 try writer.print(
50 \\pub const version = Version{{ .major = {}, .minor = 0, .patch = {} }};
51 \\
52 ,
53 .{ ext_reg.version, ext_reg.revision },
54 );
55 try renderOpcodes(writer, ext_reg.instructions);
56 try renderOperandKinds(writer, ext_reg.operand_kinds);
57 },
58 }
122 try writer.print(
123 \\pub const version = Version{{ .major = {}, .minor = {}, .patch = {} }};
124 \\pub const magic_number: Word = {s};
125 \\
126 ,
127 .{ registry.major_version, registry.minor_version, registry.revision, registry.magic_number },
128 );
129 const extended_structs = try extendedStructs(allocator, registry.operand_kinds);
130 try renderOpcodes(writer, allocator, registry.instructions, extended_structs);
131 try renderOperandKinds(writer, allocator, registry.operand_kinds, extended_structs);
59132}
60133
61fn renderOpcodes(writer: anytype, instructions: []const g.Instruction) !void {
62 try writer.writeAll("pub const Opcode = extern enum(u16) {\n");
63 for (instructions) |instr| {
64 try writer.print(" {} = {},\n", .{ std.zig.fmtId(instr.opname), instr.opcode });
134fn renderOpcodes(
135 writer: anytype,
136 allocator: Allocator,
137 instructions: []const g.Instruction,
138 extended_structs: ExtendedStructSet,
139) !void {
140 var inst_map = std.AutoArrayHashMap(u32, usize).init(allocator);
141 try inst_map.ensureTotalCapacity(instructions.len);
142
143 var aliases = std.ArrayList(struct { inst: usize, alias: usize }).init(allocator);
144 try aliases.ensureTotalCapacity(instructions.len);
145
146 for (instructions) |inst, i| {
147 const result = inst_map.getOrPutAssumeCapacity(inst.opcode);
148 if (!result.found_existing) {
149 result.value_ptr.* = i;
150 continue;
151 }
152
153 const existing = instructions[result.value_ptr.*];
154
155 const tag_index = std.mem.indexOfDiff(u8, inst.opname, existing.opname).?;
156 const inst_priority = tagPriorityScore(inst.opname[tag_index..]);
157 const existing_priority = tagPriorityScore(existing.opname[tag_index..]);
158
159 if (inst_priority < existing_priority) {
160 aliases.appendAssumeCapacity(.{ .inst = result.value_ptr.*, .alias = i });
161 result.value_ptr.* = i;
162 } else {
163 aliases.appendAssumeCapacity(.{ .inst = i, .alias = result.value_ptr.* });
164 }
65165 }
66 try writer.writeAll(" _,\n};\n");
166
167 const instructions_indices = inst_map.values();
168
169 try writer.writeAll("pub const Opcode = enum(u16) {\n");
170 for (instructions_indices) |i| {
171 const inst = instructions[i];
172 try writer.print("{} = {},\n", .{ std.zig.fmtId(inst.opname), inst.opcode });
173 }
174
175 try writer.writeByte('\n');
176
177 for (aliases.items) |alias| {
178 try writer.print("pub const {} = Opcode.{};\n", .{
179 std.zig.fmtId(instructions[alias.inst].opname),
180 std.zig.fmtId(instructions[alias.alias].opname),
181 });
182 }
183
184 try writer.writeAll(
185 \\
186 \\pub fn Operands(comptime self: Opcode) type {
187 \\return switch (self) {
188 \\
189 );
190
191 for (instructions_indices) |i| {
192 const inst = instructions[i];
193 try renderOperand(writer, .instruction, inst.opname, inst.operands, extended_structs);
194 }
195 try writer.writeAll("};\n}\n};\n");
196 _ = extended_structs;
67197}
68198
69fn renderOperandKinds(writer: anytype, kinds: []const g.OperandKind) !void {
199fn renderOperandKinds(
200 writer: anytype,
201 allocator: Allocator,
202 kinds: []const g.OperandKind,
203 extended_structs: ExtendedStructSet,
204) !void {
70205 for (kinds) |kind| {
71206 switch (kind.category) {
72 .ValueEnum => try renderValueEnum(writer, kind),
73 .BitEnum => try renderBitEnum(writer, kind),
207 .ValueEnum => try renderValueEnum(writer, allocator, kind, extended_structs),
208 .BitEnum => try renderBitEnum(writer, allocator, kind, extended_structs),
74209 else => {},
75210 }
76211 }
77212}
78213
79fn renderValueEnum(writer: anytype, enumeration: g.OperandKind) !void {
80 try writer.print("pub const {s} = extern enum(u32) {{\n", .{enumeration.kind});
81
214fn renderValueEnum(
215 writer: anytype,
216 allocator: Allocator,
217 enumeration: g.OperandKind,
218 extended_structs: ExtendedStructSet,
219) !void {
82220 const enumerants = enumeration.enumerants orelse return error.InvalidRegistry;
83 for (enumerants) |enumerant| {
221
222 var enum_map = std.AutoArrayHashMap(u32, usize).init(allocator);
223 try enum_map.ensureTotalCapacity(enumerants.len);
224
225 var aliases = std.ArrayList(struct { enumerant: usize, alias: usize }).init(allocator);
226 try aliases.ensureTotalCapacity(enumerants.len);
227
228 for (enumerants) |enumerant, i| {
229 const result = enum_map.getOrPutAssumeCapacity(enumerant.value.int);
230 if (!result.found_existing) {
231 result.value_ptr.* = i;
232 continue;
233 }
234
235 const existing = enumerants[result.value_ptr.*];
236
237 const tag_index = std.mem.indexOfDiff(u8, enumerant.enumerant, existing.enumerant).?;
238 const enum_priority = tagPriorityScore(enumerant.enumerant[tag_index..]);
239 const existing_priority = tagPriorityScore(existing.enumerant[tag_index..]);
240
241 if (enum_priority < existing_priority) {
242 aliases.appendAssumeCapacity(.{ .enumerant = result.value_ptr.*, .alias = i });
243 result.value_ptr.* = i;
244 } else {
245 aliases.appendAssumeCapacity(.{ .enumerant = i, .alias = result.value_ptr.* });
246 }
247 }
248
249 const enum_indices = enum_map.values();
250
251 try writer.print("pub const {s} = enum(u32) {{\n", .{std.zig.fmtId(enumeration.kind)});
252
253 for (enum_indices) |i| {
254 const enumerant = enumerants[i];
84255 if (enumerant.value != .int) return error.InvalidRegistry;
85256
86 try writer.print(" {} = {},\n", .{ std.zig.fmtId(enumerant.enumerant), enumerant.value.int });
257 try writer.print("{} = {},\n", .{ std.zig.fmtId(enumerant.enumerant), enumerant.value.int });
258 }
259
260 try writer.writeByte('\n');
261
262 for (aliases.items) |alias| {
263 try writer.print("pub const {} = {}.{};\n", .{
264 std.zig.fmtId(enumerants[alias.enumerant].enumerant),
265 std.zig.fmtId(enumeration.kind),
266 std.zig.fmtId(enumerants[alias.alias].enumerant),
267 });
268 }
269
270 if (!extended_structs.contains(enumeration.kind)) {
271 try writer.writeAll("};\n");
272 return;
273 }
274
275 try writer.print("\npub const Extended = union({}) {{\n", .{std.zig.fmtId(enumeration.kind)});
276
277 for (enum_indices) |i| {
278 const enumerant = enumerants[i];
279 try renderOperand(writer, .@"union", enumerant.enumerant, enumerant.parameters, extended_structs);
87280 }
88281
89 try writer.writeAll(" _,\n};\n");
282 try writer.writeAll("};\n};\n");
90283}
91284
92fn renderBitEnum(writer: anytype, enumeration: g.OperandKind) !void {
93 try writer.print("pub const {s} = packed struct {{\n", .{enumeration.kind});
285fn renderBitEnum(
286 writer: anytype,
287 allocator: Allocator,
288 enumeration: g.OperandKind,
289 extended_structs: ExtendedStructSet,
290) !void {
291 try writer.print("pub const {s} = packed struct {{\n", .{std.zig.fmtId(enumeration.kind)});
94292
95 var flags_by_bitpos = [_]?[]const u8{null} ** 32;
293 var flags_by_bitpos = [_]?usize{null} ** 32;
96294 const enumerants = enumeration.enumerants orelse return error.InvalidRegistry;
97 for (enumerants) |enumerant| {
295
296 var aliases = std.ArrayList(struct { flag: usize, alias: u5 }).init(allocator);
297 try aliases.ensureTotalCapacity(enumerants.len);
298
299 for (enumerants) |enumerant, i| {
98300 if (enumerant.value != .bitflag) return error.InvalidRegistry;
99301 const value = try parseHexInt(enumerant.value.bitflag);
100 if (@popCount(u32, value) != 1) {
101 continue; // Skip combinations and 'none' items
302 if (@popCount(u32, value) == 0) {
303 continue; // Skip 'none' items
102304 }
103305
306 std.debug.assert(@popCount(u32, value) == 1);
307
104308 var bitpos = std.math.log2_int(u32, value);
105309 if (flags_by_bitpos[bitpos]) |*existing| {
106 // Keep the shortest
107 if (enumerant.enumerant.len < existing.len)
108 existing.* = enumerant.enumerant;
310 const tag_index = std.mem.indexOfDiff(u8, enumerant.enumerant, enumerants[existing.*].enumerant).?;
311 const enum_priority = tagPriorityScore(enumerant.enumerant[tag_index..]);
312 const existing_priority = tagPriorityScore(enumerants[existing.*].enumerant[tag_index..]);
313
314 if (enum_priority < existing_priority) {
315 aliases.appendAssumeCapacity(.{ .flag = existing.*, .alias = bitpos });
316 existing.* = i;
317 } else {
318 aliases.appendAssumeCapacity(.{ .flag = i, .alias = bitpos });
319 }
109320 } else {
110 flags_by_bitpos[bitpos] = enumerant.enumerant;
321 flags_by_bitpos[bitpos] = i;
111322 }
112323 }
113324
114 for (flags_by_bitpos) |maybe_flag_name, bitpos| {
115 try writer.writeAll(" ");
116 if (maybe_flag_name) |flag_name| {
117 try writer.writeAll(flag_name);
325 for (flags_by_bitpos) |maybe_flag_index, bitpos| {
326 if (maybe_flag_index) |flag_index| {
327 try writer.print("{}", .{std.zig.fmtId(enumerants[flag_index].enumerant)});
118328 } else {
119329 try writer.print("_reserved_bit_{}", .{bitpos});
120330 }
......@@ -126,7 +336,169 @@ fn renderBitEnum(writer: anytype, enumeration: g.OperandKind) !void {
126336 try writer.writeAll("= false,\n");
127337 }
128338
129 try writer.writeAll("};\n");
339 try writer.writeByte('\n');
340
341 for (aliases.items) |alias| {
342 try writer.print("pub const {}: {} = .{{.{} = true}};\n", .{
343 std.zig.fmtId(enumerants[alias.flag].enumerant),
344 std.zig.fmtId(enumeration.kind),
345 std.zig.fmtId(enumerants[flags_by_bitpos[alias.alias].?].enumerant),
346 });
347 }
348
349 if (!extended_structs.contains(enumeration.kind)) {
350 try writer.writeAll("};\n");
351 return;
352 }
353
354 try writer.print("\npub const Extended = struct {{\n", .{});
355
356 for (flags_by_bitpos) |maybe_flag_index, bitpos| {
357 const flag_index = maybe_flag_index orelse {
358 try writer.print("_reserved_bit_{}: bool = false,\n", .{bitpos});
359 continue;
360 };
361 const enumerant = enumerants[flag_index];
362
363 try renderOperand(writer, .mask, enumerant.enumerant, enumerant.parameters, extended_structs);
364 }
365
366 try writer.writeAll("};\n};\n");
367}
368
369fn renderOperand(
370 writer: anytype,
371 kind: enum {
372 @"union",
373 instruction,
374 mask,
375 },
376 field_name: []const u8,
377 parameters: []const g.Operand,
378 extended_structs: ExtendedStructSet,
379) !void {
380 if (kind == .instruction) {
381 try writer.writeByte('.');
382 }
383 try writer.print("{}", .{std.zig.fmtId(field_name)});
384 if (parameters.len == 0) {
385 switch (kind) {
386 .@"union" => try writer.writeAll(",\n"),
387 .instruction => try writer.writeAll(" => void,\n"),
388 .mask => try writer.writeAll(": bool = false,\n"),
389 }
390 return;
391 }
392
393 if (kind == .instruction) {
394 try writer.writeAll(" => ");
395 } else {
396 try writer.writeAll(": ");
397 }
398
399 if (kind == .mask) {
400 try writer.writeByte('?');
401 }
402
403 try writer.writeAll("struct{");
404
405 for (parameters) |param, j| {
406 if (j != 0) {
407 try writer.writeAll(", ");
408 }
409
410 try renderFieldName(writer, parameters, j);
411 try writer.writeAll(": ");
412
413 if (param.quantifier) |q| {
414 switch (q) {
415 .@"?" => try writer.writeByte('?'),
416 .@"*" => try writer.writeAll("[]const "),
417 }
418 }
419
420 try writer.print("{}", .{std.zig.fmtId(param.kind)});
421
422 if (extended_structs.contains(param.kind)) {
423 try writer.writeAll(".Extended");
424 }
425
426 if (param.quantifier) |q| {
427 switch (q) {
428 .@"?" => try writer.writeAll(" = null"),
429 .@"*" => try writer.writeAll(" = &.{}"),
430 }
431 }
432 }
433
434 try writer.writeAll("}");
435
436 if (kind == .mask) {
437 try writer.writeAll(" = null");
438 }
439
440 try writer.writeAll(",\n");
441}
442
443fn renderFieldName(writer: anytype, operands: []const g.Operand, field_index: usize) !void {
444 const operand = operands[field_index];
445
446 // Should be enough for all names - adjust as needed.
447 var name_buffer = std.BoundedArray(u8, 64){
448 .buffer = undefined,
449 };
450
451 derive_from_kind: {
452 // Operand names are often in the json encoded as "'Name'" (with two sets of quotes).
453 // Additionally, some operands have ~ in them at the end (D~ref~).
454 const name = std.mem.trim(u8, operand.name, "'~");
455 if (name.len == 0) {
456 break :derive_from_kind;
457 }
458
459 // Some names have weird characters in them (like newlines) - skip any such ones.
460 // Use the same loop to transform to snake-case.
461 for (name) |c| {
462 switch (c) {
463 'a'...'z', '0'...'9' => try name_buffer.append(c),
464 'A'...'Z' => try name_buffer.append(std.ascii.toLower(c)),
465 ' ', '~' => try name_buffer.append('_'),
466 else => break :derive_from_kind,
467 }
468 }
469
470 // Assume there are no duplicate 'name' fields.
471 try writer.print("{}", .{std.zig.fmtId(name_buffer.slice())});
472 return;
473 }
474
475 // Translate to snake case.
476 name_buffer.len = 0;
477 for (operand.kind) |c, i| {
478 switch (c) {
479 'a'...'z', '0'...'9' => try name_buffer.append(c),
480 'A'...'Z' => if (i > 0 and std.ascii.isLower(operand.kind[i - 1])) {
481 try name_buffer.appendSlice(&[_]u8{ '_', std.ascii.toLower(c) });
482 } else {
483 try name_buffer.append(std.ascii.toLower(c));
484 },
485 else => unreachable, // Assume that the name is valid C-syntax (and contains no underscores).
486 }
487 }
488
489 try writer.print("{}", .{std.zig.fmtId(name_buffer.slice())});
490
491 // For fields derived from type name, there could be any amount.
492 // Simply check against all other fields, and if another similar one exists, add a number.
493 const need_extra_index = for (operands) |other_operand, i| {
494 if (i != field_index and std.mem.eql(u8, operand.kind, other_operand.kind)) {
495 break true;
496 }
497 } else false;
498
499 if (need_extra_index) {
500 try writer.print("_{}", .{field_index});
501 }
130502}
131503
132504fn parseHexInt(text: []const u8) !u31 {
......@@ -142,7 +514,7 @@ fn usageAndExit(file: std.fs.File, arg0: []const u8, code: u8) noreturn {
142514 \\
143515 \\Generates Zig bindings for a SPIR-V specification .json (either core or
144516 \\extinst versions). The result, printed to stdout, should be used to update
145 \\files in src/codegen/spirv.
517 \\files in src/codegen/spirv. Don't forget to format the output.
146518 \\
147519 \\The relevant specifications can be obtained from the SPIR-V registry:
148520 \\https://github.com/KhronosGroup/SPIRV-Headers/blob/master/include/spirv/unified1/