ability to use zig cc as a drop-in C compiler

The basics are working
This commit is contained in:
Andrew Kelley 2020-03-21 10:12:28 -04:00
parent beea478acc
commit a4eaeee720
No known key found for this signature in database
GPG Key ID: 7C5F548F728501A9
11 changed files with 6295 additions and 18 deletions

View File

@ -0,0 +1,126 @@
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,
/// TODO we're going to want to change this when we start shipping self-hosted because this causes
/// all the functions in stage2.zig to get exported.
zig_equivalent: @import("stage2.zig").ClangArgIterator.ZigEquivalent,
/// Prefixed by "-"
pd1: bool = false,
/// Prefixed by "--"
pd2: bool = false,
/// Prefixed by "/"
psl: bool = false,
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,
};
fn matchEql(self: CliArg, arg: []const u8) bool {
if (self.pd1 and arg.len >= self.name.len + 1 and
mem.startsWith(u8, arg, "-") and mem.eql(u8, arg[1..], self.name))
{
return true;
}
if (self.pd2 and arg.len >= self.name.len + 2 and
mem.startsWith(u8, arg, "--") and mem.eql(u8, arg[2..], self.name))
{
return true;
}
if (self.psl and arg.len >= self.name.len + 1 and
mem.startsWith(u8, arg, "/") and mem.eql(u8, arg[1..], self.name))
{
return true;
}
return false;
}
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 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,
};
}

File diff suppressed because it is too large Load Diff

View File

