master
  1//! To get started, run this tool with no args and read the help message.
  2//!
  3//! Clang has a file "options.td" which describes all of its command line parameter options.
  4//! When using `zig cc`, Zig acts as a proxy between the user and Clang. It does not need
  5//! to understand all the parameters, but it does need to understand some of them, such as
  6//! the target. This means that Zig must understand when a C command line parameter expects
  7//! to "consume" the next parameter on the command line.
  8//!
  9//! For example, `-z -target` would mean to pass `-target` to the linker, whereas `-E -target`
 10//! would mean that the next parameter specifies the target.
 11
 12const std = @import("std");
 13const fs = std.fs;
 14const assert = std.debug.assert;
 15const json = std.json;
 16
 17const KnownOpt = struct {
 18    name: []const u8,
 19
 20    /// Corresponds to stage.zig ClangArgIterator.Kind
 21    ident: []const u8,
 22};
 23
 24const known_options = [_]KnownOpt{
 25    .{
 26        .name = "target",
 27        .ident = "target",
 28    },
 29    .{
 30        .name = "o",
 31        .ident = "o",
 32    },
 33    .{
 34        .name = "c",
 35        .ident = "c",
 36    },
 37    .{
 38        .name = "r",
 39        .ident = "r",
 40    },
 41    .{
 42        .name = "l",
 43        .ident = "l",
 44    },
 45    .{
 46        .name = "pipe",
 47        .ident = "ignore",
 48    },
 49    .{
 50        .name = "help",
 51        .ident = "driver_punt",
 52    },
 53    .{
 54        .name = "fPIC",
 55        .ident = "pic",
 56    },
 57    .{
 58        .name = "fpic",
 59        .ident = "pic",
 60    },
 61    .{
 62        .name = "fno-PIC",
 63        .ident = "no_pic",
 64    },
 65    .{
 66        .name = "fno-pic",
 67        .ident = "no_pic",
 68    },
 69    .{
 70        .name = "fPIE",
 71        .ident = "pie",
 72    },
 73    .{
 74        .name = "fpie",
 75        .ident = "pie",
 76    },
 77    .{
 78        .name = "pie",
 79        .ident = "pie",
 80    },
 81    .{
 82        .name = "fno-PIE",
 83        .ident = "no_pie",
 84    },
 85    .{
 86        .name = "fno-pie",
 87        .ident = "no_pie",
 88    },
 89    .{
 90        .name = "no-pie",
 91        .ident = "no_pie",
 92    },
 93    .{
 94        .name = "nopie",
 95        .ident = "no_pie",
 96    },
 97    .{
 98        .name = "flto",
 99        .ident = "lto",
100    },
101    .{
102        .name = "fno-lto",
103        .ident = "no_lto",
104    },
105    .{
106        .name = "funwind-tables",
107        .ident = "unwind_tables",
108    },
109    .{
110        .name = "fno-unwind-tables",
111        .ident = "no_unwind_tables",
112    },
113    .{
114        .name = "fasynchronous-unwind-tables",
115        .ident = "asynchronous_unwind_tables",
116    },
117    .{
118        .name = "fno-asynchronous-unwind-tables",
119        .ident = "no_asynchronous_unwind_tables",
120    },
121    .{
122        .name = "nolibc",
123        .ident = "nostdlib",
124    },
125    .{
126        .name = "nostdlib",
127        .ident = "nostdlib",
128    },
129    .{
130        .name = "no-standard-libraries",
131        .ident = "nostdlib",
132    },
133    .{
134        .name = "nostdlib++",
135        .ident = "nostdlib_cpp",
136    },
137    .{
138        .name = "nostdinc++",
139        .ident = "nostdlib_cpp",
140    },
141    .{
142        .name = "nostdlibinc",
143        .ident = "nostdlibinc",
144    },
145    .{
146        .name = "nostdinc",
147        .ident = "nostdlibinc",
148    },
149    .{
150        .name = "no-standard-includes",
151        .ident = "nostdlibinc",
152    },
153    .{
154        .name = "shared",
155        .ident = "shared",
156    },
157    .{
158        .name = "rdynamic",
159        .ident = "rdynamic",
160    },
161    .{
162        .name = "Wl,",
163        .ident = "wl",
164    },
165    .{
166        .name = "Wp,",
167        .ident = "wp",
168    },
169    .{
170        .name = "Xlinker",
171        .ident = "for_linker",
172    },
173    .{
174        .name = "for-linker",
175        .ident = "for_linker",
176    },
177    .{
178        .name = "for-linker=",
179        .ident = "for_linker",
180    },
181    .{
182        .name = "z",
183        .ident = "linker_input_z",
184    },
185    .{
186        .name = "E",
187        .ident = "preprocess_only",
188    },
189    .{
190        .name = "preprocess",
191        .ident = "preprocess_only",
192    },
193    .{
194        .name = "S",
195        .ident = "asm_only",
196    },
197    .{
198        .name = "assemble",
199        .ident = "asm_only",
200    },
201    .{
202        .name = "O0",
203        .ident = "optimize",
204    },
205    .{
206        .name = "O1",
207        .ident = "optimize",
208    },
209    .{
210        .name = "O2",
211        .ident = "optimize",
212    },
213    // O3 is only detected from the joined "-O" option
214    .{
215        .name = "O4",
216        .ident = "optimize",
217    },
218    .{
219        .name = "Og",
220        .ident = "optimize",
221    },
222    .{
223        .name = "Os",
224        .ident = "optimize",
225    },
226    // Oz is only detected from the joined "-O" option
227    .{
228        .name = "O",
229        .ident = "optimize",
230    },
231    .{
232        .name = "Ofast",
233        .ident = "optimize",
234    },
235    .{
236        .name = "optimize",
237        .ident = "optimize",
238    },
239    .{
240        .name = "g1",
241        .ident = "debug",
242    },
243    .{
244        .name = "gline-tables-only",
245        .ident = "debug",
246    },
247    .{
248        .name = "g",
249        .ident = "debug",
250    },
251    .{
252        .name = "debug",
253        .ident = "debug",
254    },
255    .{
256        .name = "gdwarf32",
257        .ident = "gdwarf32",
258    },
259    .{
260        .name = "gdwarf64",
261        .ident = "gdwarf64",
262    },
263    .{
264        .name = "gdwarf",
265        .ident = "debug",
266    },
267    .{
268        .name = "gdwarf-2",
269        .ident = "debug",
270    },
271    .{
272        .name = "gdwarf-3",
273        .ident = "debug",
274    },
275    .{
276        .name = "gdwarf-4",
277        .ident = "debug",
278    },
279    .{
280        .name = "gdwarf-5",
281        .ident = "debug",
282    },
283    .{
284        .name = "fsanitize",
285        .ident = "sanitize",
286    },
287    .{
288        .name = "fno-sanitize",
289        .ident = "no_sanitize",
290    },
291    .{
292        .name = "fsanitize-trap",
293        .ident = "sanitize_trap",
294    },
295    .{
296        .name = "fno-sanitize-trap",
297        .ident = "no_sanitize_trap",
298    },
299    .{
300        .name = "T",
301        .ident = "linker_script",
302    },
303    .{
304        .name = "###",
305        .ident = "dry_run",
306    },
307    .{
308        .name = "v",
309        .ident = "verbose",
310    },
311    .{
312        .name = "verbose",
313        .ident = "verbose",
314    },
315    .{
316        .name = "L",
317        .ident = "lib_dir",
318    },
319    .{
320        .name = "library-directory",
321        .ident = "lib_dir",
322    },
323    .{
324        .name = "mcpu",
325        .ident = "mcpu",
326    },
327    .{
328        .name = "march",
329        .ident = "mcpu",
330    },
331    .{
332        .name = "mtune",
333        .ident = "mcpu",
334    },
335    .{
336        .name = "mred-zone",
337        .ident = "red_zone",
338    },
339    .{
340        .name = "mno-red-zone",
341        .ident = "no_red_zone",
342    },
343    .{
344        .name = "fomit-frame-pointer",
345        .ident = "omit_frame_pointer",
346    },
347    .{
348        .name = "fno-omit-frame-pointer",
349        .ident = "no_omit_frame_pointer",
350    },
351    .{
352        .name = "ffunction-sections",
353        .ident = "function_sections",
354    },
355    .{
356        .name = "fno-function-sections",
357        .ident = "no_function_sections",
358    },
359    .{
360        .name = "fdata-sections",
361        .ident = "data_sections",
362    },
363    .{
364        .name = "fno-data-sections",
365        .ident = "no_data_sections",
366    },
367    .{
368        .name = "fbuiltin",
369        .ident = "builtin",
370    },
371    .{
372        .name = "fno-builtin",
373        .ident = "no_builtin",
374    },
375    .{
376        .name = "fcolor-diagnostics",
377        .ident = "color_diagnostics",
378    },
379    .{
380        .name = "fno-color-diagnostics",
381        .ident = "no_color_diagnostics",
382    },
383    .{
384        .name = "fcaret-diagnostics",
385        .ident = "color_diagnostics",
386    },
387    .{
388        .name = "fno-caret-diagnostics",
389        .ident = "no_color_diagnostics",
390    },
391    .{
392        .name = "fstack-check",
393        .ident = "stack_check",
394    },
395    .{
396        .name = "fno-stack-check",
397        .ident = "no_stack_check",
398    },
399    .{
400        .name = "stack-protector",
401        .ident = "stack_protector",
402    },
403    .{
404        .name = "fstack-protector",
405        .ident = "stack_protector",
406    },
407    .{
408        .name = "fno-stack-protector",
409        .ident = "no_stack_protector",
410    },
411    .{
412        .name = "fstack-protector-strong",
413        .ident = "stack_protector",
414    },
415    .{
416        .name = "fstack-protector-all",
417        .ident = "stack_protector",
418    },
419    .{
420        .name = "MD",
421        .ident = "dep_file",
422    },
423    .{
424        .name = "write-dependencies",
425        .ident = "dep_file",
426    },
427    .{
428        .name = "MV",
429        .ident = "dep_file",
430    },
431    .{
432        .name = "MF",
433        .ident = "dep_file",
434    },
435    .{
436        .name = "MT",
437        .ident = "dep_file",
438    },
439    .{
440        .name = "MG",
441        .ident = "dep_file",
442    },
443    .{
444        .name = "print-missing-file-dependencies",
445        .ident = "dep_file",
446    },
447    .{
448        .name = "MJ",
449        .ident = "dep_file",
450    },
451    .{
452        .name = "MM",
453        .ident = "dep_file_to_stdout",
454    },
455    .{
456        .name = "M",
457        .ident = "dep_file_to_stdout",
458    },
459    .{
460        .name = "user-dependencies",
461        .ident = "dep_file_to_stdout",
462    },
463    .{
464        .name = "MMD",
465        .ident = "dep_file",
466    },
467    .{
468        .name = "write-user-dependencies",
469        .ident = "dep_file",
470    },
471    .{
472        .name = "MP",
473        .ident = "dep_file",
474    },
475    .{
476        .name = "MQ",
477        .ident = "dep_file",
478    },
479    .{
480        .name = "F",
481        .ident = "framework_dir",
482    },
483    .{
484        .name = "framework",
485        .ident = "framework",
486    },
487    .{
488        .name = "s",
489        .ident = "strip",
490    },
491    .{
492        .name = "dynamiclib",
493        .ident = "shared",
494    },
495    .{
496        .name = "mexec-model",
497        .ident = "exec_model",
498    },
499    .{
500        .name = "emit-llvm",
501        .ident = "emit_llvm",
502    },
503    .{
504        .name = "sysroot",
505        .ident = "sysroot",
506    },
507    .{
508        .name = "entry",
509        .ident = "entry",
510    },
511    .{
512        .name = "e",
513        .ident = "entry",
514    },
515    .{
516        .name = "u",
517        .ident = "force_undefined_symbol",
518    },
519    .{
520        .name = "weak-l",
521        .ident = "weak_library",
522    },
523    .{
524        .name = "weak_library",
525        .ident = "weak_library",
526    },
527    .{
528        .name = "weak_framework",
529        .ident = "weak_framework",
530    },
531    .{
532        .name = "headerpad_max_install_names",
533        .ident = "headerpad_max_install_names",
534    },
535    .{
536        .name = "compress-debug-sections",
537        .ident = "compress_debug_sections",
538    },
539    .{
540        .name = "compress-debug-sections=",
541        .ident = "compress_debug_sections",
542    },
543    .{
544        .name = "install_name",
545        .ident = "install_name",
546    },
547    .{
548        .name = "undefined",
549        .ident = "undefined",
550    },
551    .{
552        .name = "x",
553        .ident = "x",
554    },
555    .{
556        .name = "ObjC",
557        .ident = "force_load_objc",
558    },
559    .{
560        .name = "municode",
561        .ident = "mingw_unicode_entry_point",
562    },
563    .{
564        .name = "fsanitize-coverage-trace-pc-guard",
565        .ident = "san_cov_trace_pc_guard",
566    },
567    .{
568        .name = "fsanitize-coverage",
569        .ident = "san_cov",
570    },
571    .{
572        .name = "fno-sanitize-coverage",
573        .ident = "no_san_cov",
574    },
575    .{
576        .name = "rtlib",
577        .ident = "rtlib",
578    },
579    .{
580        .name = "rtlib=",
581        .ident = "rtlib",
582    },
583    .{
584        .name = "static",
585        .ident = "static",
586    },
587    .{
588        .name = "dynamic",
589        .ident = "dynamic",
590    },
591};
592
593const blacklisted_options = [_][]const u8{};
594
595fn knownOption(name: []const u8) ?[]const u8 {
596    const chopped_name = if (std.mem.endsWith(u8, name, "=")) name[0 .. name.len - 1] else name;
597    for (known_options) |item| {
598        if (std.mem.eql(u8, chopped_name, item.name)) {
599            return item.ident;
600        }
601    }
602    return null;
603}
604
605const cpu_targets = struct {
606    pub const aarch64 = std.Target.aarch64;
607    pub const amdgcn = std.Target.amdgcn;
608    pub const arc = std.Target.arc;
609    pub const arm = std.Target.arm;
610    pub const avr = std.Target.avr;
611    pub const bpf = std.Target.bpf;
612    pub const csky = std.Target.csky;
613    pub const hexagon = std.Target.hexagon;
614    pub const loongarch = std.Target.loongarch;
615    pub const m68k = std.Target.m68k;
616    pub const mips = std.Target.mips;
617    pub const msp430 = std.Target.msp430;
618    pub const nvptx = std.Target.nvptx;
619    pub const powerpc = std.Target.powerpc;
620    pub const riscv = std.Target.riscv;
621    pub const s390x = std.Target.s390x;
622    pub const sparc = std.Target.sparc;
623    pub const spirv = std.Target.spirv;
624    pub const ve = std.Target.ve;
625    pub const wasm = std.Target.wasm;
626    pub const x86 = std.Target.x86;
627    pub const xtensa = std.Target.xtensa;
628};
629
630pub fn main() anyerror!void {
631    var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
632    defer arena.deinit();
633
634    const allocator = arena.allocator();
635    const args = try std.process.argsAlloc(allocator);
636
637    var stdout_buffer: [4000]u8 = undefined;
638    var stdout_writer = fs.File.stdout().writerStreaming(&stdout_buffer);
639    const stdout = &stdout_writer.interface;
640
641    if (args.len <= 1) printUsageAndExit(args[0]);
642
643    if (std.mem.eql(u8, args[1], "--help")) {
644        printUsage(stdout, args[0]) catch std.process.exit(2);
645        stdout.flush() catch std.process.exit(2);
646        std.process.exit(0);
647    }
648
649    if (args.len < 3) printUsageAndExit(args[0]);
650
651    const llvm_tblgen_exe = args[1];
652    if (std.mem.startsWith(u8, llvm_tblgen_exe, "-")) printUsageAndExit(args[0]);
653
654    const llvm_src_root = args[2];
655    if (std.mem.startsWith(u8, llvm_src_root, "-")) printUsageAndExit(args[0]);
656
657    var llvm_to_zig_cpu_features = std.StringHashMap([]const u8).init(allocator);
658
659    inline for (@typeInfo(cpu_targets).@"struct".decls) |decl| {
660        const Feature = @field(cpu_targets, decl.name).Feature;
661        const all_features = @field(cpu_targets, decl.name).all_features;
662
663        for (all_features, 0..) |feat, i| {
664            const llvm_name = feat.llvm_name orelse continue;
665            const zig_feat = @as(Feature, @enumFromInt(i));
666            const zig_name = @tagName(zig_feat);
667            try llvm_to_zig_cpu_features.put(llvm_name, zig_name);
668        }
669    }
670
671    const child_args = [_][]const u8{
672        llvm_tblgen_exe,
673        "--dump-json",
674        try std.fmt.allocPrint(allocator, "{s}/clang/include/clang/Driver/Options.td", .{llvm_src_root}),
675        try std.fmt.allocPrint(allocator, "-I={s}/llvm/include", .{llvm_src_root}),
676        try std.fmt.allocPrint(allocator, "-I={s}/clang/include/clang/Driver", .{llvm_src_root}),
677    };
678
679    const child_result = try std.process.Child.run(.{
680        .allocator = allocator,
681        .argv = &child_args,
682        .max_output_bytes = 100 * 1024 * 1024,
683    });
684
685    std.debug.print("{s}\n", .{child_result.stderr});
686
687    const json_text = switch (child_result.term) {
688        .Exited => |code| if (code == 0) child_result.stdout else {
689            std.debug.print("llvm-tblgen exited with code {d}\n", .{code});
690            std.process.exit(1);
691        },
692        else => {
693            std.debug.print("llvm-tblgen crashed\n", .{});
694            std.process.exit(1);
695        },
696    };
697
698    const parsed = try json.parseFromSlice(json.Value, allocator, json_text, .{});
699    defer parsed.deinit();
700    const root_map = &parsed.value.object;
701
702    var all_objects = std.array_list.Managed(*json.ObjectMap).init(allocator);
703    {
704        var it = root_map.iterator();
705        it_map: while (it.next()) |kv| {
706            if (kv.key_ptr.len == 0) continue;
707            if (kv.key_ptr.*[0] == '!') continue;
708            if (kv.value_ptr.* != .object) continue;
709            if (!kv.value_ptr.object.contains("NumArgs")) continue;
710            if (!kv.value_ptr.object.contains("Name")) continue;
711            for (blacklisted_options) |blacklisted_key| {
712                if (std.mem.eql(u8, blacklisted_key, kv.key_ptr.*)) continue :it_map;
713            }
714            if (kv.value_ptr.object.get("Name").?.string.len == 0) continue;
715            try all_objects.append(&kv.value_ptr.object);
716        }
717    }
718    // Some options have multiple matches. As an example, "-Wl,foo" matches both
719    // "W" and "Wl,". So we sort this list in order of descending priority.
720    std.mem.sort(*json.ObjectMap, all_objects.items, {}, objectLessThan);
721
722    try stdout.writeAll(
723        \\// This file is generated by tools/update_clang_options.zig.
724        \\// zig fmt: off
725        \\const clang_options = @import("clang_options.zig");
726        \\const CliArg = clang_options.CliArg;
727        \\const flagpd1 = clang_options.flagpd1;
728        \\const flagpsl = clang_options.flagpsl;
729        \\const joinpd1 = clang_options.joinpd1;
730        \\const jspd1 = clang_options.jspd1;
731        \\const sepd1 = clang_options.sepd1;
732        \\const m = clang_options.m;
733        \\pub const data = blk: { @setEvalBranchQuota(6000); break :blk &[_]CliArg{
734        \\
735    );
736
737    for (all_objects.items) |obj| {
738        const name = obj.get("Name").?.string;
739        var pd1 = false;
740        var pd2 = false;
741        var pslash = false;
742        for (obj.get("Prefixes").?.array.items) |prefix_json| {
743            const prefix = prefix_json.string;
744            if (std.mem.eql(u8, prefix, "-")) {
745                pd1 = true;
746            } else if (std.mem.eql(u8, prefix, "--")) {
747                pd2 = true;
748            } else if (std.mem.eql(u8, prefix, "/")) {
749                pslash = true;
750            } else {
751                std.debug.print("{s} has unrecognized prefix '{s}'\n", .{ name, prefix });
752                std.process.exit(1);
753            }
754        }
755        const syntax = objSyntax(obj) orelse continue;
756
757        if (std.mem.eql(u8, name, "MT") and syntax == .flag) {
758            // `-MT foo` is ambiguous because there is also an -MT flag
759            // The canonical way to specify the flag is with `/MT` and so we make this
760            // the only way.
761            try stdout.print("flagpsl(\"{s}\"),\n", .{name});
762        } else if (knownOption(name)) |ident| {
763
764            // Workaround the fact that in 'Options.td'  -Ofast is listed as 'joined'
765            const final_syntax = if (std.mem.eql(u8, name, "Ofast")) .flag else syntax;
766
767            try stdout.print(
768                \\.{{
769                \\    .name = "{s}",
770                \\    .syntax = {f},
771                \\    .zig_equivalent = .{s},
772                \\    .pd1 = {},
773                \\    .pd2 = {},
774                \\    .psl = {},
775                \\}},
776                \\
777            , .{ name, final_syntax, ident, pd1, pd2, pslash });
778        } else if (pd1 and !pd2 and !pslash and syntax == .flag) {
779            if ((std.mem.startsWith(u8, name, "mno-") and
780                llvm_to_zig_cpu_features.contains(name["mno-".len..])) or
781                (std.mem.startsWith(u8, name, "m") and
782                    llvm_to_zig_cpu_features.contains(name["m".len..])))
783            {
784                try stdout.print("m(\"{s}\"),\n", .{name});
785            } else {
786                try stdout.print("flagpd1(\"{s}\"),\n", .{name});
787            }
788        } else if (!pd1 and !pd2 and pslash and syntax == .flag) {
789            try stdout.print("flagpsl(\"{s}\"),\n", .{name});
790        } else if (pd1 and !pd2 and !pslash and syntax == .joined) {
791            try stdout.print("joinpd1(\"{s}\"),\n", .{name});
792        } else if (pd1 and !pd2 and !pslash and syntax == .joined_or_separate) {
793            try stdout.print("jspd1(\"{s}\"),\n", .{name});
794        } else if (pd1 and !pd2 and !pslash and syntax == .separate) {
795            try stdout.print("sepd1(\"{s}\"),\n", .{name});
796        } else {
797            try stdout.print(
798                \\.{{
799                \\    .name = "{s}",
800                \\    .syntax = {f},
801                \\    .zig_equivalent = .other,
802                \\    .pd1 = {},
803                \\    .pd2 = {},
804                \\    .psl = {},
805                \\}},
806                \\
807            , .{ name, syntax, pd1, pd2, pslash });
808        }
809    }
810
811    try stdout.writeAll(
812        \\};};
813        \\
814    );
815
816    try stdout.flush();
817}
818
819// TODO we should be able to import clang_options.zig but currently this is problematic because it will
820// import stage2.zig and that causes a bunch of stuff to get exported
821const Syntax = union(enum) {
822    /// A flag with no values.
823    flag,
824
825    /// An option which prefixes its (single) value.
826    joined,
827
828    /// An option which is followed by its value.
829    separate,
830
831    /// An option which is either joined to its (non-empty) value, or followed by its value.
832    joined_or_separate,
833
834    /// An option which is both joined to its (first) value, and followed by its (second) value.
835    joined_and_separate,
836
837    /// An option followed by its values, which are separated by commas.
838    comma_joined,
839
840    /// An option which consumes an optional joined argument and any other remaining arguments.
841    remaining_args_joined,
842
843    /// An option which is which takes multiple (separate) arguments.
844    multi_arg: u8,
845
846    pub fn format(
847        self: Syntax,
848        out_stream: *std.Io.Writer,
849    ) std.Io.Writer.Error!void {
850        switch (self) {
851            .multi_arg => |n| return out_stream.print(".{{.{t}={d}}}", .{ self, n }),
852            else => return out_stream.print(".{s}", .{@tagName(self)}),
853        }
854    }
855};
856
857fn objSyntax(obj: *json.ObjectMap) ?Syntax {
858    const num_args = @as(u8, @intCast(obj.get("NumArgs").?.integer));
859    for (obj.get("!superclasses").?.array.items) |superclass_json| {
860        const superclass = superclass_json.string;
861        if (std.mem.eql(u8, superclass, "Joined")) {
862            return .joined;
863        } else if (std.mem.eql(u8, superclass, "CLJoined")) {
864            return .joined;
865        } else if (std.mem.eql(u8, superclass, "CLIgnoredJoined")) {
866            return .joined;
867        } else if (std.mem.eql(u8, superclass, "CLCompileJoined")) {
868            return .joined;
869        } else if (std.mem.eql(u8, superclass, "CLDXCJoined")) {
870            return .joined;
871        } else if (std.mem.eql(u8, superclass, "JoinedOrSeparate")) {
872            return .joined_or_separate;
873        } else if (std.mem.eql(u8, superclass, "CLJoinedOrSeparate")) {
874            return .joined_or_separate;
875        } else if (std.mem.eql(u8, superclass, "CLCompileJoinedOrSeparate")) {
876            return .joined_or_separate;
877        } else if (std.mem.eql(u8, superclass, "DXCJoinedOrSeparate")) {
878            return .joined_or_separate;
879        } else if (std.mem.eql(u8, superclass, "CLDXCJoinedOrSeparate")) {
880            return .joined_or_separate;
881        } else if (std.mem.eql(u8, superclass, "Flag")) {
882            return .flag;
883        } else if (std.mem.eql(u8, superclass, "CLFlag")) {
884            return .flag;
885        } else if (std.mem.eql(u8, superclass, "CLIgnoredFlag")) {
886            return .flag;
887        } else if (std.mem.eql(u8, superclass, "Separate")) {
888            return .separate;
889        } else if (std.mem.eql(u8, superclass, "JoinedAndSeparate")) {
890            return .joined_and_separate;
891        } else if (std.mem.eql(u8, superclass, "CommaJoined")) {
892            return .comma_joined;
893        } else if (std.mem.eql(u8, superclass, "CLRemainingArgsJoined")) {
894            return .remaining_args_joined;
895        } else if (std.mem.eql(u8, superclass, "MultiArg")) {
896            return .{ .multi_arg = num_args };
897        }
898    }
899    const name = obj.get("Name").?.string;
900    if (std.mem.eql(u8, name, "<input>")) {
901        return .flag;
902    } else if (std.mem.eql(u8, name, "<unknown>")) {
903        return .flag;
904    }
905    const kind_def = obj.get("Kind").?.object.get("def").?.string;
906    if (std.mem.eql(u8, kind_def, "KIND_FLAG")) {
907        return .flag;
908    }
909    const key = obj.get("!name").?.string;
910    std.debug.print("{s} (key {s}) has unrecognized superclasses:\n", .{ name, key });
911    for (obj.get("!superclasses").?.array.items) |superclass_json| {
912        std.debug.print(" {s}\n", .{superclass_json.string});
913    }
914    //std.process.exit(1);
915    return null;
916}
917
918fn syntaxMatchesWithEql(syntax: Syntax) bool {
919    return switch (syntax) {
920        .flag,
921        .separate,
922        .multi_arg,
923        => true,
924
925        .joined,
926        .joined_or_separate,
927        .joined_and_separate,
928        .comma_joined,
929        .remaining_args_joined,
930        => false,
931    };
932}
933
934fn objectLessThan(context: void, a: *json.ObjectMap, b: *json.ObjectMap) bool {
935    _ = context;
936    // Priority is determined by exact matches first, followed by prefix matches in descending
937    // length, with key as a final tiebreaker.
938    const a_syntax = objSyntax(a) orelse return false;
939    const b_syntax = objSyntax(b) orelse return true;
940
941    const a_match_with_eql = syntaxMatchesWithEql(a_syntax);
942    const b_match_with_eql = syntaxMatchesWithEql(b_syntax);
943
944    if (a_match_with_eql and !b_match_with_eql) {
945        return true;
946    } else if (!a_match_with_eql and b_match_with_eql) {
947        return false;
948    }
949
950    if (!a_match_with_eql and !b_match_with_eql) {
951        const a_name = a.get("Name").?.string;
952        const b_name = b.get("Name").?.string;
953        if (a_name.len != b_name.len) {
954            return a_name.len > b_name.len;
955        }
956    }
957
958    const a_key = a.get("!name").?.string;
959    const b_key = b.get("!name").?.string;
960    return std.mem.lessThan(u8, a_key, b_key);
961}
962
963fn printUsageAndExit(arg0: []const u8) noreturn {
964    const w, _ = std.debug.lockStderrWriter(&.{});
965    defer std.debug.unlockStderrWriter();
966    printUsage(w, arg0) catch std.process.exit(2);
967    std.process.exit(1);
968}
969
970fn printUsage(w: *std.Io.Writer, arg0: []const u8) std.Io.Writer.Error!void {
971    try w.print(
972        \\Usage: {s} /path/to/llvm-tblgen /path/to/git/llvm/llvm-project
973        \\Alternative Usage: zig run /path/to/git/zig/tools/update_clang_options.zig -- /path/to/llvm-tblgen /path/to/git/llvm/llvm-project
974        \\
975        \\Prints to stdout Zig code which you can use to replace the file src/clang_options_data.zig.
976        \\
977    , .{arg0});
978}