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

View File

@ -348,8 +348,6 @@ set(ZIG_STAGE2_SOURCES
src/Value.zig
src/Zcu.zig
src/Zcu/PerThread.zig
src/clang_options.zig
src/clang_options_data.zig
src/codegen.zig
src/codegen/aarch64.zig
src/codegen/aarch64/abi.zig

View File

@ -3,12 +3,14 @@
//! guarantees whatsoever.
const std = @import("std.zig");
const tokenizer = @import("zig/tokenizer.zig");
const assert = std.debug.assert;
const mem = std.mem;
const Allocator = std.mem.Allocator;
const Io = std.Io;
const Writer = std.Io.Writer;
const tokenizer = @import("zig/tokenizer.zig");
pub const ErrorBundle = @import("zig/ErrorBundle.zig");
pub const Server = @import("zig/Server.zig");
pub const Client = @import("zig/Client.zig");
@ -94,7 +96,7 @@ pub const Loc = struct {
source_line: []const u8,
pub fn eql(a: Loc, b: Loc) bool {
return a.line == b.line and a.column == b.column and std.mem.eql(u8, a.source_line, b.source_line);
return a.line == b.line and a.column == b.column and mem.eql(u8, a.source_line, b.source_line);
}
};
@ -258,7 +260,7 @@ pub const BuildId = union(enum) {
if (a_tag != b_tag) return false;
return switch (a) {
.none, .fast, .uuid, .sha1, .md5 => true,
.hexstring => |a_hexstring| std.mem.eql(u8, a_hexstring.toSlice(), b.hexstring.toSlice()),
.hexstring => |a_hexstring| mem.eql(u8, a_hexstring.toSlice(), b.hexstring.toSlice()),
};
}
@ -285,17 +287,17 @@ pub const BuildId = union(enum) {
/// Converts UTF-8 text to a `BuildId`.
pub fn parse(text: []const u8) !BuildId {
if (std.mem.eql(u8, text, "none")) {
if (mem.eql(u8, text, "none")) {
return .none;
} else if (std.mem.eql(u8, text, "fast")) {
} else if (mem.eql(u8, text, "fast")) {
return .fast;
} else if (std.mem.eql(u8, text, "uuid")) {
} else if (mem.eql(u8, text, "uuid")) {
return .uuid;
} else if (std.mem.eql(u8, text, "sha1") or std.mem.eql(u8, text, "tree")) {
} else if (mem.eql(u8, text, "sha1") or mem.eql(u8, text, "tree")) {
return .sha1;
} else if (std.mem.eql(u8, text, "md5")) {
} else if (mem.eql(u8, text, "md5")) {
return .md5;
} else if (std.mem.startsWith(u8, text, "0x")) {
} else if (mem.startsWith(u8, text, "0x")) {
var result: BuildId = .{ .hexstring = undefined };
const slice = try std.fmt.hexToBytes(&result.hexstring.bytes, text[2..]);
result.hexstring.len = @as(u8, @intCast(slice.len));
@ -614,13 +616,13 @@ pub fn readSourceFileToEndAlloc(gpa: Allocator, file_reader: *Io.File.Reader) ![
"\xfe\xff", // UTF-16 big endian
};
for (unsupported_boms) |bom| {
if (std.mem.startsWith(u8, buffer.items, bom)) {
if (mem.startsWith(u8, buffer.items, bom)) {
return error.UnsupportedEncoding;
}
}
// If the file starts with a UTF-16 little endian BOM, translate it to UTF-8
if (std.mem.startsWith(u8, buffer.items, "\xff\xfe")) {
if (mem.startsWith(u8, buffer.items, "\xff\xfe")) {
if (buffer.items.len % 2 != 0) return error.InvalidEncoding;
return std.unicode.utf16LeToUtf8AllocZ(gpa, @ptrCast(@alignCast(buffer.items))) catch |err| switch (err) {
error.DanglingSurrogateHalf => error.UnsupportedEncoding,
@ -1002,6 +1004,164 @@ pub const EmitArtifact = enum {
}
};
/// The defaults are chosen here to reduce the size of src/clang_options.zon
pub const ClangCliParam = struct {
name: []const u8,
ze: ZigEquivalent = .other,
syntax: Syntax = .flag,
/// Prefixed by "-"
pd1: bool = true,
/// Prefixed by "--"
pd2: bool = false,
/// Prefixed by "/"
psl: bool = false,
pub 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 const ZigEquivalent = enum {
target,
o,
c,
r,
m,
x,
other,
positional,
l,
ignore,
driver_punt,
pic,
no_pic,
pie,
no_pie,
lto,
no_lto,
unwind_tables,
no_unwind_tables,
asynchronous_unwind_tables,
no_asynchronous_unwind_tables,
nostdlib,
nostdlib_cpp,
shared,
rdynamic,
wl,
wp,
preprocess_only,
asm_only,
optimize,
debug,
gdwarf32,
gdwarf64,
sanitize,
no_sanitize,
sanitize_trap,
no_sanitize_trap,
linker_script,
dry_run,
verbose,
for_linker,
linker_input_z,
lib_dir,
mcpu,
dep_file,
dep_file_to_stdout,
framework_dir,
framework,
nostdlibinc,
red_zone,
no_red_zone,
omit_frame_pointer,
no_omit_frame_pointer,
function_sections,
no_function_sections,
data_sections,
no_data_sections,
builtin,
no_builtin,
color_diagnostics,
no_color_diagnostics,
stack_check,
no_stack_check,
stack_protector,
no_stack_protector,
strip,
exec_model,
emit_llvm,
sysroot,
entry,
force_undefined_symbol,
weak_library,
weak_framework,
headerpad_max_install_names,
compress_debug_sections,
install_name,
undefined,
force_load_objc,
mingw_unicode_entry_point,
san_cov_trace_pc_guard,
san_cov,
no_san_cov,
rtlib,
static,
dynamic,
};
pub fn matchEql(self: @This(), arg: []const u8) u2 {
if (self.pd1 and arg.len >= self.name.len + 1 and
mem.startsWith(u8, arg, "-") and mem.eql(u8, arg[1..], self.name))
{
return 1;
}
if (self.pd2 and arg.len >= self.name.len + 2 and
mem.startsWith(u8, arg, "--") and mem.eql(u8, arg[2..], self.name))
{
return 2;
}
if (self.psl and arg.len >= self.name.len + 1 and
mem.startsWith(u8, arg, "/") and mem.eql(u8, arg[1..], self.name))
{
return 1;
}
return 0;
}
pub fn matchStartsWith(self: @This(), arg: []const u8) usize {
if (self.pd1 and arg.len >= self.name.len + 1 and
mem.startsWith(u8, arg, "-") and mem.startsWith(u8, arg[1..], self.name))
{
return self.name.len + 1;
}
if (self.pd2 and arg.len >= self.name.len + 2 and
mem.startsWith(u8, arg, "--") and mem.startsWith(u8, arg[2..], self.name))
{
return self.name.len + 2;
}
if (self.psl and arg.len >= self.name.len + 1 and
mem.startsWith(u8, arg, "/") and mem.startsWith(u8, arg[1..], self.name))
{
return self.name.len + 1;
}
return 0;
}
};
test {
_ = Ast;
_ = AstRlAnnotate;

View File

@ -1,144 +0,0 @@
const std = @import("std");
const mem = std.mem;
pub const list = @import("clang_options_data.zig").data;
pub const CliArg = struct {
name: []const u8,
syntax: Syntax,
zig_equivalent: @import("main.zig").ClangArgIterator.ZigEquivalent,
/// Prefixed by "-"
pd1: bool = false,
/// Prefixed by "--"
pd2: bool = false,
/// Prefixed by "/"
psl: bool = false,
pub 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 matchEql(self: CliArg, arg: []const u8) u2 {
if (self.pd1 and arg.len >= self.name.len + 1 and
mem.startsWith(u8, arg, "-") and mem.eql(u8, arg[1..], self.name))
{
return 1;
}
if (self.pd2 and arg.len >= self.name.len + 2 and
mem.startsWith(u8, arg, "--") and mem.eql(u8, arg[2..], self.name))
{
return 2;
}
if (self.psl and arg.len >= self.name.len + 1 and
mem.startsWith(u8, arg, "/") and mem.eql(u8, arg[1..], self.name))
{
return 1;
}
return 0;
}
pub fn matchStartsWith(self: CliArg, arg: []const u8) usize {
if (self.pd1 and arg.len >= self.name.len + 1 and
mem.startsWith(u8, arg, "-") and mem.startsWith(u8, arg[1..], self.name))
{
return self.name.len + 1;
}
if (self.pd2 and arg.len >= self.name.len + 2 and
mem.startsWith(u8, arg, "--") and mem.startsWith(u8, arg[2..], self.name))
{
return self.name.len + 2;
}
if (self.psl and arg.len >= self.name.len + 1 and
mem.startsWith(u8, arg, "/") and mem.startsWith(u8, arg[1..], self.name))
{
return self.name.len + 1;
}
return 0;
}
};
/// Shortcut function for initializing a `CliArg`
pub fn flagpd1(name: []const u8) CliArg {
return .{
.name = name,
.syntax = .flag,
.zig_equivalent = .other,
.pd1 = true,
};
}
/// Shortcut function for initializing a `CliArg`
pub fn flagpsl(name: []const u8) CliArg {
return .{
.name = name,
.syntax = .flag,
.zig_equivalent = .other,
.psl = true,
};
}
/// Shortcut function for initializing a `CliArg`
pub fn joinpd1(name: []const u8) CliArg {
return .{
.name = name,
.syntax = .joined,
.zig_equivalent = .other,
.pd1 = true,
};
}
/// Shortcut function for initializing a `CliArg`
pub fn jspd1(name: []const u8) CliArg {
return .{
.name = name,
.syntax = .joined_or_separate,
.zig_equivalent = .other,
.pd1 = true,
};
}
/// Shortcut function for initializing a `CliArg`
pub fn sepd1(name: []const u8) CliArg {
return .{
.name = name,
.syntax = .separate,
.zig_equivalent = .other,
.pd1 = true,
};
}
/// Shortcut function for initializing a `CliArg`
pub fn m(name: []const u8) CliArg {
return .{
.name = name,
.syntax = .flag,
.zig_equivalent = .m,
.pd1 = true,
};
}

5060
src/clang_options.zon Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -6042,11 +6042,9 @@ fn initArgIteratorResponseFile(allocator: Allocator, io: Io, resp_file_path: []c
return ArgIteratorResponseFile.initTakeOwnership(allocator, cmd_line);
}
const clang_args = @import("clang_options.zig").list;
pub const ClangArgIterator = struct {
has_next: bool,
zig_equivalent: ZigEquivalent,
zig_equivalent: std.zig.ClangCliParam.ZigEquivalent,
only_arg: []const u8,
second_arg: []const u8,
other_args: []const []const u8,
@ -6056,94 +6054,6 @@ pub const ClangArgIterator = struct {
arg_iterator_response_file: ArgIteratorResponseFile,
arena: Allocator,
pub const ZigEquivalent = enum {
target,
o,
c,
r,
m,
x,
other,
positional,
l,
ignore,
driver_punt,
pic,
no_pic,
pie,
no_pie,
lto,
no_lto,
unwind_tables,
no_unwind_tables,
asynchronous_unwind_tables,
no_asynchronous_unwind_tables,
nostdlib,
nostdlib_cpp,
shared,
rdynamic,
wl,
wp,
preprocess_only,
asm_only,
optimize,
debug,
gdwarf32,
gdwarf64,
sanitize,
no_sanitize,
sanitize_trap,
no_sanitize_trap,
linker_script,
dry_run,
verbose,
for_linker,
linker_input_z,
lib_dir,
mcpu,
dep_file,
dep_file_to_stdout,
framework_dir,
framework,
nostdlibinc,
red_zone,
no_red_zone,
omit_frame_pointer,
no_omit_frame_pointer,
function_sections,
no_function_sections,
data_sections,
no_data_sections,
builtin,
no_builtin,
color_diagnostics,
no_color_diagnostics,
stack_check,
no_stack_check,
stack_protector,
no_stack_protector,
strip,
exec_model,
emit_llvm,
sysroot,
entry,
force_undefined_symbol,
weak_library,
weak_framework,
headerpad_max_install_names,
compress_debug_sections,
install_name,
undefined,
force_load_objc,
mingw_unicode_entry_point,
san_cov_trace_pc_guard,
san_cov,
no_san_cov,
rtlib,
static,
dynamic,
};
const Args = struct {
next_index: usize,
argv: []const []const u8,
@ -6223,11 +6133,13 @@ pub const ClangArgIterator = struct {
return;
}
const clang_args: []const std.zig.ClangCliParam = @import("clang_options.zon");
find_clang_arg: for (clang_args) |clang_arg| switch (clang_arg.syntax) {
.flag => {
const prefix_len = clang_arg.matchEql(arg);
if (prefix_len > 0) {
self.zig_equivalent = clang_arg.zig_equivalent;
self.zig_equivalent = clang_arg.ze;
self.only_arg = arg[prefix_len..];
break :find_clang_arg;
@ -6238,7 +6150,7 @@ pub const ClangArgIterator = struct {
// comma_joined example: -Wl,-soname,libsoundio.so.2
const prefix_len = clang_arg.matchStartsWith(arg);
if (prefix_len != 0) {
self.zig_equivalent = clang_arg.zig_equivalent;
self.zig_equivalent = clang_arg.ze;
self.only_arg = arg[prefix_len..]; // This will skip over the "--target=" part.
break :find_clang_arg;
@ -6254,11 +6166,11 @@ pub const ClangArgIterator = struct {
self.only_arg = self.argv[self.next_index];
self.incrementArgIndex();
self.other_args.len += 1;
self.zig_equivalent = clang_arg.zig_equivalent;
self.zig_equivalent = clang_arg.ze;
break :find_clang_arg;
} else if (prefix_len != 0) {
self.zig_equivalent = clang_arg.zig_equivalent;
self.zig_equivalent = clang_arg.ze;
self.only_arg = arg[prefix_len..];
break :find_clang_arg;
@ -6275,7 +6187,7 @@ pub const ClangArgIterator = struct {
self.second_arg = self.argv[self.next_index];
self.incrementArgIndex();
self.other_args.len += 1;
self.zig_equivalent = clang_arg.zig_equivalent;
self.zig_equivalent = clang_arg.ze;
break :find_clang_arg;
}
},
@ -6286,7 +6198,7 @@ pub const ClangArgIterator = struct {
self.only_arg = self.argv[self.next_index];
self.incrementArgIndex();
self.other_args.len += 1;
self.zig_equivalent = clang_arg.zig_equivalent;
self.zig_equivalent = clang_arg.ze;
break :find_clang_arg;
},
.remaining_args_joined => {
@ -6302,7 +6214,7 @@ pub const ClangArgIterator = struct {
self.incrementArgIndex();
self.other_args.len += 1;
}
self.zig_equivalent = clang_arg.zig_equivalent;
self.zig_equivalent = clang_arg.ze;
break :find_clang_arg;
},
} else {

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});
}