| ... | ... | @@ -0,0 +1,245 @@ |
| 1 | const std = @import("std"); |
| 2 | const Writer = std.ArrayList(u8).Writer; |
| 3 | |
| 4 | //! See https://www.khronos.org/registry/spir-v/specs/unified1/MachineReadableGrammar.html |
| 5 | //! and the files in https://github.com/KhronosGroup/SPIRV-Headers/blob/master/include/spirv/unified1/ |
| 6 | //! Note: Non-canonical casing in these structs used to match SPIR-V spec json. |
| 7 | const Registry = union(enum) { |
| 8 | core: CoreRegistry, |
| 9 | extension: ExtensionRegistry, |
| 10 | }; |
| 11 | |
| 12 | const CoreRegistry = struct { |
| 13 | copyright: [][]const u8, |
| 14 | /// Hexadecimal representation of the magic number |
| 15 | magic_number: []const u8, |
| 16 | major_version: u32, |
| 17 | minor_version: u32, |
| 18 | revision: u32, |
| 19 | instruction_printing_class: []InstructionPrintingClass, |
| 20 | instructions: []Instruction, |
| 21 | operand_kinds: []OperandKind, |
| 22 | }; |
| 23 | |
| 24 | const ExtensionRegistry = struct { |
| 25 | copyright: [][]const u8, |
| 26 | version: u32, |
| 27 | revision: u32, |
| 28 | instructions: []Instruction, |
| 29 | operand_kinds: []OperandKind = &[_]OperandKind{}, |
| 30 | }; |
| 31 | |
| 32 | const InstructionPrintingClass = struct { |
| 33 | tag: []const u8, |
| 34 | heading: ?[]const u8 = null, |
| 35 | }; |
| 36 | |
| 37 | const Instruction = struct { |
| 38 | opname: []const u8, |
| 39 | class: ?[]const u8 = null, // Note: Only available in the core registry. |
| 40 | opcode: u32, |
| 41 | operands: []Operand = &[_]Operand{}, |
| 42 | capabilities: [][]const u8 = &[_][]const u8{}, |
| 43 | extensions: [][]const u8 = &[_][]const u8{}, |
| 44 | version: ?[]const u8 = null, |
| 45 | |
| 46 | lastVersion: ?[]const u8 = null, |
| 47 | }; |
| 48 | |
| 49 | const Operand = struct { |
| 50 | kind: []const u8, |
| 51 | /// If this field is 'null', the operand is only expected once. |
| 52 | quantifier: ?Quantifier = null, |
| 53 | name: []const u8 = "", |
| 54 | }; |
| 55 | |
| 56 | const Quantifier = enum { |
| 57 | /// zero or once |
| 58 | @"?", |
| 59 | /// zero or more |
| 60 | @"*", |
| 61 | }; |
| 62 | |
| 63 | const OperandCategory = enum { |
| 64 | BitEnum, |
| 65 | ValueEnum, |
| 66 | Id, |
| 67 | Literal, |
| 68 | Composite, |
| 69 | }; |
| 70 | |
| 71 | const OperandKind = struct { |
| 72 | category: OperandCategory, |
| 73 | /// The name |
| 74 | kind: []const u8, |
| 75 | doc: ?[]const u8 = null, |
| 76 | enumerants: ?[]Enumerant = null, |
| 77 | bases: ?[]const []const u8 = null, |
| 78 | }; |
| 79 | |
| 80 | const Enumerant = struct { |
| 81 | enumerant: []const u8, |
| 82 | value: union(enum) { |
| 83 | bitflag: []const u8, // Hexadecimal representation of the value |
| 84 | int: u31, |
| 85 | }, |
| 86 | capabilities: [][]const u8 = &[_][]const u8{}, |
| 87 | /// Valid for .ValueEnum and .BitEnum |
| 88 | extensions: [][]const u8 = &[_][]const u8{}, |
| 89 | /// `quantifier` will always be `null`. |
| 90 | parameters: []Operand = &[_]Operand{}, |
| 91 | version: ?[]const u8 = null, |
| 92 | lastVersion: ?[]const u8 = null, |
| 93 | }; |
| 94 | |
| 95 | pub fn main() !void { |
| 96 | var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); |
| 97 | defer arena.deinit(); |
| 98 | const allocator = &arena.allocator; |
| 99 | |
| 100 | const args = try std.process.argsAlloc(allocator); |
| 101 | if (args.len != 2) { |
| 102 | usageAndExit(std.io.getStdErr(), args[0], 1); |
| 103 | } |
| 104 | |
| 105 | const spec_path = args[1]; |
| 106 | const spec = try std.fs.cwd().readFileAlloc(allocator, spec_path, std.math.maxInt(usize)); |
| 107 | |
| 108 | var tokens = std.json.TokenStream.init(spec); |
| 109 | var registry = try std.json.parse(Registry, &tokens, .{.allocator = allocator}); |
| 110 | |
| 111 | var buf = std.ArrayList(u8).init(allocator); |
| 112 | defer buf.deinit(); |
| 113 | |
| 114 | try render(buf.writer(), registry); |
| 115 | |
| 116 | const tree = try std.zig.parse(allocator, buf.items); |
| 117 | _ = try std.zig.render(allocator, std.io.getStdOut().writer(), tree); |
| 118 | } |
| 119 | |
| 120 | fn render(writer: Writer, registry: Registry) !void { |
| 121 | switch (registry) { |
| 122 | .core => |core_reg| { |
| 123 | try renderCopyRight(writer, core_reg.copyright); |
| 124 | try writer.print( |
| 125 | \\const Version = @import("builtin").Version; |
| 126 | \\pub const version = Version{{.major = {}, .minor = {}, .patch = {}}}; |
| 127 | \\pub const magic_number: u32 = {s}; |
| 128 | \\ |
| 129 | , .{ core_reg.major_version, core_reg.minor_version, core_reg.revision, core_reg.magic_number }, |
| 130 | ); |
| 131 | try renderOpcodes(writer, core_reg.instructions); |
| 132 | try renderOperandKinds(writer, core_reg.operand_kinds); |
| 133 | }, |
| 134 | .extension => |ext_reg| { |
| 135 | try renderCopyRight(writer, ext_reg.copyright); |
| 136 | try writer.print( |
| 137 | \\const Version = @import("builtin").Version; |
| 138 | \\pub const version = Version{{.major = {}, .minor = 0, .patch = {}}}; |
| 139 | \\ |
| 140 | , .{ ext_reg.version, ext_reg.revision }, |
| 141 | ); |
| 142 | try renderOpcodes(writer, ext_reg.instructions); |
| 143 | try renderOperandKinds(writer, ext_reg.operand_kinds); |
| 144 | } |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | fn renderCopyRight(writer: Writer, copyright: []const []const u8) !void { |
| 149 | for (copyright) |line| { |
| 150 | try writer.print("// {s}\n", .{ line }); |
| 151 | } |
| 152 | } |
| 153 | |
| 154 | fn renderOpcodes(writer: Writer, instructions: []const Instruction) !void { |
| 155 | try writer.writeAll("pub const Opcode = extern enum(u16) {\n"); |
| 156 | for (instructions) |instr| { |
| 157 | try writer.print("{} = {},\n", .{ std.zig.fmtId(instr.opname), instr.opcode }); |
| 158 | } |
| 159 | try writer.writeAll("_,\n};\n"); |
| 160 | } |
| 161 | |
| 162 | fn renderOperandKinds(writer: Writer, kinds: []const OperandKind) !void { |
| 163 | for (kinds) |kind| { |
| 164 | switch (kind.category) { |
| 165 | .ValueEnum => try renderValueEnum(writer, kind), |
| 166 | .BitEnum => try renderBitEnum(writer, kind), |
| 167 | else => {}, |
| 168 | } |
| 169 | } |
| 170 | } |
| 171 | |
| 172 | fn renderValueEnum(writer: Writer, enumeration: OperandKind) !void { |
| 173 | try writer.print("pub const {s} = extern enum(u32) {{\n", .{ enumeration.kind }); |
| 174 | |
| 175 | const enumerants = enumeration.enumerants orelse return error.InvalidRegistry; |
| 176 | for (enumerants) |enumerant| { |
| 177 | if (enumerant.value != .int) return error.InvalidRegistry; |
| 178 | |
| 179 | try writer.print("{} = {},\n", .{ std.zig.fmtId(enumerant.enumerant), enumerant.value.int }); |
| 180 | } |
| 181 | |
| 182 | try writer.writeAll("_,\n};\n"); |
| 183 | } |
| 184 | |
| 185 | fn renderBitEnum(writer: Writer, enumeration: OperandKind) !void { |
| 186 | try writer.print("pub const {s} = packed struct {{\n", .{ enumeration.kind }); |
| 187 | |
| 188 | var flags_by_bitpos = [_]?[]const u8{null} ** 32; |
| 189 | const enumerants = enumeration.enumerants orelse return error.InvalidRegistry; |
| 190 | for (enumerants) |enumerant| { |
| 191 | if (enumerant.value != .bitflag) return error.InvalidRegistry; |
| 192 | const value = try parseHexInt(enumerant.value.bitflag); |
| 193 | if (@popCount(u32, value) != 1) { |
| 194 | continue; // Skip combinations and 'none' items |
| 195 | } |
| 196 | |
| 197 | var bitpos = std.math.log2_int(u32, value); |
| 198 | if (flags_by_bitpos[bitpos]) |*existing|{ |
| 199 | // Keep the shortest |
| 200 | if (enumerant.enumerant.len < existing.len) |
| 201 | existing.* = enumerant.enumerant; |
| 202 | } else { |
| 203 | flags_by_bitpos[bitpos] = enumerant.enumerant; |
| 204 | } |
| 205 | } |
| 206 | |
| 207 | for (flags_by_bitpos) |maybe_flag_name, bitpos| { |
| 208 | if (maybe_flag_name) |flag_name| { |
| 209 | try writer.writeAll(flag_name); |
| 210 | } else { |
| 211 | try writer.print("_reserved_bit_{}", .{bitpos}); |
| 212 | } |
| 213 | |
| 214 | try writer.writeAll(": bool "); |
| 215 | if (bitpos == 0) { // Force alignment to integer boundaries |
| 216 | try writer.writeAll("align(@alignOf(u32)) "); |
| 217 | } |
| 218 | try writer.writeAll("= false, "); |
| 219 | } |
| 220 | |
| 221 | try writer.writeAll("};\n"); |
| 222 | } |
| 223 | |
| 224 | fn parseHexInt(text: []const u8) !u31 { |
| 225 | const prefix = "0x"; |
| 226 | if (!std.mem.startsWith(u8, text, prefix)) |
| 227 | return error.InvalidHexInt; |
| 228 | return try std.fmt.parseInt(u31, text[prefix.len ..], 16); |
| 229 | } |
| 230 | |
| 231 | fn usageAndExit(file: std.fs.File, arg0: []const u8, code: u8) noreturn { |
| 232 | file.writer().print( |
| 233 | \\Usage: {s} <spirv json spec> |
| 234 | \\ |
| 235 | \\Generates Zig bindings for a SPIR-V specification .json (either core or |
| 236 | \\extinst versions). The result, printed to stdout, should be used to update |
| 237 | \\files in src/codegen/spirv. |
| 238 | \\ |
| 239 | \\The relevant specifications can be obtained from the SPIR-V registry: |
| 240 | \\https://github.com/KhronosGroup/SPIRV-Headers/blob/master/include/spirv/unified1/ |
| 241 | \\ |
| 242 | , .{arg0} |
| 243 | ) catch std.process.exit(1); |
| 244 | std.process.exit(code); |
| 245 | } |