CLI: use zon format for clang options

- plain old data ftw
- 177K -> 151K
- data bypasses Sema

This change is not really important but it was nice to explore best
practices for data like this.

When I measured building the compiler, I found no statistically
significant difference in compilation time.
This commit is contained in:
Andrew Kelley
2026-03-19 17:25:40 -07:00
parent 982f26bcdd
commit 06b85a4fd0
7 changed files with 5269 additions and 8978 deletions
+28 -101
View File
@@ -13,6 +13,8 @@ const std = @import("std");
const Io = std.Io;
const assert = std.debug.assert;
const json = std.json;
const fatal = std.process.fatal;
const ClangCliParam = std.zig.ClangCliParam;
const KnownOpt = struct {
name: []const u8,
@@ -682,13 +684,9 @@ pub fn main(init: std.process.Init) !void {
const json_text = switch (child_result.term) {
.exited => |code| if (code == 0) child_result.stdout else {
std.debug.print("llvm-tblgen exited with code {d}\n", .{code});
std.process.exit(1);
},
else => {
std.debug.print("llvm-tblgen crashed\n", .{});
std.process.exit(1);
fatal("llvm-tblgen exited with code {d}", .{code});
},
else => fatal("llvm-tblgen crashed", .{}),
};
const parsed = try json.parseFromSlice(json.Value, arena, json_text, .{});
@@ -718,18 +716,13 @@ pub fn main(init: std.process.Init) !void {
try stdout.writeAll(
\\// This file is generated by tools/update_clang_options.zig.
\\// zig fmt: off
\\const clang_options = @import("clang_options.zig");
\\const CliArg = clang_options.CliArg;
\\const flagpd1 = clang_options.flagpd1;
\\const flagpsl = clang_options.flagpsl;
\\const joinpd1 = clang_options.joinpd1;
\\const jspd1 = clang_options.jspd1;
\\const sepd1 = clang_options.sepd1;
\\const m = clang_options.m;
\\pub const data = blk: { @setEvalBranchQuota(6000); break :blk &[_]CliArg{
\\
);
var serializer: std.zon.Serializer = .{ .writer = stdout };
var top = try serializer.beginTuple(.{});
serializer.indent_level = 0;
for (all_objects.items) |obj| {
const name = obj.get("Name").?.string;
var pd1 = false;
@@ -744,113 +737,48 @@ pub fn main(init: std.process.Init) !void {
} else if (std.mem.eql(u8, prefix, "/")) {
pslash = true;
} else {
std.debug.print("{s} has unrecognized prefix '{s}'\n", .{ name, prefix });
std.process.exit(1);
fatal("{s} has unrecognized prefix '{s}'", .{ name, prefix });
}
}
const syntax = objSyntax(obj) orelse continue;
var element: ClangCliParam = .{
.name = name,
.syntax = syntax,
.pd1 = pd1,
.pd2 = pd2,
.psl = pslash,
};
if (std.mem.eql(u8, name, "MT") and syntax == .flag) {
// `-MT foo` is ambiguous because there is also an -MT flag
// The canonical way to specify the flag is with `/MT` and so we make this
// the only way.
try stdout.print("flagpsl(\"{s}\"),\n", .{name});
element.psl = true;
element.pd1 = false;
element.pd2 = false;
} else if (knownOption(name)) |ident| {
// Workaround the fact that in 'Options.td' -Ofast is listed as 'joined'
const final_syntax = if (std.mem.eql(u8, name, "Ofast")) .flag else syntax;
try stdout.print(
\\.{{
\\ .name = "{s}",
\\ .syntax = {f},
\\ .zig_equivalent = .{s},
\\ .pd1 = {},
\\ .pd2 = {},
\\ .psl = {},
\\}},
\\
, .{ name, final_syntax, ident, pd1, pd2, pslash });
if (std.mem.eql(u8, name, "Ofast")) element.syntax = .flag;
element.ze = std.meta.stringToEnum(ClangCliParam.ZigEquivalent, ident) orelse fatal("unknown known option: {s}", .{ident});
} else if (pd1 and !pd2 and !pslash and syntax == .flag) {
if ((std.mem.startsWith(u8, name, "mno-") and
llvm_to_zig_cpu_features.contains(name["mno-".len..])) or
(std.mem.startsWith(u8, name, "m") and
llvm_to_zig_cpu_features.contains(name["m".len..])))
{
try stdout.print("m(\"{s}\"),\n", .{name});
} else {
try stdout.print("flagpd1(\"{s}\"),\n", .{name});
element.ze = .m;
}
} else if (!pd1 and !pd2 and pslash and syntax == .flag) {
try stdout.print("flagpsl(\"{s}\"),\n", .{name});
} else if (pd1 and !pd2 and !pslash and syntax == .joined) {
try stdout.print("joinpd1(\"{s}\"),\n", .{name});
} else if (pd1 and !pd2 and !pslash and syntax == .joined_or_separate) {
try stdout.print("jspd1(\"{s}\"),\n", .{name});
} else if (pd1 and !pd2 and !pslash and syntax == .separate) {
try stdout.print("sepd1(\"{s}\"),\n", .{name});
} else {
try stdout.print(
\\.{{
\\ .name = "{s}",
\\ .syntax = {f},
\\ .zig_equivalent = .other,
\\ .pd1 = {},
\\ .pd2 = {},
\\ .psl = {},
\\}},
\\
, .{ name, syntax, pd1, pd2, pslash });
}
try top.field(element, .{ .emit_default_optional_fields = false });
}
try stdout.writeAll(
\\};};
\\
);
try top.end();
try stdout.flush();
}
// TODO we should be able to import clang_options.zig but currently this is problematic because it will
// import stage2.zig and that causes a bunch of stuff to get exported
const Syntax = union(enum) {
/// A flag with no values.
flag,
/// An option which prefixes its (single) value.
joined,
/// An option which is followed by its value.
separate,
/// An option which is either joined to its (non-empty) value, or followed by its value.
joined_or_separate,
/// An option which is both joined to its (first) value, and followed by its (second) value.
joined_and_separate,
/// An option followed by its values, which are separated by commas.
comma_joined,
/// An option which consumes an optional joined argument and any other remaining arguments.
remaining_args_joined,
/// An option which is which takes multiple (separate) arguments.
multi_arg: u8,
pub fn format(
self: Syntax,
out_stream: *std.Io.Writer,
) std.Io.Writer.Error!void {
switch (self) {
.multi_arg => |n| return out_stream.print(".{{.{t}={d}}}", .{ self, n }),
else => return out_stream.print(".{s}", .{@tagName(self)}),
}
}
};
fn objSyntax(obj: *json.ObjectMap) ?Syntax {
fn objSyntax(obj: *json.ObjectMap) ?ClangCliParam.Syntax {
const num_args = @as(u8, @intCast(obj.get("NumArgs").?.integer));
for (obj.get("!superclasses").?.array.items) |superclass_json| {
const superclass = superclass_json.string;
@@ -911,7 +839,7 @@ fn objSyntax(obj: *json.ObjectMap) ?Syntax {
return null;
}
fn syntaxMatchesWithEql(syntax: Syntax) bool {
fn syntaxMatchesWithEql(syntax: ClangCliParam.Syntax) bool {
return switch (syntax) {
.flag,
.separate,
@@ -966,9 +894,8 @@ fn printUsageAndExit(arg0: []const u8) noreturn {
fn printUsage(w: *std.Io.Writer, arg0: []const u8) std.Io.Writer.Error!void {
try w.print(
\\Usage: {s} /path/to/llvm-tblgen /path/to/git/llvm/llvm-project
\\Alternative Usage: zig run /path/to/git/zig/tools/update_clang_options.zig -- /path/to/llvm-tblgen /path/to/git/llvm/llvm-project
\\
\\Prints to stdout Zig code which you can use to replace the file src/clang_options_data.zig.
\\Prints to stdout Zig code which you can use to replace the file src/clang_options.zon.
\\
, .{arg0});
}