@ -113,6 +113,7 @@ const Error = extern enum {
TargetHasNoDynamicLinker,
InvalidAbiVersion,
InvalidOperatingSystemVersion,
UnknownClangOption,
};
const FILE = std.c.FILE;
@ -1215,3 +1216,157 @@ fn convertSlice(slice: [][:0]u8, ptr: *[*][*:0]u8, len: *usize) !void {
}
ptr.* = new_slice.ptr;
}
const clang_args = @import("clang_options.zig").list;
// ABI warning
pub const ClangArgIterator = extern struct {
has_next: bool,
zig_equivalent: ZigEquivalent,
only_arg: [*:0]const u8,
second_arg: [*:0]const u8,
other_args_ptr: [*]const [*:0]const u8,
other_args_len: usize,
argv_ptr: [*]const [*:0]const u8,
argv_len: usize,
next_index: usize,
// ABI warning
pub const ZigEquivalent = extern enum {
target,
o,
c,
other,
positional,
l,
};
fn init(argv: []const [*:0]const u8) ClangArgIterator {
return .{
.next_index = 2, // `zig cc foo` this points to `foo`
.has_next = argv.len > 2,
.zig_equivalent = undefined,
.only_arg = undefined,
.second_arg = undefined,
.other_args_ptr = undefined,
.other_args_len = undefined,
.argv_ptr = argv.ptr,
.argv_len = argv.len,
};
}
fn next(self: *ClangArgIterator) !void {
assert(self.has_next);
assert(self.next_index < self.argv_len);
// In this state we know that the parameter we are looking at is a root parameter
// rather than an argument to a parameter.
self.other_args_ptr = self.argv_ptr + self.next_index;
self.other_args_len = 1; // We adjust this value below when necessary.
const arg = mem.span(self.argv_ptr[self.next_index]);
self.next_index += 1;
defer {
if (self.next_index >= self.argv_len) self.has_next = false;
}
if (!mem.startsWith(u8, arg, "-")) {
self.zig_equivalent = .positional;
self.only_arg = arg.ptr;
return;
}
find_clang_arg: for (clang_args) |clang_arg| switch (clang_arg.syntax) {
.flag => if (clang_arg.matchEql(arg)) {
self.zig_equivalent = clang_arg.zig_equivalent;
self.only_arg = arg.ptr;
break :find_clang_arg;
},
.joined, .comma_joined => {
// Example: --target=foo
const prefix_len = clang_arg.matchStartsWith(arg);
if (prefix_len != 0) {
self.zig_equivalent = clang_arg.zig_equivalent;
self.only_arg = arg.ptr + prefix_len; // This will skip over the "--target=" part.
break :find_clang_arg;
}
},
.joined_or_separate => {
// Examples: `-lfoo`, `-l foo`
const prefix_len = clang_arg.matchStartsWith(arg);
if (prefix_len == arg.len) {
if (self.next_index >= self.argv_len) {
std.debug.warn("Expected parameter after '{}'\n", .{arg});
process.exit(1);
}
self.only_arg = self.argv_ptr[self.next_index];
self.next_index += 1;
self.other_args_len += 1;
self.zig_equivalent = clang_arg.zig_equivalent;
break :find_clang_arg;
} else if (prefix_len != 0) {
self.zig_equivalent = clang_arg.zig_equivalent;
self.only_arg = arg.ptr + prefix_len;
break :find_clang_arg;
}
},
.joined_and_separate => {
// Example: `-Xopenmp-target=riscv64-linux-unknown foo`
const prefix_len = clang_arg.matchStartsWith(arg);
if (prefix_len != 0) {
self.only_arg = arg.ptr + prefix_len;
if (self.next_index >= self.argv_len) {
std.debug.warn("Expected parameter after '{}'\n", .{arg});
process.exit(1);
}
self.second_arg = self.argv_ptr[self.next_index];
self.next_index += 1;
self.other_args_len += 1;
self.zig_equivalent = clang_arg.zig_equivalent;
break :find_clang_arg;
}
},
.separate => if (clang_arg.matchEql(arg)) {
if (self.next_index >= self.argv_len) {
std.debug.warn("Expected parameter after '{}'\n", .{arg});
process.exit(1);
}
self.only_arg = self.argv_ptr[self.next_index];
self.next_index += 1;
self.other_args_len += 1;
self.zig_equivalent = clang_arg.zig_equivalent;
break :find_clang_arg;
},
.remaining_args_joined => {
const prefix_len = clang_arg.matchStartsWith(arg);
if (prefix_len != 0) {
@panic("TODO");
}
},
.multi_arg => if (clang_arg.matchEql(arg)) {
@panic("TODO");
},
}
else {
std.debug.warn("Unknown Clang option: '{}'\n", .{arg});
process.exit(1);
}
}
};
export fn stage2_clang_arg_iterator(
result: *ClangArgIterator,
argc: usize,
argv: [*]const [*:0]const u8,
) void {
result.* = ClangArgIterator.init(argv[0..argc]);
}
export fn stage2_clang_arg_next(it: *ClangArgIterator) Error {
it.next() catch |err| switch (err) {
error.UnknownClangOption => return .UnknownClangOption,
};
return .None;
}

View File

@ -9778,7 +9778,7 @@ static void gen_c_object(CodeGen *g, Buf *self_exe_path, CFile *c_file) {
Termination term;
ZigList<const char *> args = {};
args.append(buf_ptr(self_exe_path));
args.append("cc");
args.append("clang");
Buf *out_dep_path = buf_sprintf("%s.d", buf_ptr(out_obj_path));
add_cc_args(g, args, buf_ptr(out_dep_path), false);

View File

@ -83,6 +83,7 @@ const char *err_str(Error err) {
case ErrorTargetHasNoDynamicLinker: return "target has no dynamic linker";
case ErrorInvalidAbiVersion: return "invalid C ABI version";
case ErrorInvalidOperatingSystemVersion: return "invalid operating system version";
case ErrorUnknownClangOption: return "unknown Clang option";
}
return "(invalid error)";
}

View File

