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}