master
1const std = @import("std");
2const Allocator = std.mem.Allocator;
3const g = @import("spirv/grammar.zig");
4const CoreRegistry = g.CoreRegistry;
5const ExtensionRegistry = g.ExtensionRegistry;
6const Instruction = g.Instruction;
7const OperandKind = g.OperandKind;
8const Enumerant = g.Enumerant;
9const Operand = g.Operand;
10
11const ExtendedStructSet = std.StringHashMap(void);
12
13const Extension = struct {
14 name: []const u8,
15 opcode_name: []const u8,
16 spec: ExtensionRegistry,
17};
18
19const CmpInst = struct {
20 fn lt(_: CmpInst, a: Instruction, b: Instruction) bool {
21 return a.opcode < b.opcode;
22 }
23};
24
25const StringPair = struct { []const u8, []const u8 };
26
27const StringPairContext = struct {
28 pub fn hash(_: @This(), a: StringPair) u32 {
29 var hasher = std.hash.Wyhash.init(0);
30 const x, const y = a;
31 hasher.update(x);
32 hasher.update(y);
33 return @truncate(hasher.final());
34 }
35
36 pub fn eql(_: @This(), a: StringPair, b: StringPair, b_index: usize) bool {
37 _ = b_index;
38 const a_x, const a_y = a;
39 const b_x, const b_y = b;
40 return std.mem.eql(u8, a_x, b_x) and std.mem.eql(u8, a_y, b_y);
41 }
42};
43
44const OperandKindMap = std.ArrayHashMap(StringPair, OperandKind, StringPairContext, true);
45
46/// Khronos made it so that these names are not defined explicitly, so
47/// we need to hardcode it (like they did).
48/// See https://github.com/KhronosGroup/SPIRV-Registry
49const set_names = std.StaticStringMap(struct { []const u8, []const u8 }).initComptime(.{
50 .{ "opencl.std.100", .{ "OpenCL.std", "OpenClOpcode" } },
51 .{ "glsl.std.450", .{ "GLSL.std.450", "GlslOpcode" } },
52 .{ "zig", .{ "zig", "Zig" } },
53});
54
55var arena = std.heap.ArenaAllocator.init(std.heap.smp_allocator);
56const allocator = arena.allocator();
57
58pub fn main() !void {
59 defer arena.deinit();
60
61 const args = try std.process.argsAlloc(allocator);
62 if (args.len != 3) {
63 usageAndExit(args[0], 1);
64 }
65
66 const json_path = try std.fs.path.join(allocator, &.{ args[1], "include/spirv/unified1/" });
67 const dir = try std.fs.cwd().openDir(json_path, .{ .iterate = true });
68
69 const core_spec = try readRegistry(CoreRegistry, dir, "spirv.core.grammar.json");
70 std.mem.sortUnstable(Instruction, core_spec.instructions, CmpInst{}, CmpInst.lt);
71
72 var exts = std.array_list.Managed(Extension).init(allocator);
73
74 var it = dir.iterate();
75 while (try it.next()) |entry| {
76 if (entry.kind != .file) {
77 continue;
78 }
79
80 try readExtRegistry(&exts, dir, entry.name);
81 }
82
83 try readExtRegistry(&exts, std.fs.cwd(), args[2]);
84
85 var allocating: std.Io.Writer.Allocating = .init(allocator);
86 defer allocating.deinit();
87 try render(&allocating.writer, core_spec, exts.items);
88 try allocating.writer.writeByte(0);
89 const output = allocating.written()[0 .. allocating.written().len - 1 :0];
90
91 var tree = try std.zig.Ast.parse(allocator, output, .zig);
92
93 if (tree.errors.len != 0) {
94 try std.zig.printAstErrorsToStderr(allocator, tree, "", .auto);
95 return;
96 }
97
98 var zir = try std.zig.AstGen.generate(allocator, tree);
99 if (zir.hasCompileErrors()) {
100 var wip_errors: std.zig.ErrorBundle.Wip = undefined;
101 try wip_errors.init(allocator);
102 defer wip_errors.deinit();
103 try wip_errors.addZirErrorMessages(zir, tree, output, "");
104 var error_bundle = try wip_errors.toOwnedBundle("");
105 defer error_bundle.deinit(allocator);
106 error_bundle.renderToStdErr(.{}, .auto);
107 }
108
109 const formatted_output = try tree.renderAlloc(allocator);
110 _ = try std.fs.File.stdout().write(formatted_output);
111}
112
113fn readExtRegistry(exts: *std.array_list.Managed(Extension), dir: std.fs.Dir, sub_path: []const u8) !void {
114 const filename = std.fs.path.basename(sub_path);
115 if (!std.mem.startsWith(u8, filename, "extinst.")) {
116 return;
117 }
118
119 std.debug.assert(std.mem.endsWith(u8, filename, ".grammar.json"));
120 const name = filename["extinst.".len .. filename.len - ".grammar.json".len];
121 const spec = try readRegistry(ExtensionRegistry, dir, sub_path);
122
123 const set_name = set_names.get(name) orelse {
124 std.log.info("ignored instruction set '{s}'", .{name});
125 return;
126 };
127
128 std.sort.block(Instruction, spec.instructions, CmpInst{}, CmpInst.lt);
129
130 try exts.append(.{
131 .name = set_name.@"0",
132 .opcode_name = set_name.@"1",
133 .spec = spec,
134 });
135}
136
137fn readRegistry(comptime RegistryType: type, dir: std.fs.Dir, path: []const u8) !RegistryType {
138 const spec = try dir.readFileAlloc(path, allocator, .unlimited);
139 // Required for json parsing.
140 // TODO: ALI
141 @setEvalBranchQuota(10000);
142
143 var scanner = std.json.Scanner.initCompleteInput(allocator, spec);
144 var diagnostics = std.json.Diagnostics{};
145 scanner.enableDiagnostics(&diagnostics);
146 const parsed = std.json.parseFromTokenSource(RegistryType, allocator, &scanner, .{}) catch |err| {
147 std.debug.print("{s}:{}:{}:\n", .{ path, diagnostics.getLine(), diagnostics.getColumn() });
148 return err;
149 };
150 return parsed.value;
151}
152
153/// Returns a set with types that require an extra struct for the `Instruction` interface
154/// to the spir-v spec, or whether the original type can be used.
155fn extendedStructs(kinds: []const OperandKind) !ExtendedStructSet {
156 var map = ExtendedStructSet.init(allocator);
157 try map.ensureTotalCapacity(@as(u32, @intCast(kinds.len)));
158
159 for (kinds) |kind| {
160 const enumerants = kind.enumerants orelse continue;
161
162 for (enumerants) |enumerant| {
163 if (enumerant.parameters.len > 0) {
164 break;
165 }
166 } else continue;
167
168 map.putAssumeCapacity(kind.kind, {});
169 }
170
171 return map;
172}
173
174// Return a score for a particular priority. Duplicate instruction/operand enum values are
175// removed by picking the tag with the lowest score to keep, and by making an alias for the
176// other. Note that the tag does not need to be just a tag at this point, in which case it
177// gets the lowest score automatically anyway.
178fn tagPriorityScore(tag: []const u8) usize {
179 if (tag.len == 0) {
180 return 1;
181 } else if (std.mem.eql(u8, tag, "EXT")) {
182 return 2;
183 } else if (std.mem.eql(u8, tag, "KHR")) {
184 return 3;
185 } else {
186 return 4;
187 }
188}
189
190fn render(
191 writer: *std.Io.Writer,
192 registry: CoreRegistry,
193 extensions: []const Extension,
194) !void {
195 try writer.writeAll(
196 \\//! This file is auto-generated by tools/gen_spirv_spec.zig.
197 \\
198 \\const std = @import("std");
199 \\
200 \\pub const Version = packed struct(Word) {
201 \\ padding: u8 = 0,
202 \\ minor: u8,
203 \\ major: u8,
204 \\ padding0: u8 = 0,
205 \\
206 \\ pub fn toWord(self: @This()) Word {
207 \\ return @bitCast(self);
208 \\ }
209 \\};
210 \\
211 \\pub const Word = u32;
212 \\pub const Id = enum(Word) {
213 \\ none,
214 \\ _,
215 \\
216 \\ pub fn format(self: Id, writer: *std.Io.Writer) std.Io.Writer.Error!void {
217 \\ switch (self) {
218 \\ .none => try writer.writeAll("(none)"),
219 \\ else => try writer.print("%{d}", .{@intFromEnum(self)}),
220 \\ }
221 \\ }
222 \\};
223 \\
224 \\pub const IdRange = struct {
225 \\ base: u32,
226 \\ len: u32,
227 \\
228 \\ pub fn at(range: IdRange, i: usize) Id {
229 \\ std.debug.assert(i < range.len);
230 \\ return @enumFromInt(range.base + i);
231 \\ }
232 \\};
233 \\
234 \\pub const LiteralInteger = Word;
235 \\pub const LiteralFloat = Word;
236 \\pub const LiteralString = []const u8;
237 \\pub const LiteralContextDependentNumber = union(enum) {
238 \\ int32: i32,
239 \\ uint32: u32,
240 \\ int64: i64,
241 \\ uint64: u64,
242 \\ float32: f32,
243 \\ float64: f64,
244 \\};
245 \\pub const LiteralExtInstInteger = struct{ inst: Word };
246 \\pub const LiteralSpecConstantOpInteger = struct { opcode: Opcode };
247 \\pub const PairLiteralIntegerIdRef = struct { value: LiteralInteger, label: Id };
248 \\pub const PairIdRefLiteralInteger = struct { target: Id, member: LiteralInteger };
249 \\pub const PairIdRefIdRef = [2]Id;
250 \\
251 \\pub const Quantifier = enum {
252 \\ required,
253 \\ optional,
254 \\ variadic,
255 \\};
256 \\
257 \\pub const Operand = struct {
258 \\ kind: OperandKind,
259 \\ quantifier: Quantifier,
260 \\};
261 \\
262 \\pub const OperandCategory = enum {
263 \\ bit_enum,
264 \\ value_enum,
265 \\ id,
266 \\ literal,
267 \\ composite,
268 \\};
269 \\
270 \\pub const Enumerant = struct {
271 \\ name: []const u8,
272 \\ value: Word,
273 \\ parameters: []const OperandKind,
274 \\};
275 \\
276 \\pub const Instruction = struct {
277 \\ name: []const u8,
278 \\ opcode: Word,
279 \\ operands: []const Operand,
280 \\};
281 \\
282 \\pub const zig_generator_id: Word = 41;
283 \\
284 );
285
286 try writer.print(
287 \\pub const version: Version = .{{ .major = {}, .minor = {}, .patch = {} }};
288 \\pub const magic_number: Word = {s};
289 \\
290 \\
291 ,
292 .{ registry.major_version, registry.minor_version, registry.revision, registry.magic_number },
293 );
294
295 // Merge the operand kinds from all extensions together.
296 var all_operand_kinds = OperandKindMap.init(allocator);
297 for (registry.operand_kinds) |kind| {
298 try all_operand_kinds.putNoClobber(.{ "core", kind.kind }, kind);
299 }
300 for (extensions) |ext| {
301 // Note: extensions may define the same operand kind, with different
302 // parameters. Instead of trying to merge them, just discriminate them
303 // using the name of the extension. This is similar to what
304 // the official headers do.
305
306 try all_operand_kinds.ensureUnusedCapacity(ext.spec.operand_kinds.len);
307 for (ext.spec.operand_kinds) |kind| {
308 var new_kind = kind;
309 new_kind.kind = try std.mem.join(allocator, ".", &.{ ext.name, kind.kind });
310 try all_operand_kinds.putNoClobber(.{ ext.name, kind.kind }, new_kind);
311 }
312 }
313
314 const extended_structs = try extendedStructs(all_operand_kinds.values());
315 // Note: extensions don't seem to have class.
316 try renderClass(writer, registry.instructions);
317 try renderOperandKind(writer, all_operand_kinds.values());
318
319 try renderOpcodes(writer, "Opcode", true, registry.instructions, extended_structs);
320 for (extensions) |ext| {
321 try renderOpcodes(writer, ext.opcode_name, false, ext.spec.instructions, extended_structs);
322 }
323
324 try renderOperandKinds(writer, all_operand_kinds.values(), extended_structs);
325 try renderInstructionSet(writer, registry, extensions, all_operand_kinds);
326}
327
328fn renderInstructionSet(
329 writer: *std.Io.Writer,
330 core: CoreRegistry,
331 extensions: []const Extension,
332 all_operand_kinds: OperandKindMap,
333) !void {
334 try writer.writeAll(
335 \\pub const InstructionSet = enum {
336 \\ core,
337 );
338
339 for (extensions) |ext| {
340 try writer.print("{f},\n", .{std.zig.fmtId(ext.name)});
341 }
342
343 try writer.writeAll(
344 \\
345 \\ pub fn instructions(self: InstructionSet) []const Instruction {
346 \\ return switch (self) {
347 \\
348 );
349
350 try renderInstructionsCase(writer, "core", core.instructions, all_operand_kinds);
351 for (extensions) |ext| {
352 try renderInstructionsCase(writer, ext.name, ext.spec.instructions, all_operand_kinds);
353 }
354
355 try writer.writeAll(
356 \\ };
357 \\ }
358 \\};
359 \\
360 );
361}
362
363fn renderInstructionsCase(
364 writer: *std.Io.Writer,
365 set_name: []const u8,
366 instructions: []const Instruction,
367 all_operand_kinds: OperandKindMap,
368) !void {
369 // Note: theoretically we could dedup from tags and give every instruction a list of aliases,
370 // but there aren't so many total aliases and that would add more overhead in total. We will
371 // just filter those out when needed.
372
373 try writer.print(".{f} => &.{{\n", .{std.zig.fmtId(set_name)});
374
375 for (instructions) |inst| {
376 try writer.print(
377 \\.{{
378 \\ .name = "{s}",
379 \\ .opcode = {},
380 \\ .operands = &.{{
381 \\
382 , .{ inst.opname, inst.opcode });
383
384 for (inst.operands) |operand| {
385 const quantifier = if (operand.quantifier) |q|
386 switch (q) {
387 .@"?" => "optional",
388 .@"*" => "variadic",
389 }
390 else
391 "required";
392
393 const kind = all_operand_kinds.get(.{ set_name, operand.kind }) orelse
394 all_operand_kinds.get(.{ "core", operand.kind }).?;
395 try writer.print(".{{.kind = .{f}, .quantifier = .{s}}},\n", .{ formatId(kind.kind), quantifier });
396 }
397
398 try writer.writeAll(
399 \\ },
400 \\},
401 \\
402 );
403 }
404
405 try writer.writeAll(
406 \\},
407 \\
408 );
409}
410
411fn renderClass(writer: *std.Io.Writer, instructions: []const Instruction) !void {
412 var class_map = std.StringArrayHashMap(void).init(allocator);
413
414 for (instructions) |inst| {
415 if (std.mem.eql(u8, inst.class.?, "@exclude")) continue;
416 try class_map.put(inst.class.?, {});
417 }
418
419 try writer.writeAll("pub const Class = enum {\n");
420 for (class_map.keys()) |class| {
421 try writer.print("{f},\n", .{formatId(class)});
422 }
423 try writer.writeAll("};\n\n");
424}
425
426const Formatter = struct {
427 data: []const u8,
428
429 fn format(f: Formatter, writer: *std.Io.Writer) std.Io.Writer.Error!void {
430 var id_buf: [128]u8 = undefined;
431 var fw: std.Io.Writer = .fixed(&id_buf);
432 for (f.data, 0..) |c, i| {
433 switch (c) {
434 '-', '_', '.', '~', ' ' => fw.writeByte('_') catch return error.WriteFailed,
435 'a'...'z', '0'...'9' => fw.writeByte(c) catch return error.WriteFailed,
436 'A'...'Z' => {
437 if ((i > 0 and std.ascii.isLower(f.data[i - 1])) or
438 (i > 0 and std.ascii.isUpper(f.data[i - 1]) and
439 i + 1 < f.data.len and std.ascii.isLower(f.data[i + 1])))
440 {
441 _ = fw.write(&.{ '_', std.ascii.toLower(c) }) catch return error.WriteFailed;
442 } else {
443 fw.writeByte(std.ascii.toLower(c)) catch return error.WriteFailed;
444 }
445 },
446 else => unreachable,
447 }
448 }
449
450 // make sure that this won't clobber with zig keywords
451 try writer.print("{f}", .{std.zig.fmtId(fw.buffered())});
452 }
453};
454
455fn formatId(identifier: []const u8) std.fmt.Alt(Formatter, Formatter.format) {
456 return .{ .data = .{ .data = identifier } };
457}
458
459fn renderOperandKind(writer: *std.Io.Writer, operands: []const OperandKind) !void {
460 try writer.writeAll(
461 \\pub const OperandKind = enum {
462 \\ opcode,
463 \\
464 );
465 for (operands) |operand| {
466 try writer.print("{f},\n", .{formatId(operand.kind)});
467 }
468 try writer.writeAll(
469 \\
470 \\pub fn category(self: OperandKind) OperandCategory {
471 \\ return switch (self) {
472 \\ .opcode => .literal,
473 \\
474 );
475 for (operands) |operand| {
476 const cat = switch (operand.category) {
477 .BitEnum => "bit_enum",
478 .ValueEnum => "value_enum",
479 .Id => "id",
480 .Literal => "literal",
481 .Composite => "composite",
482 };
483 try writer.print(".{f} => .{s},\n", .{ formatId(operand.kind), cat });
484 }
485 try writer.writeAll(
486 \\ };
487 \\}
488 \\pub fn enumerants(self: OperandKind) []const Enumerant {
489 \\ return switch (self) {
490 \\ .opcode => unreachable,
491 \\
492 );
493 for (operands) |operand| {
494 switch (operand.category) {
495 .BitEnum, .ValueEnum => {},
496 else => {
497 try writer.print(".{f} => unreachable,\n", .{formatId(operand.kind)});
498 continue;
499 },
500 }
501
502 try writer.print(".{f} => &.{{", .{formatId(operand.kind)});
503 for (operand.enumerants.?) |enumerant| {
504 if (enumerant.value == .bitflag and std.mem.eql(u8, enumerant.enumerant, "None")) {
505 continue;
506 }
507 try renderEnumerant(writer, enumerant);
508 try writer.writeAll(",");
509 }
510 try writer.writeAll("},\n");
511 }
512 try writer.writeAll("};\n}\n};\n");
513}
514
515fn renderEnumerant(writer: *std.Io.Writer, enumerant: Enumerant) !void {
516 try writer.print(".{{.name = \"{s}\", .value = ", .{enumerant.enumerant});
517 switch (enumerant.value) {
518 .bitflag => |flag| try writer.writeAll(flag),
519 .int => |int| try writer.print("{}", .{int}),
520 }
521 try writer.writeAll(", .parameters = &.{");
522 for (enumerant.parameters, 0..) |param, i| {
523 if (i != 0)
524 try writer.writeAll(", ");
525 // Note, param.quantifier will always be one.
526 try writer.print(".{f}", .{formatId(param.kind)});
527 }
528 try writer.writeAll("}}");
529}
530
531fn renderOpcodes(
532 writer: *std.Io.Writer,
533 opcode_type_name: []const u8,
534 want_operands: bool,
535 instructions: []const Instruction,
536 extended_structs: ExtendedStructSet,
537) !void {
538 var inst_map = std.AutoArrayHashMap(u32, usize).init(allocator);
539 try inst_map.ensureTotalCapacity(instructions.len);
540
541 var aliases = std.array_list.Managed(struct { inst: usize, alias: usize }).init(allocator);
542 try aliases.ensureTotalCapacity(instructions.len);
543
544 for (instructions, 0..) |inst, i| {
545 if (inst.class) |class| {
546 if (std.mem.eql(u8, class, "@exclude")) continue;
547 }
548
549 const result = inst_map.getOrPutAssumeCapacity(inst.opcode);
550 if (!result.found_existing) {
551 result.value_ptr.* = i;
552 continue;
553 }
554
555 const existing = instructions[result.value_ptr.*];
556
557 const tag_index = std.mem.indexOfDiff(u8, inst.opname, existing.opname).?;
558 const inst_priority = tagPriorityScore(inst.opname[tag_index..]);
559 const existing_priority = tagPriorityScore(existing.opname[tag_index..]);
560
561 if (inst_priority < existing_priority) {
562 aliases.appendAssumeCapacity(.{ .inst = result.value_ptr.*, .alias = i });
563 result.value_ptr.* = i;
564 } else {
565 aliases.appendAssumeCapacity(.{ .inst = i, .alias = result.value_ptr.* });
566 }
567 }
568
569 const instructions_indices = inst_map.values();
570
571 try writer.print("\npub const {f} = enum(u16) {{\n", .{std.zig.fmtId(opcode_type_name)});
572 for (instructions_indices) |i| {
573 const inst = instructions[i];
574 try writer.print("{f} = {},\n", .{ std.zig.fmtId(inst.opname), inst.opcode });
575 }
576
577 try writer.writeAll("\n");
578
579 for (aliases.items) |alias| {
580 try writer.print("pub const {f} = {f}.{f};\n", .{
581 formatId(instructions[alias.inst].opname),
582 std.zig.fmtId(opcode_type_name),
583 formatId(instructions[alias.alias].opname),
584 });
585 }
586
587 if (want_operands) {
588 try writer.print(
589 \\
590 \\pub fn Operands(comptime self: {f}) type {{
591 \\ return switch (self) {{
592 \\
593 , .{std.zig.fmtId(opcode_type_name)});
594
595 for (instructions_indices) |i| {
596 const inst = instructions[i];
597 try renderOperand(writer, .instruction, inst.opname, inst.operands, extended_structs, false);
598 }
599
600 try writer.writeAll(
601 \\ };
602 \\}
603 \\
604 );
605
606 try writer.print(
607 \\pub fn class(self: {f}) Class {{
608 \\ return switch (self) {{
609 \\
610 , .{std.zig.fmtId(opcode_type_name)});
611
612 for (instructions_indices) |i| {
613 const inst = instructions[i];
614 try writer.print(".{f} => .{f},\n", .{ std.zig.fmtId(inst.opname), formatId(inst.class.?) });
615 }
616
617 try writer.writeAll(
618 \\ };
619 \\}
620 \\
621 );
622 }
623
624 try writer.writeAll(
625 \\};
626 \\
627 );
628}
629
630fn renderOperandKinds(
631 writer: *std.Io.Writer,
632 kinds: []const OperandKind,
633 extended_structs: ExtendedStructSet,
634) !void {
635 for (kinds) |kind| {
636 switch (kind.category) {
637 .ValueEnum => try renderValueEnum(writer, kind, extended_structs),
638 .BitEnum => try renderBitEnum(writer, kind, extended_structs),
639 else => {},
640 }
641 }
642}
643
644fn renderValueEnum(
645 writer: *std.Io.Writer,
646 enumeration: OperandKind,
647 extended_structs: ExtendedStructSet,
648) !void {
649 const enumerants = enumeration.enumerants orelse return error.InvalidRegistry;
650
651 var enum_map = std.AutoArrayHashMap(u32, usize).init(allocator);
652 try enum_map.ensureTotalCapacity(enumerants.len);
653
654 var aliases = std.array_list.Managed(struct { enumerant: usize, alias: usize }).init(allocator);
655 try aliases.ensureTotalCapacity(enumerants.len);
656
657 for (enumerants, 0..) |enumerant, i| {
658 const value: u31 = switch (enumerant.value) {
659 .int => |value| value,
660 // Some extensions declare ints as string
661 .bitflag => |value| try std.fmt.parseInt(u31, value, 10),
662 };
663 const result = enum_map.getOrPutAssumeCapacity(value);
664 if (!result.found_existing) {
665 result.value_ptr.* = i;
666 continue;
667 }
668
669 const existing = enumerants[result.value_ptr.*];
670
671 const tag_index = std.mem.indexOfDiff(u8, enumerant.enumerant, existing.enumerant).?;
672 const enum_priority = tagPriorityScore(enumerant.enumerant[tag_index..]);
673 const existing_priority = tagPriorityScore(existing.enumerant[tag_index..]);
674
675 if (enum_priority < existing_priority) {
676 aliases.appendAssumeCapacity(.{ .enumerant = result.value_ptr.*, .alias = i });
677 result.value_ptr.* = i;
678 } else {
679 aliases.appendAssumeCapacity(.{ .enumerant = i, .alias = result.value_ptr.* });
680 }
681 }
682
683 const enum_indices = enum_map.values();
684
685 try writer.print("pub const {f} = enum(u32) {{\n", .{std.zig.fmtId(enumeration.kind)});
686
687 for (enum_indices) |i| {
688 const enumerant = enumerants[i];
689 // if (enumerant.value != .int) return error.InvalidRegistry;
690
691 switch (enumerant.value) {
692 .int => |value| try writer.print("{f} = {},\n", .{ formatId(enumerant.enumerant), value }),
693 .bitflag => |value| try writer.print("{f} = {s},\n", .{ formatId(enumerant.enumerant), value }),
694 }
695 }
696
697 try writer.writeByte('\n');
698
699 for (aliases.items) |alias| {
700 try writer.print("pub const {f} = {f}.{f};\n", .{
701 formatId(enumerants[alias.enumerant].enumerant),
702 std.zig.fmtId(enumeration.kind),
703 formatId(enumerants[alias.alias].enumerant),
704 });
705 }
706
707 if (!extended_structs.contains(enumeration.kind)) {
708 try writer.writeAll("};\n");
709 return;
710 }
711
712 try writer.print("\npub const Extended = union({f}) {{\n", .{std.zig.fmtId(enumeration.kind)});
713
714 for (enum_indices) |i| {
715 const enumerant = enumerants[i];
716 try renderOperand(writer, .@"union", enumerant.enumerant, enumerant.parameters, extended_structs, true);
717 }
718
719 try writer.writeAll("};\n};\n");
720}
721
722fn renderBitEnum(
723 writer: *std.Io.Writer,
724 enumeration: OperandKind,
725 extended_structs: ExtendedStructSet,
726) !void {
727 try writer.print("pub const {f} = packed struct {{\n", .{std.zig.fmtId(enumeration.kind)});
728
729 var flags_by_bitpos = [_]?usize{null} ** 32;
730 const enumerants = enumeration.enumerants orelse return error.InvalidRegistry;
731
732 var aliases = std.array_list.Managed(struct { flag: usize, alias: u5 }).init(allocator);
733 try aliases.ensureTotalCapacity(enumerants.len);
734
735 for (enumerants, 0..) |enumerant, i| {
736 if (enumerant.value != .bitflag) return error.InvalidRegistry;
737 const value = try parseHexInt(enumerant.value.bitflag);
738 if (value == 0) {
739 continue; // Skip 'none' items
740 } else if (std.mem.eql(u8, enumerant.enumerant, "FlagIsPublic")) {
741 // This flag is special and poorly defined in the json files.
742 // Just skip it for now
743 continue;
744 }
745
746 std.debug.assert(@popCount(value) == 1);
747
748 const bitpos = std.math.log2_int(u32, value);
749 if (flags_by_bitpos[bitpos]) |*existing| {
750 const tag_index = std.mem.indexOfDiff(u8, enumerant.enumerant, enumerants[existing.*].enumerant).?;
751 const enum_priority = tagPriorityScore(enumerant.enumerant[tag_index..]);
752 const existing_priority = tagPriorityScore(enumerants[existing.*].enumerant[tag_index..]);
753
754 if (enum_priority < existing_priority) {
755 aliases.appendAssumeCapacity(.{ .flag = existing.*, .alias = bitpos });
756 existing.* = i;
757 } else {
758 aliases.appendAssumeCapacity(.{ .flag = i, .alias = bitpos });
759 }
760 } else {
761 flags_by_bitpos[bitpos] = i;
762 }
763 }
764
765 for (flags_by_bitpos, 0..) |maybe_flag_index, bitpos| {
766 if (maybe_flag_index) |flag_index| {
767 try writer.print("{f}", .{formatId(enumerants[flag_index].enumerant)});
768 } else {
769 try writer.print("_reserved_bit_{}", .{bitpos});
770 }
771
772 try writer.writeAll(": bool = false,\n");
773 }
774
775 try writer.writeByte('\n');
776
777 for (aliases.items) |alias| {
778 try writer.print("pub const {f}: {f} = .{{.{f} = true}};\n", .{
779 formatId(enumerants[alias.flag].enumerant),
780 std.zig.fmtId(enumeration.kind),
781 formatId(enumerants[flags_by_bitpos[alias.alias].?].enumerant),
782 });
783 }
784
785 if (!extended_structs.contains(enumeration.kind)) {
786 try writer.writeAll("};\n");
787 return;
788 }
789
790 try writer.print("\npub const Extended = struct {{\n", .{});
791
792 for (flags_by_bitpos, 0..) |maybe_flag_index, bitpos| {
793 const flag_index = maybe_flag_index orelse {
794 try writer.print("_reserved_bit_{}: bool = false,\n", .{bitpos});
795 continue;
796 };
797 const enumerant = enumerants[flag_index];
798
799 try renderOperand(writer, .mask, enumerant.enumerant, enumerant.parameters, extended_structs, true);
800 }
801
802 try writer.writeAll("};\n};\n");
803}
804
805fn renderOperand(
806 writer: *std.Io.Writer,
807 kind: enum {
808 @"union",
809 instruction,
810 mask,
811 },
812 field_name: []const u8,
813 parameters: []const Operand,
814 extended_structs: ExtendedStructSet,
815 snake_case: bool,
816) !void {
817 if (kind == .instruction) {
818 try writer.writeByte('.');
819 }
820
821 if (snake_case) {
822 try writer.print("{f}", .{formatId(field_name)});
823 } else {
824 try writer.print("{f}", .{std.zig.fmtId(field_name)});
825 }
826
827 if (parameters.len == 0) {
828 switch (kind) {
829 .@"union" => try writer.writeAll(",\n"),
830 .instruction => try writer.writeAll(" => void,\n"),
831 .mask => try writer.writeAll(": bool = false,\n"),
832 }
833 return;
834 }
835
836 if (kind == .instruction) {
837 try writer.writeAll(" => ");
838 } else {
839 try writer.writeAll(": ");
840 }
841
842 if (kind == .mask) {
843 try writer.writeByte('?');
844 }
845
846 try writer.writeAll("struct {");
847
848 for (parameters, 0..) |param, j| {
849 if (j != 0) {
850 try writer.writeAll(", ");
851 }
852
853 try renderFieldName(writer, parameters, j);
854 try writer.writeAll(": ");
855
856 if (param.quantifier) |q| {
857 switch (q) {
858 .@"?" => try writer.writeByte('?'),
859 .@"*" => try writer.writeAll("[]const "),
860 }
861 }
862
863 if (std.mem.startsWith(u8, param.kind, "Id")) {
864 _ = try writer.write("Id");
865 } else {
866 try writer.print("{f}", .{std.zig.fmtId(param.kind)});
867 }
868
869 if (extended_structs.contains(param.kind)) {
870 try writer.writeAll(".Extended");
871 }
872
873 if (param.quantifier) |q| {
874 switch (q) {
875 .@"?" => try writer.writeAll(" = null"),
876 .@"*" => try writer.writeAll(" = &.{}"),
877 }
878 }
879 }
880
881 try writer.writeAll("}");
882
883 if (kind == .mask) {
884 try writer.writeAll(" = null");
885 }
886
887 try writer.writeAll(",\n");
888}
889
890fn renderFieldName(writer: *std.Io.Writer, operands: []const Operand, field_index: usize) !void {
891 const operand = operands[field_index];
892
893 derive_from_kind: {
894 // Operand names are often in the json encoded as "'Name'" (with two sets of quotes).
895 // Additionally, some operands have ~ in them at the end (D~ref~).
896 const name = std.mem.trim(u8, operand.name, "'~");
897 if (name.len == 0) break :derive_from_kind;
898
899 for (name) |c| {
900 switch (c) {
901 'a'...'z', '0'...'9', 'A'...'Z', ' ', '~' => continue,
902 else => break :derive_from_kind,
903 }
904 }
905
906 try writer.print("{f}", .{formatId(name)});
907 return;
908 }
909
910 try writer.print("{f}", .{formatId(operand.kind)});
911
912 // For fields derived from type name, there could be any amount.
913 // Simply check against all other fields, and if another similar one exists, add a number.
914 const need_extra_index = for (operands, 0..) |other_operand, i| {
915 if (i != field_index and std.mem.eql(u8, operand.kind, other_operand.kind)) {
916 break true;
917 }
918 } else false;
919
920 if (need_extra_index) {
921 try writer.print("_{}", .{field_index});
922 }
923}
924
925fn parseHexInt(text: []const u8) !u31 {
926 const prefix = "0x";
927 if (!std.mem.startsWith(u8, text, prefix))
928 return error.InvalidHexInt;
929 return try std.fmt.parseInt(u31, text[prefix.len..], 16);
930}
931
932fn usageAndExit(arg0: []const u8, code: u8) noreturn {
933 const stderr, _ = std.debug.lockStderrWriter(&.{});
934 stderr.print(
935 \\Usage: {s} <SPIRV-Headers repository path> <path/to/zig/src/codegen/spirv/extinst.zig.grammar.json>
936 \\
937 \\Generates Zig bindings for SPIR-V specifications found in the SPIRV-Headers
938 \\repository. The result, printed to stdout, should be used to update
939 \\files in src/codegen/spirv. Don't forget to format the output.
940 \\
941 \\<SPIRV-Headers repository path> should point to a clone of
942 \\https://github.com/KhronosGroup/SPIRV-Headers/
943 \\
944 , .{arg0}) catch std.process.exit(1);
945 std.process.exit(code);
946}