Commit 724804a4e0

ippsav <69125922+ippsav@users.noreply.github.com>
2024-08-05 19:43:47
Rewrite `generate_linux_syscalls.zig` (#20895)
refactors the syscall generation tool aiming to reduce code duplication for both the table based arches and the ones generated using a preprocessor.
1 parent fab5df4
Changed files (1)
tools/generate_linux_syscalls.zig
@@ -48,100 +48,132 @@ fn isReservedNameOld(name: []const u8) bool {
         std.mem.startsWith(u8, name, "unused");
 }
 
-pub fn main() !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 < 3 or mem.eql(u8, args[1], "--help"))
-        usageAndExit(std.io.getStdErr(), args[0], 1);
-    const zig_exe = args[1];
-    const linux_path = args[2];
-
-    var buf_out = std.io.bufferedWriter(std.io.getStdOut().writer());
-    const writer = buf_out.writer();
-
-    // As of 5.17.1, the largest table is 23467 bytes.
-    // 32k should be enough for now.
-    const buf = try allocator.alloc(u8, 1 << 15);
-    const linux_dir = try std.fs.openDirAbsolute(linux_path, .{});
-
-    try writer.writeAll(
-        \\// This file is automatically generated.
-        \\// See tools/generate_linux_syscalls.zig for more info.
-        \\
-        \\
-    );
-
-    // These architectures have their syscall definitions generated from a TSV
-    // file, processed via scripts/syscallhdr.sh.
-    {
-        try writer.writeAll("pub const X86 = enum(usize) {\n");
-
-        const table = try linux_dir.readFile("arch/x86/entry/syscalls/syscall_32.tbl", buf);
-        var lines = mem.tokenizeScalar(u8, table, '\n');
-        while (lines.next()) |line| {
-            if (line[0] == '#') continue;
-
-            var fields = mem.tokenizeAny(u8, line, " \t");
-            const number = fields.next() orelse return error.Incomplete;
-            // abi is always i386
-            _ = fields.next() orelse return error.Incomplete;
-            const name = fields.next() orelse return error.Incomplete;
-            const fixed_name = if (stdlib_renames.get(name)) |fixed| fixed else name;
-
-            try writer.print("    {p} = {s},\n", .{ zig.fmtId(fixed_name), number });
-        }
-
-        try writer.writeAll("};\n\n");
-    }
-    {
-        try writer.writeAll("pub const X64 = enum(usize) {\n");
-
-        const table = try linux_dir.readFile("arch/x86/entry/syscalls/syscall_64.tbl", buf);
-        var lines = mem.tokenizeScalar(u8, table, '\n');
-        while (lines.next()) |line| {
-            if (line[0] == '#') continue;
-
-            var fields = mem.tokenizeAny(u8, line, " \t");
-            const number = fields.next() orelse return error.Incomplete;
-            const abi = fields.next() orelse return error.Incomplete;
-            // The x32 abi syscalls are always at the end.
-            if (mem.eql(u8, abi, "x32")) break;
-            const name = fields.next() orelse return error.Incomplete;
-            const fixed_name = if (stdlib_renames.get(name)) |fixed| fixed else name;
-
-            try writer.print("    {p} = {s},\n", .{ zig.fmtId(fixed_name), number });
-        }
-
-        try writer.writeAll("};\n\n");
-    }
-    {
-        try writer.writeAll(
-            \\pub const Arm = enum(usize) {
-            \\    const arm_base = 0x0f0000;
-            \\
-            \\
-        );
-
-        const table = try linux_dir.readFile("arch/arm/tools/syscall.tbl", buf);
-        var lines = mem.tokenizeScalar(u8, table, '\n');
-        while (lines.next()) |line| {
-            if (line[0] == '#') continue;
-
-            var fields = mem.tokenizeAny(u8, line, " \t");
-            const number = fields.next() orelse return error.Incomplete;
-            const abi = fields.next() orelse return error.Incomplete;
-            if (mem.eql(u8, abi, "oabi")) continue;
-            const name = fields.next() orelse return error.Incomplete;
-            const fixed_name = if (stdlib_renames.get(name)) |fixed| fixed else name;
+const default_args: []const []const u8 = &.{
+    "-E",
+    // -dM is cleaner, but -dD preserves iteration order.
+    "-dD",
+    // No need for line-markers.
+    "-P",
+    "-nostdinc",
+    // Using -I=[dir] includes the zig linux headers, which we don't want.
+    "-Itools/include",
+    "-Itools/include/uapi",
+    // Output the syscall in a format we can easily recognize.
+    "-D __SYSCALL(nr, nm)=zigsyscall nm nr",
+};
+
+const ProcessPreprocessedFileFn = *const fn (bytes: []const u8, writer: anytype) anyerror!void;
+const ProcessTableBasedArchFileFn = *const fn (
+    bytes: []const u8,
+    filters: Filters,
+    writer: anytype,
+    optional_writer: anytype,
+) anyerror!void;
+
+const FlowControl = enum {
+    @"break",
+    @"continue",
+    none,
+};
+
+const AbiCheckParams = struct { abi: []const u8, flow: FlowControl };
+
+const Filters = struct {
+    abiCheckParams: ?AbiCheckParams,
+    fixedName: ?*const fn (name: []const u8) []const u8,
+    isReservedNameOld: ?*const fn (name: []const u8) bool,
+};
+
+fn abiCheck(abi: []const u8, params: *const AbiCheckParams) FlowControl {
+    if (mem.eql(u8, abi, params.abi)) return params.flow;
+    return .none;
+}
 
-            try writer.print("    {p} = {s},\n", .{ zig.fmtId(fixed_name), number });
-        }
+fn fixedName(name: []const u8) []const u8 {
+    return if (stdlib_renames.get(name)) |fixed| fixed else name;
+}
 
-        // TODO: maybe extract these from arch/arm/include/uapi/asm/unistd.h
-        try writer.writeAll(
+const ArchInfo = union(enum) {
+    table: struct {
+        name: []const u8,
+        enum_name: []const u8,
+        file_path: []const u8,
+        header: ?[]const u8,
+        extra_values: ?[]const u8,
+        process_file: ProcessTableBasedArchFileFn,
+        filters: Filters,
+        additional_enum: ?[]const u8,
+    },
+    preprocessor: struct {
+        name: []const u8,
+        enum_name: []const u8,
+        file_path: []const u8,
+        child_options: struct {
+            comptime additional_args: ?[]const []const u8 = null,
+            target: []const u8,
+
+            pub inline fn getArgs(self: *const @This(), zig_exe: []const u8, file_path: []const u8) []const []const u8 {
+                const additional_args: []const []const u8 = self.additional_args orelse &.{};
+                return .{ zig_exe, "cc" } ++ additional_args ++ .{ "-target", self.target } ++ default_args ++ .{file_path};
+            }
+        },
+        header: ?[]const u8,
+        extra_values: ?[]const u8,
+        process_file: ProcessPreprocessedFileFn,
+        additional_enum: ?[]const u8,
+    },
+};
+
+const arch_infos = [_]ArchInfo{
+    .{
+        // These architectures have their syscall definitions generated from a TSV
+        // file, processed via scripts/syscallhdr.sh.
+        .table = .{
+            .name = "x86",
+            .enum_name = "X86",
+            .file_path = "arch/x86/entry/syscalls/syscall_32.tbl",
+            .process_file = &processTableBasedArch,
+            .filters = .{
+                .abiCheckParams = null,
+                .fixedName = &fixedName,
+                .isReservedNameOld = null,
+            },
+            .header = null,
+            .extra_values = null,
+            .additional_enum = null,
+        },
+    },
+    .{
+        .table = .{
+            .name = "x64",
+            .enum_name = "X64",
+            .file_path = "arch/x86/entry/syscalls/syscall_64.tbl",
+            .process_file = &processTableBasedArch,
+            .filters = .{
+                // The x32 abi syscalls are always at the end.
+                .abiCheckParams = .{ .abi = "x32", .flow = .@"break" },
+                .fixedName = &fixedName,
+                .isReservedNameOld = null,
+            },
+            .header = null,
+            .extra_values = null,
+            .additional_enum = null,
+        },
+    },
+    .{
+        .table = .{
+            .name = "arm",
+            .enum_name = "Arm",
+            .file_path = "arch/arm/tools/syscall.tbl",
+            .process_file = &processTableBasedArch,
+            .filters = .{
+                .abiCheckParams = .{ .abi = "oabi", .flow = .@"continue" },
+                .fixedName = &fixedName,
+                .isReservedNameOld = null,
+            },
+            .header = "    const arm_base = 0x0f0000;\n\n",
+            // TODO: maybe extract these from arch/arm/include/uapi/asm/unistd.h
+            .extra_values =
             \\
             \\    breakpoint = arm_base + 1,
             \\    cacheflush = arm_base + 2,
@@ -149,609 +181,514 @@ pub fn main() !void {
             \\    usr32 = arm_base + 4,
             \\    set_tls = arm_base + 5,
             \\    get_tls = arm_base + 6,
-            \\};
-            \\
             \\
-        );
-    }
-    {
-        try writer.writeAll("pub const Sparc = enum(usize) {\n");
-        const table = try linux_dir.readFile("arch/sparc/kernel/syscalls/syscall.tbl", buf);
-        var lines = mem.tokenizeScalar(u8, table, '\n');
-        while (lines.next()) |line| {
-            if (line[0] == '#') continue;
-
-            var fields = mem.tokenizeAny(u8, line, " \t");
-            const number = fields.next() orelse return error.Incomplete;
-            const abi = fields.next() orelse return error.Incomplete;
-            if (mem.eql(u8, abi, "64")) continue;
-            const name = fields.next() orelse return error.Incomplete;
-            const fixed_name = if (stdlib_renames.get(name)) |fixed| fixed else name;
-
-            try writer.print("    {p} = {s},\n", .{ zig.fmtId(fixed_name), number });
-        }
-
-        try writer.writeAll("};\n\n");
-    }
-    {
-        try writer.writeAll("pub const Sparc64 = enum(usize) {\n");
-        const table = try linux_dir.readFile("arch/sparc/kernel/syscalls/syscall.tbl", buf);
-        var lines = mem.tokenizeScalar(u8, table, '\n');
-        while (lines.next()) |line| {
-            if (line[0] == '#') continue;
-
-            var fields = mem.tokenizeAny(u8, line, " \t");
-            const number = fields.next() orelse return error.Incomplete;
-            const abi = fields.next() orelse return error.Incomplete;
-            if (mem.eql(u8, abi, "32")) continue;
-            const name = fields.next() orelse return error.Incomplete;
-            const fixed_name = if (stdlib_renames.get(name)) |fixed| fixed else name;
-
-            try writer.print("    {p} = {s},\n", .{ zig.fmtId(fixed_name), number });
-        }
-
-        try writer.writeAll("};\n\n");
+            ,
+            .additional_enum = null,
+        },
+    },
+    .{
+        .table = .{
+            .name = "sparc",
+            .enum_name = "Sparc",
+            .file_path = "arch/sparc/kernel/syscalls/syscall.tbl",
+            .process_file = &processTableBasedArch,
+            .filters = .{
+                .abiCheckParams = .{ .abi = "64", .flow = .@"continue" },
+                .fixedName = &fixedName,
+                .isReservedNameOld = null,
+            },
+            .header = null,
+            .extra_values = null,
+            .additional_enum = null,
+        },
+    },
+    .{
+        .table = .{
+            .name = "sparc64",
+            .enum_name = "Sparc64",
+            .file_path = "arch/sparc/kernel/syscalls/syscall.tbl",
+            .process_file = &processTableBasedArch,
+            .filters = .{
+                .abiCheckParams = .{ .abi = "32", .flow = .@"continue" },
+                .fixedName = &fixedName,
+                .isReservedNameOld = null,
+            },
+            .header = null,
+            .extra_values = null,
+            .additional_enum = null,
+        },
+    },
+    .{
+        .table = .{
+            .name = "m68k",
+            .enum_name = "M68k",
+            .file_path = "arch/m68k/kernel/syscalls/syscall.tbl",
+            .process_file = &processTableBasedArch,
+            .filters = .{
+                // abi is always common
+                .abiCheckParams = null,
+                .fixedName = &fixedName,
+                .isReservedNameOld = null,
+            },
+            .header = null,
+            .extra_values = null,
+            .additional_enum = null,
+        },
+    },
+    .{
+        .table = .{
+            .name = "mips_o32",
+            .enum_name = "MipsO32",
+            .file_path = "arch/mips/kernel/syscalls/syscall_o32.tbl",
+            .process_file = &processMipsBasedArch,
+            .filters = .{
+                // abi is always o32
+                .abiCheckParams = null,
+                .fixedName = &fixedName,
+                .isReservedNameOld = &isReservedNameOld,
+            },
+            .header = "    const linux_base = 4000;\n\n",
+            .extra_values = null,
+            .additional_enum = null,
+        },
+    },
+    .{
+        .table = .{
+            .name = "mips_n64",
+            .enum_name = "MipsN64",
+            .file_path = "arch/mips/kernel/syscalls/syscall_n64.tbl",
+            .process_file = &processMipsBasedArch,
+            .filters = .{
+                // abi is always n64
+                .abiCheckParams = null,
+                .fixedName = &fixedName,
+                .isReservedNameOld = &isReservedNameOld,
+            },
+            .header = "    const linux_base = 5000;\n\n",
+            .extra_values = null,
+            .additional_enum = null,
+        },
+    },
+    .{
+        .table = .{
+            .name = "mips_n32",
+            .enum_name = "MipsN32",
+            .file_path = "arch/mips/kernel/syscalls/syscall_n32.tbl",
+            .process_file = &processMipsBasedArch,
+            .filters = .{
+                // abi is always n32
+                .abiCheckParams = null,
+                .fixedName = &fixedName,
+                .isReservedNameOld = &isReservedNameOld,
+            },
+            .header = "    const linux_base = 6000;\n\n",
+            .extra_values = null,
+            .additional_enum = null,
+        },
+    },
+    .{
+        .table = .{
+            .name = "powerpc",
+            .enum_name = "PowerPC",
+            .file_path = "arch/powerpc/kernel/syscalls/syscall.tbl",
+            .process_file = &processPowerPcBasedArch,
+            .filters = .{
+                .abiCheckParams = null,
+                .fixedName = null,
+                .isReservedNameOld = null,
+            },
+            .header = null,
+            .extra_values = null,
+            .additional_enum = "PowerPC64",
+        },
+    },
+    .{
+        .table = .{
+            .name = "s390x",
+            .enum_name = "S390x",
+            .file_path = "arch/s390/kernel/syscalls/syscall.tbl",
+            .process_file = &processTableBasedArch,
+            .filters = .{
+                // 32-bit s390 support in linux is deprecated
+                .abiCheckParams = .{ .abi = "32", .flow = .@"continue" },
+                .fixedName = &fixedName,
+                .isReservedNameOld = null,
+            },
+            .header = null,
+            .extra_values = null,
+            .additional_enum = null,
+        },
+    },
+    .{
+        .table = .{
+            .name = "xtensa",
+            .enum_name = "Xtensa",
+            .file_path = "arch/xtensa/kernel/syscalls/syscall.tbl",
+            .process_file = &processTableBasedArch,
+            .filters = .{
+                // abi is always common
+                .abiCheckParams = null,
+                .fixedName = fixedName,
+                .isReservedNameOld = &isReservedNameOld,
+            },
+            .header = null,
+            .extra_values = null,
+            .additional_enum = null,
+        },
+    },
+    .{
+        .preprocessor = .{
+            .name = "arm64",
+            .enum_name = "Arm64",
+            .file_path = "arch/arm64/include/uapi/asm/unistd.h",
+            .child_options = .{
+                .additional_args = null,
+                .target = "aarch64-freestanding-none",
+            },
+            .process_file = &processPreprocessedFile,
+            .header = null,
+            .extra_values = null,
+            .additional_enum = null,
+        },
+    },
+    .{
+        .preprocessor = .{
+            .name = "riscv32",
+            .enum_name = "RiscV32",
+            .file_path = "arch/riscv/include/uapi/asm/unistd.h",
+            .child_options = .{
+                .additional_args = null,
+                .target = "riscv32-freestanding-none",
+            },
+            .process_file = &processPreprocessedFile,
+            .header = null,
+            .extra_values = null,
+            .additional_enum = null,
+        },
+    },
+    .{
+        .preprocessor = .{
+            .name = "riscv64",
+            .enum_name = "RiscV64",
+            .file_path = "arch/riscv/include/uapi/asm/unistd.h",
+            .child_options = .{
+                .additional_args = null,
+                .target = "riscv64-freestanding-none",
+            },
+            .process_file = &processPreprocessedFile,
+            .header = null,
+            .extra_values = null,
+            .additional_enum = null,
+        },
+    },
+    .{
+        .preprocessor = .{
+            .name = "loongarch",
+            .enum_name = "LoongArch64",
+            .file_path = "arch/loongarch/include/uapi/asm/unistd.h",
+            .child_options = .{
+                .additional_args = null,
+                .target = "loongarch64-freestanding-none",
+            },
+            .process_file = &processPreprocessedFile,
+            .header = null,
+            .extra_values = null,
+            .additional_enum = null,
+        },
+    },
+    .{
+        .preprocessor = .{
+            .name = "arc",
+            .enum_name = "Arc",
+            .file_path = "arch/arc/include/uapi/asm/unistd.h",
+            .child_options = .{
+                .additional_args = null,
+                .target = "arc-freestanding-none",
+            },
+            .process_file = &processPreprocessedFile,
+            .header = null,
+            .extra_values = null,
+            .additional_enum = null,
+        },
+    },
+    .{
+        .preprocessor = .{
+            .name = "csky",
+            .enum_name = "CSky",
+            .file_path = "arch/csky/include/uapi/asm/unistd.h",
+            .child_options = .{
+                .additional_args = null,
+                .target = "csky-freestanding-none",
+            },
+            .process_file = &processPreprocessedFile,
+            .header = null,
+            .extra_values = null,
+            .additional_enum = null,
+        },
+    },
+    .{
+        .preprocessor = .{
+            .name = "hexagon",
+            .enum_name = "Hexagon",
+            .file_path = "arch/hexagon/include/uapi/asm/unistd.h",
+            .child_options = .{
+                .additional_args = null,
+                .target = "hexagon-freestanding-none",
+            },
+            .process_file = &processPreprocessedFile,
+            .header = null,
+            .extra_values = null,
+            .additional_enum = null,
+        },
+    },
+};
+
+fn processPreprocessedFile(
+    bytes: []const u8,
+    writer: anytype,
+) !void {
+    var lines = mem.tokenizeScalar(u8, bytes, '\n');
+    while (lines.next()) |line| {
+        var fields = mem.tokenizeAny(u8, line, " ");
+        const prefix = fields.next() orelse return error.Incomplete;
+
+        if (!mem.eql(u8, prefix, "zigsyscall")) continue;
+
+        const sys_name = fields.next() orelse return error.Incomplete;
+        const value = fields.rest();
+        const name = (getOverridenNameNew(value) orelse sys_name)["sys_".len..];
+        const fixed_name = if (stdlib_renames_new.get(name)) |f| f else if (stdlib_renames.get(name)) |f| f else name;
+
+        try writer.print("    {p} = {s},\n", .{ zig.fmtId(fixed_name), value });
     }
-    {
-        try writer.writeAll("pub const M68k = enum(usize) {\n");
-
-        const table = try linux_dir.readFile("arch/m68k/kernel/syscalls/syscall.tbl", buf);
-        var lines = mem.tokenizeScalar(u8, table, '\n');
-        while (lines.next()) |line| {
-            if (line[0] == '#') continue;
-
-            var fields = mem.tokenizeAny(u8, line, " \t");
-            const number = fields.next() orelse return error.Incomplete;
-            // abi is always common
-            _ = fields.next() orelse return error.Incomplete;
-            const name = fields.next() orelse return error.Incomplete;
-            const fixed_name = if (stdlib_renames.get(name)) |fixed| fixed else name;
+}
 
-            try writer.print("    {p} = {s},\n", .{ zig.fmtId(fixed_name), number });
+fn processTableBasedArch(
+    bytes: []const u8,
+    filters: Filters,
+    writer: anytype,
+    optional_writer: anytype,
+) !void {
+    _ = optional_writer;
+
+    var lines = mem.tokenizeScalar(u8, bytes, '\n');
+    while (lines.next()) |line| {
+        if (line[0] == '#') continue;
+
+        var fields = mem.tokenizeAny(u8, line, " \t");
+        const number = fields.next() orelse return error.Incomplete;
+
+        const abi = fields.next() orelse return error.Incomplete;
+        if (filters.abiCheckParams) |*params| {
+            switch (abiCheck(abi, params)) {
+                .none => {},
+                .@"break" => break,
+                .@"continue" => continue,
+            }
         }
-
-        try writer.writeAll("};\n\n");
-    }
-    {
-        try writer.writeAll(
-            \\pub const MipsO32 = enum(usize) {
-            \\    const linux_base = 4000;
-            \\
-            \\
-        );
-
-        const table = try linux_dir.readFile("arch/mips/kernel/syscalls/syscall_o32.tbl", buf);
-        var lines = mem.tokenizeScalar(u8, table, '\n');
-        while (lines.next()) |line| {
-            if (line[0] == '#') continue;
-
-            var fields = mem.tokenizeAny(u8, line, " \t");
-            const number = fields.next() orelse return error.Incomplete;
-            // abi is always o32
-            _ = fields.next() orelse return error.Incomplete;
-            const name = fields.next() orelse return error.Incomplete;
-            if (isReservedNameOld(name)) continue;
-            const fixed_name = if (stdlib_renames.get(name)) |fixed| fixed else name;
-
-            try writer.print("    {p} = linux_base + {s},\n", .{ zig.fmtId(fixed_name), number });
+        const name = fields.next() orelse return error.Incomplete;
+        if (filters.isReservedNameOld) |isReservedNameOldFn| {
+            if (isReservedNameOldFn(name)) continue;
         }
+        const fixed_name = if (filters.fixedName) |fixedNameFn| fixedNameFn(name) else name;
 
-        try writer.writeAll("};\n\n");
+        try writer.print("    {p} = {s},\n", .{ zig.fmtId(fixed_name), number });
     }
-    {
-        try writer.writeAll(
-            \\pub const MipsN64 = enum(usize) {
-            \\    const linux_base = 5000;
-            \\
-            \\
-        );
-
-        const table = try linux_dir.readFile("arch/mips/kernel/syscalls/syscall_n64.tbl", buf);
-        var lines = mem.tokenizeScalar(u8, table, '\n');
-        while (lines.next()) |line| {
-            if (line[0] == '#') continue;
-
-            var fields = mem.tokenizeAny(u8, line, " \t");
-            const number = fields.next() orelse return error.Incomplete;
-            // abi is always n64
-            _ = fields.next() orelse return error.Incomplete;
-            const name = fields.next() orelse return error.Incomplete;
-            if (isReservedNameOld(name)) continue;
-            const fixed_name = if (stdlib_renames.get(name)) |fixed| fixed else name;
-
-            try writer.print("    {p} = linux_base + {s},\n", .{ zig.fmtId(fixed_name), number });
-        }
-
-        try writer.writeAll("};\n\n");
-    }
-    {
-        try writer.writeAll(
-            \\pub const MipsN32 = enum(usize) {
-            \\    const linux_base = 6000;
-            \\
-            \\
-        );
-
-        const table = try linux_dir.readFile("arch/mips/kernel/syscalls/syscall_n32.tbl", buf);
-        var lines = mem.tokenizeScalar(u8, table, '\n');
-        while (lines.next()) |line| {
-            if (line[0] == '#') continue;
-
-            var fields = mem.tokenizeAny(u8, line, " \t");
-            const number = fields.next() orelse return error.Incomplete;
-            // abi is always n32
-            _ = fields.next() orelse return error.Incomplete;
-            const name = fields.next() orelse return error.Incomplete;
-            if (isReservedNameOld(name)) continue;
-            const fixed_name = if (stdlib_renames.get(name)) |fixed| fixed else name;
-
-            try writer.print("    {p} = linux_base + {s},\n", .{ zig.fmtId(fixed_name), number });
-        }
+}
 
-        try writer.writeAll("};\n\n");
-    }
-    {
-        try writer.writeAll("pub const PowerPC = enum(usize) {\n");
-
-        const table = try linux_dir.readFile("arch/powerpc/kernel/syscalls/syscall.tbl", buf);
-        var list_64 = std.ArrayList(u8).init(allocator);
-        var lines = mem.tokenizeScalar(u8, table, '\n');
-        while (lines.next()) |line| {
-            if (line[0] == '#') continue;
-
-            var fields = mem.tokenizeAny(u8, line, " \t");
-            const number = fields.next() orelse return error.Incomplete;
-            const abi = fields.next() orelse return error.Incomplete;
-            const name = fields.next() orelse return error.Incomplete;
-            const fixed_name = if (stdlib_renames.get(name)) |fixed| fixed else name;
-
-            if (mem.eql(u8, abi, "spu")) {
-                continue;
-            } else if (mem.eql(u8, abi, "32")) {
-                try writer.print("    {p} = {s},\n", .{ zig.fmtId(fixed_name), number });
-            } else if (mem.eql(u8, abi, "64")) {
-                try list_64.writer().print("    {p} = {s},\n", .{ zig.fmtId(fixed_name), number });
-            } else { // common/nospu
-                try writer.print("    {p} = {s},\n", .{ zig.fmtId(fixed_name), number });
-                try list_64.writer().print("    {p} = {s},\n", .{ zig.fmtId(fixed_name), number });
+fn processMipsBasedArch(
+    bytes: []const u8,
+    filters: Filters,
+    writer: anytype,
+    optional_writer: anytype,
+) !void {
+    _ = optional_writer;
+
+    var lines = mem.tokenizeScalar(u8, bytes, '\n');
+    while (lines.next()) |line| {
+        if (line[0] == '#') continue;
+
+        var fields = mem.tokenizeAny(u8, line, " \t");
+        const number = fields.next() orelse return error.Incomplete;
+
+        const abi = fields.next() orelse return error.Incomplete;
+        if (filters.abiCheckParams) |*params| {
+            switch (abiCheck(abi, params)) {
+                .none => {},
+                .@"break" => break,
+                .@"continue" => continue,
             }
         }
-
-        try writer.writeAll(
-            \\};
-            \\
-            \\pub const PowerPC64 = enum(usize) {
-            \\
-        );
-        try writer.writeAll(list_64.items);
-        try writer.writeAll("};\n\n");
-    }
-    {
-        try writer.writeAll("pub const S390x = enum(usize) {\n");
-
-        const table = try linux_dir.readFile("arch/s390/kernel/syscalls/syscall.tbl", buf);
-        var lines = mem.tokenizeScalar(u8, table, '\n');
-        while (lines.next()) |line| {
-            if (line[0] == '#') continue;
-
-            var fields = mem.tokenizeAny(u8, line, " \t");
-            const number = fields.next() orelse return error.Incomplete;
-            const abi = fields.next() orelse return error.Incomplete;
-            if (mem.eql(u8, abi, "32")) continue; // 32-bit s390 support in linux is deprecated
-            const name = fields.next() orelse return error.Incomplete;
-            const fixed_name = if (stdlib_renames.get(name)) |fixed| fixed else name;
-
-            try writer.print("    {p} = {s},\n", .{ zig.fmtId(fixed_name), number });
+        const name = fields.next() orelse return error.Incomplete;
+        if (filters.isReservedNameOld) |isReservedNameOldFn| {
+            if (isReservedNameOldFn(name)) continue;
         }
+        const fixed_name = if (filters.fixedName) |fixedNameFn| fixedNameFn(name) else name;
 
-        try writer.writeAll("};\n\n");
+        try writer.print("    {p} = linux_base + {s},\n", .{ zig.fmtId(fixed_name), number });
     }
-    {
-        try writer.writeAll("pub const Xtensa = enum(usize) {\n");
-
-        const table = try linux_dir.readFile("arch/xtensa/kernel/syscalls/syscall.tbl", buf);
-        var lines = mem.tokenizeScalar(u8, table, '\n');
-        while (lines.next()) |line| {
-            if (line[0] == '#') continue;
-
-            var fields = mem.tokenizeAny(u8, line, " \t");
-            const number = fields.next() orelse return error.Incomplete;
-            // abi is always common
-            _ = fields.next() orelse return error.Incomplete;
-            const name = fields.next() orelse return error.Incomplete;
-            if (isReservedNameOld(name)) continue;
-            const fixed_name = if (stdlib_renames.get(name)) |fixed| fixed else name;
+}
 
+fn processPowerPcBasedArch(
+    bytes: []const u8,
+    filters: Filters,
+    writer: anytype,
+    optional_writer: anytype,
+) !void {
+    _ = filters;
+    var lines = mem.tokenizeScalar(u8, bytes, '\n');
+
+    while (lines.next()) |line| {
+        if (line[0] == '#') continue;
+
+        var fields = mem.tokenizeAny(u8, line, " \t");
+        const number = fields.next() orelse return error.Incomplete;
+        const abi = fields.next() orelse return error.Incomplete;
+        const name = fields.next() orelse return error.Incomplete;
+        const fixed_name = if (stdlib_renames.get(name)) |fixed| fixed else name;
+
+        if (mem.eql(u8, abi, "spu")) {
+            continue;
+        } else if (mem.eql(u8, abi, "32")) {
+            try writer.print("    {p} = {s},\n", .{ zig.fmtId(fixed_name), number });
+        } else if (mem.eql(u8, abi, "64")) {
+            try optional_writer.?.print("    {p} = {s},\n", .{ zig.fmtId(fixed_name), number });
+        } else { // common/nospu
             try writer.print("    {p} = {s},\n", .{ zig.fmtId(fixed_name), number });
+            try optional_writer.?.print("    {p} = {s},\n", .{ zig.fmtId(fixed_name), number });
         }
-
-        try writer.writeAll("};\n\n");
     }
+}
 
-    // Newer architectures (starting with aarch64 c. 2012) now use the same C
-    // header file for their syscall numbers. Arch-specific headers are used to
-    // define pre-proc. vars that add additional (usually obsolete) syscalls.
-    {
-        try writer.writeAll("pub const Arm64 = enum(usize) {\n");
-
-        const child_args = [_][]const u8{
-            zig_exe,
-            "cc",
-            "-target",
-            "aarch64-linux-gnu",
-            "-E",
-            // -dM is cleaner, but -dD preserves iteration order.
-            "-dD",
-            // No need for line-markers.
-            "-P",
-            "-nostdinc",
-            // Using -I=[dir] includes the zig linux headers, which we don't want.
-            "-Itools/include",
-            "-Itools/include/uapi",
-            // Output the syscall in a format we can easily recognize.
-            "-D __SYSCALL(nr, nm)=zigsyscall nm nr",
-            "arch/arm64/include/uapi/asm/unistd.h",
-        };
-
-        const child_result = try std.process.Child.run(.{
-            .allocator = allocator,
-            .argv = &child_args,
-            .cwd = linux_path,
-            .cwd_dir = linux_dir,
-        });
-        if (child_result.stderr.len > 0) std.debug.print("{s}\n", .{child_result.stderr});
-
-        const defines = switch (child_result.term) {
-            .Exited => |code| if (code == 0) child_result.stdout else {
-                std.debug.print("zig cc exited with code {d}\n", .{code});
-                std.process.exit(1);
-            },
-            else => {
-                std.debug.print("zig cc crashed\n", .{});
-                std.process.exit(1);
-            },
-        };
+fn generateSyscallsFromTable(
+    allocator: std.mem.Allocator,
+    buf: []u8,
+    linux_dir: std.fs.Dir,
+    writer: anytype,
+    _arch_info: *const ArchInfo,
+) !void {
+    std.debug.assert(_arch_info.* == .table);
 
-        var lines = mem.tokenizeScalar(u8, defines, '\n');
-        while (lines.next()) |line| {
-            var fields = mem.tokenizeAny(u8, line, " ");
-            const prefix = fields.next() orelse return error.Incomplete;
+    const arch_info = _arch_info.table;
 
-            if (!mem.eql(u8, prefix, "zigsyscall")) continue;
+    const table = try linux_dir.readFile(arch_info.file_path, buf);
 
-            const sys_name = fields.next() orelse return error.Incomplete;
-            const value = fields.rest();
-            const name = (getOverridenNameNew(value) orelse sys_name)["sys_".len..];
-            const fixed_name = if (stdlib_renames_new.get(name)) |f| f else if (stdlib_renames.get(name)) |f| f else name;
+    var optional_array_list: ?std.ArrayList(u8) = if (arch_info.additional_enum) |_| std.ArrayList(u8).init(allocator) else null;
+    const optional_writer = if (optional_array_list) |_| optional_array_list.?.writer() else null;
 
-            try writer.print("    {p} = {s},\n", .{ zig.fmtId(fixed_name), value });
-        }
+    try writer.print("pub const {s} = enum(usize) {{\n", .{arch_info.enum_name});
 
-        try writer.writeAll("};\n\n");
+    if (arch_info.header) |header| {
+        try writer.writeAll(header);
     }
-    {
-        try writer.writeAll("pub const RiscV32 = enum(usize) {\n");
-
-        const child_args = [_][]const u8{
-            zig_exe,
-            "cc",
-            "-target",
-            "riscv32-linux-gnuilp32",
-            "-E",
-            "-dD",
-            "-P",
-            "-nostdinc",
-            "-Itools/include",
-            "-Itools/include/uapi",
-            "-D __SYSCALL(nr, nm)=zigsyscall nm nr",
-            "arch/riscv/include/uapi/asm/unistd.h",
-        };
-
-        const child_result = try std.process.Child.run(.{
-            .allocator = allocator,
-            .argv = &child_args,
-            .cwd = linux_path,
-            .cwd_dir = linux_dir,
-        });
-        if (child_result.stderr.len > 0) std.debug.print("{s}\n", .{child_result.stderr});
-
-        const defines = switch (child_result.term) {
-            .Exited => |code| if (code == 0) child_result.stdout else {
-                std.debug.print("zig cc exited with code {d}\n", .{code});
-                std.process.exit(1);
-            },
-            else => {
-                std.debug.print("zig cc crashed\n", .{});
-                std.process.exit(1);
-            },
-        };
 
-        var lines = mem.tokenizeScalar(u8, defines, '\n');
-        while (lines.next()) |line| {
-            var fields = mem.tokenizeAny(u8, line, " ");
-            const prefix = fields.next() orelse return error.Incomplete;
+    try arch_info.process_file(table, arch_info.filters, writer, optional_writer);
 
-            if (!mem.eql(u8, prefix, "zigsyscall")) continue;
-
-            const sys_name = fields.next() orelse return error.Incomplete;
-            const value = fields.rest();
-            const name = (getOverridenNameNew(value) orelse sys_name)["sys_".len..];
-            const fixed_name = if (stdlib_renames_new.get(name)) |f| f else if (stdlib_renames.get(name)) |f| f else name;
-
-            try writer.print("    {p} = {s},\n", .{ zig.fmtId(fixed_name), value });
-        }
-
-        try writer.writeAll("};\n\n");
+    if (arch_info.extra_values) |extra_values| {
+        try writer.writeAll(extra_values);
     }
-    {
-        try writer.writeAll("pub const RiscV64 = enum(usize) {\n");
-
-        const child_args = [_][]const u8{
-            zig_exe,
-            "cc",
-            "-target",
-            "riscv64-linux-gnu",
-            "-E",
-            "-dD",
-            "-P",
-            "-nostdinc",
-            "-Itools/include",
-            "-Itools/include/uapi",
-            "-D __SYSCALL(nr, nm)=zigsyscall nm nr",
-            "arch/riscv/include/uapi/asm/unistd.h",
-        };
-
-        const child_result = try std.process.Child.run(.{
-            .allocator = allocator,
-            .argv = &child_args,
-            .cwd = linux_path,
-            .cwd_dir = linux_dir,
-        });
-        if (child_result.stderr.len > 0) std.debug.print("{s}\n", .{child_result.stderr});
-
-        const defines = switch (child_result.term) {
-            .Exited => |code| if (code == 0) child_result.stdout else {
-                std.debug.print("zig cc exited with code {d}\n", .{code});
-                std.process.exit(1);
-            },
-            else => {
-                std.debug.print("zig cc crashed\n", .{});
-                std.process.exit(1);
-            },
-        };
-
-        var lines = mem.tokenizeScalar(u8, defines, '\n');
-        while (lines.next()) |line| {
-            var fields = mem.tokenizeAny(u8, line, " ");
-            const prefix = fields.next() orelse return error.Incomplete;
-
-            if (!mem.eql(u8, prefix, "zigsyscall")) continue;
 
-            const sys_name = fields.next() orelse return error.Incomplete;
-            const value = fields.rest();
-            const name = (getOverridenNameNew(value) orelse sys_name)["sys_".len..];
-            const fixed_name = if (stdlib_renames_new.get(name)) |f| f else if (stdlib_renames.get(name)) |f| f else name;
-
-            try writer.print("    {p} = {s},\n", .{ zig.fmtId(fixed_name), value });
-        }
+    try writer.writeAll("};\n\n");
 
+    if (arch_info.additional_enum) |additional_enum| {
+        try writer.print("pub const {s} = enum(usize) {{\n", .{additional_enum});
+        try writer.writeAll(optional_array_list.?.items);
         try writer.writeAll("};\n\n");
     }
-    {
-        try writer.writeAll("pub const LoongArch64 = enum(usize) {\n");
-
-        const child_args = [_][]const u8{
-            zig_exe,
-            "cc",
-            "-target",
-            "loongarch64-linux-gnu",
-            "-E",
-            "-dD",
-            "-P",
-            "-nostdinc",
-            "-Itools/include",
-            "-Itools/include/uapi",
-            "-D __SYSCALL(nr, nm)=zigsyscall nm nr",
-            "arch/loongarch/include/uapi/asm/unistd.h",
-        };
-
-        const child_result = try std.process.Child.run(.{
-            .allocator = allocator,
-            .argv = &child_args,
-            .cwd = linux_path,
-            .cwd_dir = linux_dir,
-        });
-        if (child_result.stderr.len > 0) std.debug.print("{s}\n", .{child_result.stderr});
-
-        const defines = switch (child_result.term) {
-            .Exited => |code| if (code == 0) child_result.stdout else {
-                std.debug.print("zig cc exited with code {d}\n", .{code});
-                std.process.exit(1);
-            },
-            else => {
-                std.debug.print("zig cc crashed\n", .{});
-                std.process.exit(1);
-            },
-        };
-
-        var lines = mem.tokenizeScalar(u8, defines, '\n');
-        while (lines.next()) |line| {
-            var fields = mem.tokenizeAny(u8, line, " ");
-            const prefix = fields.next() orelse return error.Incomplete;
-
-            if (!mem.eql(u8, prefix, "zigsyscall")) continue;
-
-            const sys_name = fields.next() orelse return error.Incomplete;
-            const value = fields.rest();
-            const name = (getOverridenNameNew(value) orelse sys_name)["sys_".len..];
-            const fixed_name = if (stdlib_renames_new.get(name)) |f| f else if (stdlib_renames.get(name)) |f| f else name;
-
-            try writer.print("    {p} = {s},\n", .{ zig.fmtId(fixed_name), value });
-        }
+}
 
-        try writer.writeAll("};\n\n");
+fn generateSyscallsFromPreprocessor(
+    allocator: std.mem.Allocator,
+    linux_dir: std.fs.Dir,
+    linux_path: []const u8,
+    zig_exe: []const u8,
+    writer: anytype,
+    _arch_info: *const ArchInfo,
+) !void {
+    std.debug.assert(_arch_info.* == .preprocessor);
+
+    const arch_info = _arch_info.preprocessor;
+
+    const child_result = try std.process.Child.run(.{
+        .allocator = allocator,
+        .argv = arch_info.child_options.getArgs(zig_exe, arch_info.file_path),
+        .cwd = linux_path,
+        .cwd_dir = linux_dir,
+    });
+    if (child_result.stderr.len > 0) std.debug.print("{s}\n", .{child_result.stderr});
+
+    const defines = switch (child_result.term) {
+        .Exited => |code| if (code == 0) child_result.stdout else {
+            std.debug.print("zig cc exited with code {d}\n", .{code});
+            std.process.exit(1);
+        },
+        else => {
+            std.debug.print("zig cc crashed\n", .{});
+            std.process.exit(1);
+        },
+    };
+
+    try writer.print("pub const {s} = enum(usize) {{\n", .{arch_info.enum_name});
+    if (arch_info.header) |header| {
+        try writer.writeAll(header);
     }
-    {
-        try writer.writeAll("pub const Arc = enum(usize) {\n");
-
-        const child_args = [_][]const u8{
-            zig_exe,
-            "cc",
-            "-target",
-            "arc-freestanding-none",
-            "-E",
-            "-dD",
-            "-P",
-            "-nostdinc",
-            "-Itools/include",
-            "-Itools/include/uapi",
-            "-D __SYSCALL(nr, nm)=zigsyscall nm nr",
-            "arch/arc/include/uapi/asm/unistd.h",
-        };
-
-        const child_result = try std.process.Child.run(.{
-            .allocator = allocator,
-            .argv = &child_args,
-            .cwd = linux_path,
-            .cwd_dir = linux_dir,
-        });
-        if (child_result.stderr.len > 0) std.debug.print("{s}\n", .{child_result.stderr});
-
-        const defines = switch (child_result.term) {
-            .Exited => |code| if (code == 0) child_result.stdout else {
-                std.debug.print("zig cc exited with code {d}\n", .{code});
-                std.process.exit(1);
-            },
-            else => {
-                std.debug.print("zig cc crashed\n", .{});
-                std.process.exit(1);
-            },
-        };
-
-        var lines = mem.tokenizeScalar(u8, defines, '\n');
-        while (lines.next()) |line| {
-            var fields = mem.tokenizeAny(u8, line, " ");
-            const prefix = fields.next() orelse return error.Incomplete;
 
-            if (!mem.eql(u8, prefix, "zigsyscall")) continue;
-
-            const sys_name = fields.next() orelse return error.Incomplete;
-            const value = fields.rest();
-            const name = (getOverridenNameNew(value) orelse sys_name)["sys_".len..];
-            const fixed_name = if (stdlib_renames_new.get(name)) |f| f else if (stdlib_renames.get(name)) |f| f else name;
-
-            try writer.print("    {p} = {s},\n", .{ zig.fmtId(fixed_name), value });
-        }
+    try arch_info.process_file(defines, writer);
 
-        try writer.writeAll("};\n\n");
+    if (arch_info.extra_values) |extra_values| {
+        try writer.writeAll(extra_values);
     }
-    {
-        try writer.writeAll("pub const CSky = enum(usize) {\n");
-
-        const child_args = [_][]const u8{
-            zig_exe,
-            "cc",
-            "-target",
-            "csky-freestanding-none",
-            "-E",
-            "-dD",
-            "-P",
-            "-nostdinc",
-            "-Itools/include",
-            "-Itools/include/uapi",
-            "-D __SYSCALL(nr, nm)=zigsyscall nm nr",
-            "arch/csky/include/uapi/asm/unistd.h",
-        };
-
-        const child_result = try std.process.Child.run(.{
-            .allocator = allocator,
-            .argv = &child_args,
-            .cwd = linux_path,
-            .cwd_dir = linux_dir,
-        });
-        if (child_result.stderr.len > 0) std.debug.print("{s}\n", .{child_result.stderr});
-
-        const defines = switch (child_result.term) {
-            .Exited => |code| if (code == 0) child_result.stdout else {
-                std.debug.print("zig cc exited with code {d}\n", .{code});
-                std.process.exit(1);
-            },
-            else => {
-                std.debug.print("zig cc crashed\n", .{});
-                std.process.exit(1);
-            },
-        };
 
-        var lines = mem.tokenizeScalar(u8, defines, '\n');
-        while (lines.next()) |line| {
-            var fields = mem.tokenizeAny(u8, line, " ");
-            const prefix = fields.next() orelse return error.Incomplete;
-
-            if (!mem.eql(u8, prefix, "zigsyscall")) continue;
-
-            const sys_name = fields.next() orelse return error.Incomplete;
-            const value = fields.rest();
-            const name = (getOverridenNameNew(value) orelse sys_name)["sys_".len..];
-            const fixed_name = if (stdlib_renames_new.get(name)) |f| f else if (stdlib_renames.get(name)) |f| f else name;
+    try writer.writeAll("};\n\n");
+}
 
-            try writer.print("    {p} = {s},\n", .{ zig.fmtId(fixed_name), value });
-        }
+pub fn main() !void {
+    var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
+    defer arena.deinit();
+    const allocator = arena.allocator();
 
-        try writer.writeAll("};\n\n");
-    }
-    {
-        try writer.writeAll("pub const Hexagon = enum(usize) {\n");
-
-        const child_args = [_][]const u8{
-            zig_exe,
-            "cc",
-            "-target",
-            "hexagon-freestanding-none",
-            "-E",
-            "-dD",
-            "-P",
-            "-nostdinc",
-            "-Itools/include",
-            "-Itools/include/uapi",
-            "-D __SYSCALL(nr, nm)=zigsyscall nm nr",
-            "arch/hexagon/include/uapi/asm/unistd.h",
-        };
-
-        const child_result = try std.process.Child.run(.{
-            .allocator = allocator,
-            .argv = &child_args,
-            .cwd = linux_path,
-            .cwd_dir = linux_dir,
-        });
-        if (child_result.stderr.len > 0) std.debug.print("{s}\n", .{child_result.stderr});
-
-        const defines = switch (child_result.term) {
-            .Exited => |code| if (code == 0) child_result.stdout else {
-                std.debug.print("zig cc exited with code {d}\n", .{code});
-                std.process.exit(1);
-            },
-            else => {
-                std.debug.print("zig cc crashed\n", .{});
-                std.process.exit(1);
-            },
-        };
+    const args = try std.process.argsAlloc(allocator);
+    if (args.len < 3 or mem.eql(u8, args[1], "--help"))
+        usageAndExit(std.io.getStdErr(), args[0], 1);
+    const zig_exe = args[1];
+    const linux_path = args[2];
 
-        var lines = mem.tokenizeScalar(u8, defines, '\n');
-        while (lines.next()) |line| {
-            var fields = mem.tokenizeAny(u8, line, " ");
-            const prefix = fields.next() orelse return error.Incomplete;
+    var buf_out = std.io.bufferedWriter(std.io.getStdOut().writer());
+    const writer = buf_out.writer();
 
-            if (!mem.eql(u8, prefix, "zigsyscall")) continue;
+    var linux_dir = try std.fs.cwd().openDir(linux_path, .{});
+    defer linux_dir.close();
 
-            const sys_name = fields.next() orelse return error.Incomplete;
-            const value = fields.rest();
-            const name = (getOverridenNameNew(value) orelse sys_name)["sys_".len..];
-            const fixed_name = if (stdlib_renames_new.get(name)) |f| f else if (stdlib_renames.get(name)) |f| f else name;
+    try writer.writeAll(
+        \\// This file is automatically generated.
+        \\// See tools/generate_linux_syscalls.zig for more info.
+        \\
+        \\
+    );
 
-            try writer.print("    {p} = {s},\n", .{ zig.fmtId(fixed_name), value });
+    // As of 5.17.1, the largest table is 23467 bytes.
+    // 32k should be enough for now.
+    const buf = try allocator.alloc(u8, 1 << 15);
+    defer allocator.free(buf);
+
+    inline for (arch_infos) |arch_info| {
+        switch (arch_info) {
+            .table => try generateSyscallsFromTable(
+                allocator,
+                buf,
+                linux_dir,
+                writer,
+                &arch_info,
+            ),
+            .preprocessor => try generateSyscallsFromPreprocessor(
+                allocator,
+                linux_dir,
+                linux_path,
+                zig_exe,
+                writer,
+                &arch_info,
+            ),
         }
-
-        try writer.writeAll("};\n\n");
     }
 
     try buf_out.flush();