@ -2008,7 +2008,7 @@ static const char *get_def_lib(CodeGen *parent, const char *name, Buf *def_in_fi
ZigList<const char *> args = {};
args.append(buf_ptr(self_exe_path));
args.append("cc");
args.append("clang");
args.append("-x");
args.append("c");
args.append(buf_ptr(def_in_file));

View File

@ -36,7 +36,7 @@ static int print_full_usage(const char *arg0, FILE *file, int return_code) {
" build-lib [source] create library from source or object files\n"
" build-obj [source] create object from source or assembly\n"
" builtin show the source code of @import(\"builtin\")\n"
" cc C compiler\n"
" cc use Zig as a drop-in C compiler\n"
" fmt parse files and render in canonical zig format\n"
" id print the base64-encoded compiler id\n"
" init-exe initialize a `zig build` application in the cwd\n"
@ -271,7 +271,7 @@ static int main0(int argc, char **argv) {
return 0;
}
if (argc >= 2 && (strcmp(argv[1], "cc") == 0 ||
if (argc >= 2 && (strcmp(argv[1], "clang") == 0 ||
strcmp(argv[1], "-cc1") == 0 || strcmp(argv[1], "-cc1as") == 0))
{
return ZigClang_main(argc, argv);
@ -575,9 +575,55 @@ static int main0(int argc, char **argv) {
return (term.how == TerminationIdClean) ? term.code : -1;
} else if (argc >= 2 && strcmp(argv[1], "fmt") == 0) {
return stage2_fmt(argc, argv);
} else if (argc >= 2 && strcmp(argv[1], "cc") == 0) {
const char *o_arg = nullptr;
bool c_arg = false;
Stage2ClangArgIterator it;
stage2_clang_arg_iterator(&it, argc, argv);
while (it.has_next) {
if ((err = stage2_clang_arg_next(&it))) {
fprintf(stderr, "unable to parse command line parameters: %s\n", err_str(err));
return EXIT_FAILURE;
}
for (int i = 1; i < argc; i += 1) {
switch (it.kind) {
case Stage2ClangArgTarget: // example: -target riscv64-linux-unknown
target_string = it.only_arg;
break;
case Stage2ClangArgO: // -o
o_arg = it.only_arg;
break;
case Stage2ClangArgC: // -c
c_arg = true;
break;
case Stage2ClangArgOther:
for (size_t i = 0; i < it.other_args_len; i += 1) {
clang_argv.append(it.other_args_ptr[i]);
}
break;
case Stage2ClangArgPositional: {
CFile *c_file = heap::c_allocator.create<CFile>();
c_file->source_path = it.only_arg;
c_source_files.append(c_file);
break;
}
case Stage2ClangArgL: // -l
if (strcmp(it.only_arg, "c") == 0)
have_libc = true;
link_libs.append(it.only_arg);
break;
}
}
if (!c_arg) {
cmd = CmdBuild;
out_type = OutTypeExe;
if (o_arg == nullptr) {
zig_panic("TODO set out name to a.out");
}
} else {
cmd = CmdBuild;
out_type = OutTypeObj;
}
} else for (int i = 1; i < argc; i += 1) {
char *arg = argv[i];
if (arg[0] == '-') {

View File

@ -304,3 +304,15 @@ enum Error stage2_detect_native_paths(struct Stage2NativePaths *native_paths) {
return ErrorNone;
}
void stage2_clang_arg_iterator(struct Stage2ClangArgIterator *it,
size_t argc, char **argv)
{
const char *msg = "stage0 called stage2_clang_arg_iterator";
stage2_panic(msg, strlen(msg));
}
enum Error stage2_clang_arg_next(struct Stage2ClangArgIterator *it) {
const char *msg = "stage0 called stage2_clang_arg_next";
stage2_panic(msg, strlen(msg));
}

View File

@ -105,6 +105,7 @@ enum Error {
ErrorTargetHasNoDynamicLinker,
ErrorInvalidAbiVersion,
ErrorInvalidOperatingSystemVersion,
ErrorUnknownClangOption,
};
// ABI warning
@ -316,4 +317,34 @@ struct Stage2NativePaths {
// ABI warning
ZIG_EXTERN_C enum Error stage2_detect_native_paths(struct Stage2NativePaths *native_paths);
// ABI warning
enum Stage2ClangArg {
Stage2ClangArgTarget,
Stage2ClangArgO,
Stage2ClangArgC,
Stage2ClangArgOther,
Stage2ClangArgPositional,
Stage2ClangArgL,
};
// ABI warning
struct Stage2ClangArgIterator {
bool has_next;
enum Stage2ClangArg kind;
const char *only_arg;
const char *second_arg;
const char **other_args_ptr;
size_t other_args_len;
const char **argv_ptr;
size_t argv_len;
size_t next_index;
};
// ABI warning
ZIG_EXTERN_C void stage2_clang_arg_iterator(struct Stage2ClangArgIterator *it,
size_t argc, char **argv);
// ABI warning
ZIG_EXTERN_C enum Error stage2_clang_arg_next(struct Stage2ClangArgIterator *it);
#endif

View File

@ -1,14 +1,14 @@
// To get started, run this tool with no args and read the help message.
//
// The build systems of musl-libc and glibc require specifying a single target
// architecture. Meanwhile, Zig supports out-of-the-box cross compilation for
// every target. So the process to create libc headers that Zig ships is to use
// this tool.
// First, use the musl/glibc build systems to create installations of all the
// targets in the `glibc_targets`/`musl_targets` variables.
// Next, run this tool to create a new directory which puts .h files into
// <arch> subdirectories, with `generic` being files that apply to all architectures.
// You'll then have to manually update Zig source repo with these new files.
//! To get started, run this tool with no args and read the help message.
//!
//! The build systems of musl-libc and glibc require specifying a single target
//! architecture. Meanwhile, Zig supports out-of-the-box cross compilation for
//! every target. So the process to create libc headers that Zig ships is to use
//! this tool.
//! First, use the musl/glibc build systems to create installations of all the
//! targets in the `glibc_targets`/`musl_targets` variables.
//! Next, run this tool to create a new directory which puts .h files into
//! <arch> subdirectories, with `generic` being files that apply to all architectures.
//! You'll then have to manually update Zig source repo with these new files.
const std = @import("std");
const Arch = std.Target.Cpu.Arch;

View File

@ -0,0 +1,274 @@
//! To get started, run this tool with no args and read the help message.
//!
//! Clang has a file "options.td" which describes all of its command line parameter options.
//! When using `zig cc`, Zig acts as a proxy between the user and Clang. It does not need
//! to understand all the parameters, but it does need to understand some of them, such as
//! the target. This means that Zig must understand when a C command line parameter expects
//! to "consume" the next parameter on the command line.
//!
//! For example, `-z -target` would mean to pass `-target` to the linker, whereas `-E -target`
//! would mean that the next parameter specifies the target.
const std = @import("std");
const fs = std.fs;
const assert = std.debug.assert;
const json = std.json;
const KnownOpt = struct {
name: []const u8,
/// Corresponds to stage.zig ClangArgIterator.Kind
ident: []const u8,
};
const known_options = [_]KnownOpt{
.{
.name = "target",
.ident = "target",
},
.{
.name = "o",
.ident = "o",
},
.{
.name = "c",
.ident = "c",
},
.{
.name = "l",
.ident = "l",
},
};
const blacklisted_options = [_][]const u8{};
fn knownOption(name: []const u8) ?[]const u8 {
const chopped_name = if (std.mem.endsWith(u8, name, "=")) name[0 .. name.len - 1] else name;
for (known_options) |item| {
if (std.mem.eql(u8, chopped_name, item.name)) {
return item.ident;
}
}
return null;
}
pub fn main() anyerror!void {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const allocator = &arena.allocator;
const args = try std.process.argsAlloc(allocator);
if (args.len <= 1) {
usageAndExit(std.io.getStdErr(), args[0], 1);
}
if (std.mem.eql(u8, args[1], "--help")) {
usageAndExit(std.io.getStdOut(), args[0], 0);
}
if (args.len < 3) {
usageAndExit(std.io.getStdErr(), args[0], 1);
}
const llvm_tblgen_exe = args[1];
if (std.mem.startsWith(u8, llvm_tblgen_exe, "-")) {
usageAndExit(std.io.getStdErr(), args[0], 1);
}
const llvm_src_root = args[2];
if (std.mem.startsWith(u8, llvm_src_root, "-")) {
usageAndExit(std.io.getStdErr(), args[0], 1);
}
const child_args = [_][]const u8{
llvm_tblgen_exe,
"--dump-json",
try std.fmt.allocPrint(allocator, "{}/clang/include/clang/Driver/Options.td", .{llvm_src_root}),
try std.fmt.allocPrint(allocator, "-I={}/llvm/include", .{llvm_src_root}),
try std.fmt.allocPrint(allocator, "-I={}/clang/include/clang/Driver", .{llvm_src_root}),
};
const child_result = try std.ChildProcess.exec2(.{
.allocator = allocator,
.argv = &child_args,
.max_output_bytes = 100 * 1024 * 1024,
});
std.debug.warn("{}\n", .{child_result.stderr});
const json_text = switch (child_result.term) {
.Exited => |code| if (code == 0) child_result.stdout else {
std.debug.warn("llvm-tblgen exited with code {}\n", .{code});
std.process.exit(1);
},
else => {
std.debug.warn("llvm-tblgen crashed\n", .{});
std.process.exit(1);
},
};
var parser = json.Parser.init(allocator, false);
const tree = try parser.parse(json_text);
const root_map = &tree.root.Object;
var all_names = std.ArrayList([]const u8).init(allocator);
{
var it = root_map.iterator();
it_map: while (it.next()) |kv| {
if (kv.key.len == 0) continue;
if (kv.key[0] == '!') continue;
if (kv.value != .Object) continue;
if (!kv.value.Object.contains("NumArgs")) continue;
if (!kv.value.Object.contains("Name")) continue;
for (blacklisted_options) |blacklisted_key| {
if (std.mem.eql(u8, blacklisted_key, kv.key)) continue :it_map;
}
if (kv.value.Object.get("Name").?.value.String.len == 0) continue;
try all_names.append(kv.key);
}
}
std.sort.sort([]const u8, all_names.span(), nameLessThan);
var stdout_bos = std.io.bufferedOutStream(std.io.getStdOut().outStream());
const stdout = stdout_bos.outStream();
try stdout.writeAll(
\\// This file is generated by tools/update_clang_options.zig.
\\// zig fmt: off
\\usingnamespace @import("clang_options.zig");
\\pub const data = blk: { @setEvalBranchQuota(6000); break :blk &[_]CliArg{
\\
);
for (all_names.span()) |key| {
const obj = &root_map.get(key).?.value.Object;
const name = obj.get("Name").?.value.String;
var pd1 = false;
var pd2 = false;
var pslash = false;
for (obj.get("Prefixes").?.value.Array.span()) |prefix_json| {
const prefix = prefix_json.String;
if (std.mem.eql(u8, prefix, "-")) {
pd1 = true;
} else if (std.mem.eql(u8, prefix, "--")) {
pd2 = true;
} else if (std.mem.eql(u8, prefix, "/")) {
pslash = true;
} else {
std.debug.warn("{} (key {}) has unrecognized prefix '{}'\n", .{ name, key, prefix });
std.process.exit(1);
}
}
const num_args = @intCast(u8, obj.get("NumArgs").?.value.Integer);
const syntax_str: []const u8 = blk: {
for (obj.get("!superclasses").?.value.Array.span()) |superclass_json| {
const superclass = superclass_json.String;
if (std.mem.eql(u8, superclass, "Joined")) {
break :blk ".joined";
} else if (std.mem.eql(u8, superclass, "CLJoined")) {
break :blk ".joined";
} else if (std.mem.eql(u8, superclass, "CLIgnoredJoined")) {
break :blk ".joined";
} else if (std.mem.eql(u8, superclass, "CLCompileJoined")) {
break :blk ".joined";
} else if (std.mem.eql(u8, superclass, "JoinedOrSeparate")) {
break :blk ".joined_or_separate";
} else if (std.mem.eql(u8, superclass, "CLJoinedOrSeparate")) {
break :blk ".joined_or_separate";
} else if (std.mem.eql(u8, superclass, "CLCompileJoinedOrSeparate")) {
break :blk ".joined_or_separate";
} else if (std.mem.eql(u8, superclass, "Flag")) {
break :blk ".flag";
} else if (std.mem.eql(u8, superclass, "CLFlag")) {
break :blk ".flag";
} else if (std.mem.eql(u8, superclass, "CLIgnoredFlag")) {
break :blk ".flag";
} else if (std.mem.eql(u8, superclass, "Separate")) {
break :blk ".separate";
} else if (std.mem.eql(u8, superclass, "JoinedAndSeparate")) {
break :blk ".joined_and_separate";
} else if (std.mem.eql(u8, superclass, "CommaJoined")) {
break :blk ".comma_joined";
} else if (std.mem.eql(u8, superclass, "CLRemainingArgsJoined")) {
break :blk ".remaining_args_joined";
} else if (std.mem.eql(u8, superclass, "MultiArg")) {
break :blk try std.fmt.allocPrint(allocator, ".{{ .multi_arg = {} }}", .{num_args});
}
}
if (std.mem.eql(u8, name, "<input>")) {
break :blk ".flag";
} else if (std.mem.eql(u8, name, "<unknown>")) {
break :blk ".flag";
}
const kind_def = obj.get("Kind").?.value.Object.get("def").?.value.String;
if (std.mem.eql(u8, kind_def, "KIND_FLAG")) {
break :blk ".flag";
}
std.debug.warn("{} (key {}) has unrecognized superclasses:\n", .{ name, key });
for (obj.get("!superclasses").?.value.Array.span()) |superclass_json| {
std.debug.warn(" {}\n", .{superclass_json.String});
}
std.process.exit(1);
};
if (knownOption(name)) |ident| {
try stdout.print(
\\.{{
\\ .name = "{}",
\\ .syntax = {},
\\ .zig_equivalent = .{},
\\ .pd1 = {},
\\ .pd2 = {},
\\ .psl = {},
\\}},
\\
, .{ name, syntax_str, ident, pd1, pd2, pslash });
} else if (pd1 and !pd2 and !pslash and
std.mem.eql(u8, syntax_str, ".flag"))
{
try stdout.print("flagpd1(\"{}\"),\n", .{name});
} else if (pd1 and !pd2 and !pslash and
std.mem.eql(u8, syntax_str, ".joined"))
{
try stdout.print("joinpd1(\"{}\"),\n", .{name});
} else if (pd1 and !pd2 and !pslash and
std.mem.eql(u8, syntax_str, ".joined_or_separate"))
{
try stdout.print("jspd1(\"{}\"),\n", .{name});
} else if (pd1 and !pd2 and !pslash and
std.mem.eql(u8, syntax_str, ".separate"))
{
try stdout.print("sepd1(\"{}\"),\n", .{name});
} else {
try stdout.print(
\\.{{
\\ .name = "{}",
\\ .syntax = {},
\\ .zig_equivalent = .other,
\\ .pd1 = {},
\\ .pd2 = {},
\\ .psl = {},
\\}},
\\
, .{ name, syntax_str, pd1, pd2, pslash });
}
}
try stdout.writeAll(
\\};};
\\
);
try stdout_bos.flush();
}
fn nameLessThan(a: []const u8, b: []const u8) bool {
return std.mem.lessThan(u8, a, b);
}
fn usageAndExit(file: fs.File, arg0: []const u8, code: u8) noreturn {
file.outStream().print(
\\Usage: {} /path/to/llvm-tblgen /path/to/git/llvm/llvm-project
\\
\\Prints to stdout Zig code which you can use to replace the file src-self-hosted/clang_options_data.zig.
\\
, .{arg0}) catch std.process.exit(1);
std.process.exit(code);
}