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 @@...@@ -1,5 +1,8 @@
1const std = @import("std");1const std = @import("std");
2const g = @import("spirv/grammar.zig");2const g = @import("spirv/grammar.zig");
3const Allocator = std.mem.Allocator;
4
5const ExtendedStructSet = std.StringHashMap(void);
36
4pub fn main() !void {7pub fn main() !void {
5 var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);8 var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
...@@ -20,101 +23,308 @@ pub fn main() !void {...@@ -20,101 +23,308 @@ pub fn main() !void {
20 var tokens = std.json.TokenStream.init(spec);23 var tokens = std.json.TokenStream.init(spec);
21 var registry = try std.json.parse(g.Registry, &tokens, .{ .allocator = allocator });24 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
23 var bw = std.io.bufferedWriter(std.io.getStdOut().writer());31 var bw = std.io.bufferedWriter(std.io.getStdOut().writer());
24 try render(bw.writer(), registry);32 try render(bw.writer(), allocator, core_reg);
25 try bw.flush();33 try bw.flush();
26}34}
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 {
29 try writer.writeAll(77 try writer.writeAll(
30 \\//! This file is auto-generated by tools/gen_spirv_spec.zig.78 \\//! This file is auto-generated by tools/gen_spirv_spec.zig.
31 \\79 \\
32 \\const Version = @import("std").builtin.Version;80 \\const Version = @import("std").builtin.Version;
33 \\81 \\
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 \\
34 );120 );
35121
36 switch (registry) {122 try writer.print(
37 .core => |core_reg| {123 \\pub const version = Version{{ .major = {}, .minor = {}, .patch = {} }};
38 try writer.print(124 \\pub const magic_number: Word = {s};
39 \\pub const version = Version{{ .major = {}, .minor = {}, .patch = {} }};125 \\
40 \\pub const magic_number: u32 = {s};126 ,
41 \\127 .{ registry.major_version, registry.minor_version, registry.revision, registry.magic_number },
42 ,128 );
43 .{ core_reg.major_version, core_reg.minor_version, core_reg.revision, core_reg.magic_number },129 const extended_structs = try extendedStructs(allocator, registry.operand_kinds);
44 );130 try renderOpcodes(writer, allocator, registry.instructions, extended_structs);
45 try renderOpcodes(writer, core_reg.instructions);131 try renderOperandKinds(writer, allocator, registry.operand_kinds, extended_structs);
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 }
59}132}
60133
61fn renderOpcodes(writer: anytype, instructions: []const g.Instruction) !void {134fn renderOpcodes(
62 try writer.writeAll("pub const Opcode = extern enum(u16) {\n");135 writer: anytype,
63 for (instructions) |instr| {136 allocator: Allocator,
64 try writer.print(" {} = {},\n", .{ std.zig.fmtId(instr.opname), instr.opcode });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 }
65 }165 }
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;
67}197}
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 {
70 for (kinds) |kind| {205 for (kinds) |kind| {
71 switch (kind.category) {206 switch (kind.category) {
72 .ValueEnum => try renderValueEnum(writer, kind),207 .ValueEnum => try renderValueEnum(writer, allocator, kind, extended_structs),
73 .BitEnum => try renderBitEnum(writer, kind),208 .BitEnum => try renderBitEnum(writer, allocator, kind, extended_structs),
74 else => {},209 else => {},
75 }210 }
76 }211 }
77}212}
78213
79fn renderValueEnum(writer: anytype, enumeration: g.OperandKind) !void {214fn renderValueEnum(
80 try writer.print("pub const {s} = extern enum(u32) {{\n", .{enumeration.kind});215 writer: anytype,
81216 allocator: Allocator,
217 enumeration: g.OperandKind,
218 extended_structs: ExtendedStructSet,
219) !void {
82 const enumerants = enumeration.enumerants orelse return error.InvalidRegistry;220 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];
84 if (enumerant.value != .int) return error.InvalidRegistry;255 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);
87 }280 }
88281
89 try writer.writeAll(" _,\n};\n");282 try writer.writeAll("};\n};\n");
90}283}
91284
92fn renderBitEnum(writer: anytype, enumeration: g.OperandKind) !void {285fn renderBitEnum(
93 try writer.print("pub const {s} = packed struct {{\n", .{enumeration.kind});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;
96 const enumerants = enumeration.enumerants orelse return error.InvalidRegistry;294 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| {
98 if (enumerant.value != .bitflag) return error.InvalidRegistry;300 if (enumerant.value != .bitflag) return error.InvalidRegistry;
99 const value = try parseHexInt(enumerant.value.bitflag);301 const value = try parseHexInt(enumerant.value.bitflag);
100 if (@popCount(u32, value) != 1) {302 if (@popCount(u32, value) == 0) {
101 continue; // Skip combinations and 'none' items303 continue; // Skip 'none' items
102 }304 }
103305
306 std.debug.assert(@popCount(u32, value) == 1);
307
104 var bitpos = std.math.log2_int(u32, value);308 var bitpos = std.math.log2_int(u32, value);
105 if (flags_by_bitpos[bitpos]) |*existing| {309 if (flags_by_bitpos[bitpos]) |*existing| {
106 // Keep the shortest310 const tag_index = std.mem.indexOfDiff(u8, enumerant.enumerant, enumerants[existing.*].enumerant).?;
107 if (enumerant.enumerant.len < existing.len)311 const enum_priority = tagPriorityScore(enumerant.enumerant[tag_index..]);
108 existing.* = enumerant.enumerant;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 }
109 } else {320 } else {
110 flags_by_bitpos[bitpos] = enumerant.enumerant;321 flags_by_bitpos[bitpos] = i;
111 }322 }
112 }323 }
113324
114 for (flags_by_bitpos) |maybe_flag_name, bitpos| {325 for (flags_by_bitpos) |maybe_flag_index, bitpos| {
115 try writer.writeAll(" ");326 if (maybe_flag_index) |flag_index| {
116 if (maybe_flag_name) |flag_name| {327 try writer.print("{}", .{std.zig.fmtId(enumerants[flag_index].enumerant)});
117 try writer.writeAll(flag_name);
118 } else {328 } else {
119 try writer.print("_reserved_bit_{}", .{bitpos});329 try writer.print("_reserved_bit_{}", .{bitpos});
120 }330 }
...@@ -126,7 +336,169 @@ fn renderBitEnum(writer: anytype, enumeration: g.OperandKind) !void {...@@ -126,7 +336,169 @@ fn renderBitEnum(writer: anytype, enumeration: g.OperandKind) !void {
126 try writer.writeAll("= false,\n");336 try writer.writeAll("= false,\n");
127 }337 }
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 }
130}502}
131503
132fn parseHexInt(text: []const u8) !u31 {504fn parseHexInt(text: []const u8) !u31 {
...@@ -142,7 +514,7 @@ fn usageAndExit(file: std.fs.File, arg0: []const u8, code: u8) noreturn {...@@ -142,7 +514,7 @@ fn usageAndExit(file: std.fs.File, arg0: []const u8, code: u8) noreturn {
142 \\514 \\
143 \\Generates Zig bindings for a SPIR-V specification .json (either core or515 \\Generates Zig bindings for a SPIR-V specification .json (either core or
144 \\extinst versions). The result, printed to stdout, should be used to update516 \\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.
146 \\518 \\
147 \\The relevant specifications can be obtained from the SPIR-V registry:519 \\The relevant specifications can be obtained from the SPIR-V registry:
148 \\https://github.com/KhronosGroup/SPIRV-Headers/blob/master/include/spirv/unified1/520 \\https://github.com/KhronosGroup/SPIRV-Headers/blob/master/include/spirv/unified1/