aboutsummaryrefslogtreecommitdiff
path: root/tools/update_glibc.zig
blob: 1e23bf0ff80191c804e3e6062393b8b63a549c18 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
const std = @import("std");
const fs = std.fs;
const fmt = std.fmt;
const assert = std.debug.assert;

// Example abilist path:
// ./sysdeps/unix/sysv/linux/aarch64/libc.abilist
const AbiList = struct {
    targets: []const ZigTarget,
    path: []const u8,
};
const ZigTarget = struct {
    arch: std.Target.Cpu.Arch,
    abi: std.Target.Abi,
};

const lib_names = [_][]const u8{
    "c",
    "dl",
    "m",
    "pthread",
    "rt",
    "ld",
    "util",
};

// fpu/nofpu are hardcoded elsewhere, based on .gnueabi/.gnueabihf with an exception for .arm
// n64/n32 are hardcoded elsewhere, based on .gnuabi64/.gnuabin32
const abi_lists = [_]AbiList{
    AbiList{
        .targets = &[_]ZigTarget{
            ZigTarget{ .arch = .aarch64, .abi = .gnu },
            ZigTarget{ .arch = .aarch64_be, .abi = .gnu },
        },
        .path = "aarch64",
    },
    AbiList{
        .targets = &[_]ZigTarget{ZigTarget{ .arch = .s390x, .abi = .gnu }},
        .path = "s390/s390-64",
    },
    AbiList{
        .targets = &[_]ZigTarget{
            ZigTarget{ .arch = .arm, .abi = .gnueabi },
            ZigTarget{ .arch = .armeb, .abi = .gnueabi },
            ZigTarget{ .arch = .arm, .abi = .gnueabihf },
            ZigTarget{ .arch = .armeb, .abi = .gnueabihf },
        },
        .path = "arm",
    },
    AbiList{
        .targets = &[_]ZigTarget{
            ZigTarget{ .arch = .sparc, .abi = .gnu },
            ZigTarget{ .arch = .sparcel, .abi = .gnu },
        },
        .path = "sparc/sparc32",
    },
    AbiList{
        .targets = &[_]ZigTarget{ZigTarget{ .arch = .sparcv9, .abi = .gnu }},
        .path = "sparc/sparc64",
    },
    AbiList{
        .targets = &[_]ZigTarget{
            ZigTarget{ .arch = .mips64el, .abi = .gnuabi64 },
            ZigTarget{ .arch = .mips64, .abi = .gnuabi64 },
        },
        .path = "mips/mips64",
    },
    AbiList{
        .targets = &[_]ZigTarget{
            ZigTarget{ .arch = .mips64el, .abi = .gnuabin32 },
            ZigTarget{ .arch = .mips64, .abi = .gnuabin32 },
        },
        .path = "mips/mips64",
    },
    AbiList{
        .targets = &[_]ZigTarget{
            ZigTarget{ .arch = .mipsel, .abi = .gnueabihf },
            ZigTarget{ .arch = .mips, .abi = .gnueabihf },
        },
        .path = "mips/mips32",
    },
    AbiList{
        .targets = &[_]ZigTarget{
            ZigTarget{ .arch = .mipsel, .abi = .gnueabi },
            ZigTarget{ .arch = .mips, .abi = .gnueabi },
        },
        .path = "mips/mips32",
    },
    AbiList{
        .targets = &[_]ZigTarget{ZigTarget{ .arch = .x86_64, .abi = .gnu }},
        .path = "x86_64/64",
    },
    AbiList{
        .targets = &[_]ZigTarget{ZigTarget{ .arch = .x86_64, .abi = .gnux32 }},
        .path = "x86_64/x32",
    },
    AbiList{
        .targets = &[_]ZigTarget{ZigTarget{ .arch = .i386, .abi = .gnu }},
        .path = "i386",
    },
    AbiList{
        .targets = &[_]ZigTarget{ZigTarget{ .arch = .powerpc64le, .abi = .gnu }},
        .path = "powerpc/powerpc64/le",
    },
    AbiList{
        .targets = &[_]ZigTarget{ZigTarget{ .arch = .powerpc64, .abi = .gnu }},
        .path = "powerpc/powerpc64/be",
    },
    AbiList{
        .targets = &[_]ZigTarget{
            ZigTarget{ .arch = .powerpc, .abi = .gnueabi },
            ZigTarget{ .arch = .powerpc, .abi = .gnueabihf },
        },
        .path = "powerpc/powerpc32",
    },
};

const FunctionSet = struct {
    list: std.ArrayList(VersionedFn),
    fn_vers_list: FnVersionList,
};
const FnVersionList = std.StringHashMap(std.ArrayList(usize));

const VersionedFn = struct {
    ver: []const u8, // example: "GLIBC_2.15"
    name: []const u8, // example: "puts"
};
const Function = struct {
    name: []const u8, // example: "puts"
    lib: []const u8, // example: "c"
    index: usize,
};

pub fn main() !void {
    var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
    const allocator = &arena.allocator;
    const args = try std.process.argsAlloc(allocator);
    const in_glibc_dir = args[1]; // path to the unzipped tarball of glibc, e.g. ~/downloads/glibc-2.25
    const zig_src_dir = args[2]; // path to the source checkout of zig, lib dir, e.g. ~/zig-src/lib

    const prefix = try fs.path.join(allocator, &[_][]const u8{ in_glibc_dir, "sysdeps", "unix", "sysv", "linux" });
    const glibc_out_dir = try fs.path.join(allocator, &[_][]const u8{ zig_src_dir, "libc", "glibc" });

    var global_fn_set = std.StringHashMap(Function).init(allocator);
    var global_ver_set = std.StringHashMap(usize).init(allocator);
    var target_functions = std.AutoHashMap(usize, FunctionSet).init(allocator);

    for (abi_lists) |*abi_list| {
        const target_funcs_gop = try target_functions.getOrPut(@ptrToInt(abi_list));
        if (!target_funcs_gop.found_existing) {
            target_funcs_gop.entry.value = FunctionSet{
                .list = std.ArrayList(VersionedFn).init(allocator),
                .fn_vers_list = FnVersionList.init(allocator),
            };
        }
        const fn_set = &target_funcs_gop.entry.value.list;

        for (lib_names) |lib_name, lib_name_index| {
            const lib_prefix = if (std.mem.eql(u8, lib_name, "ld")) "" else "lib";
            const basename = try fmt.allocPrint(allocator, "{}{}.abilist", .{ lib_prefix, lib_name });
            const abi_list_filename = blk: {
                const is_c = std.mem.eql(u8, lib_name, "c");
                const is_m = std.mem.eql(u8, lib_name, "m");
                const is_ld = std.mem.eql(u8, lib_name, "ld");
                if (abi_list.targets[0].abi == .gnuabi64 and (is_c or is_ld)) {
                    break :blk try fs.path.join(allocator, &[_][]const u8{ prefix, abi_list.path, "n64", basename });
                } else if (abi_list.targets[0].abi == .gnuabin32 and (is_c or is_ld)) {
                    break :blk try fs.path.join(allocator, &[_][]const u8{ prefix, abi_list.path, "n32", basename });
                } else if (abi_list.targets[0].arch != .arm and
                    abi_list.targets[0].abi == .gnueabihf and
                    (is_c or (is_m and abi_list.targets[0].arch == .powerpc)))
                {
                    break :blk try fs.path.join(allocator, &[_][]const u8{ prefix, abi_list.path, "fpu", basename });
                } else if (abi_list.targets[0].arch != .arm and
                    abi_list.targets[0].abi == .gnueabi and
                    (is_c or (is_m and abi_list.targets[0].arch == .powerpc)))
                {
                    break :blk try fs.path.join(allocator, &[_][]const u8{ prefix, abi_list.path, "nofpu", basename });
                } else if (abi_list.targets[0].arch == .arm) {
                    break :blk try fs.path.join(allocator, &[_][]const u8{ prefix, abi_list.path, "le", basename });
                } else if (abi_list.targets[0].arch == .armeb) {
                    break :blk try fs.path.join(allocator, &[_][]const u8{ prefix, abi_list.path, "be", basename });
                }
                break :blk try fs.path.join(allocator, &[_][]const u8{ prefix, abi_list.path, basename });
            };
            const max_bytes = 10 * 1024 * 1024;
            const contents = std.fs.cwd().readFileAlloc(allocator, abi_list_filename, max_bytes) catch |err| {
                std.debug.warn("unable to open {}: {}\n", .{ abi_list_filename, err });
                std.process.exit(1);
            };
            var lines_it = std.mem.tokenize(contents, "\n");
            while (lines_it.next()) |line| {
                var tok_it = std.mem.tokenize(line, " ");
                const ver = tok_it.next().?;
                const name = tok_it.next().?;
                const category = tok_it.next().?;
                if (!std.mem.eql(u8, category, "F") and
                    !std.mem.eql(u8, category, "D"))
                {
                    continue;
                }
                if (std.mem.startsWith(u8, ver, "GCC_")) continue;
                _ = try global_ver_set.put(ver, undefined);
                const gop = try global_fn_set.getOrPut(name);
                if (gop.found_existing) {
                    if (!std.mem.eql(u8, gop.entry.value.lib, "c")) {
                        gop.entry.value.lib = lib_name;
                    }
                } else {
                    gop.entry.value = Function{
                        .name = name,
                        .lib = lib_name,
                        .index = undefined,
                    };
                }
                try fn_set.append(VersionedFn{
                    .ver = ver,
                    .name = name,
                });
            }
        }
    }

    const global_fn_list = blk: {
        var list = std.ArrayList([]const u8).init(allocator);
        var it = global_fn_set.iterator();
        while (it.next()) |entry| try list.append(entry.key);
        std.sort.sort([]const u8, list.items, {}, strCmpLessThan);
        break :blk list.items;
    };
    const global_ver_list = blk: {
        var list = std.ArrayList([]const u8).init(allocator);
        var it = global_ver_set.iterator();
        while (it.next()) |entry| try list.append(entry.key);
        std.sort.sort([]const u8, list.items, {}, versionLessThan);
        break :blk list.items;
    };
    {
        const vers_txt_path = try fs.path.join(allocator, &[_][]const u8{ glibc_out_dir, "vers.txt" });
        const vers_txt_file = try fs.cwd().createFile(vers_txt_path, .{});
        defer vers_txt_file.close();
        var buffered = std.io.bufferedWriter(vers_txt_file.writer());
        const vers_txt = buffered.writer();
        for (global_ver_list) |name, i| {
            _ = global_ver_set.put(name, i) catch unreachable;
            try vers_txt.print("{}\n", .{name});
        }
        try buffered.flush();
    }
    {
        const fns_txt_path = try fs.path.join(allocator, &[_][]const u8{ glibc_out_dir, "fns.txt" });
        const fns_txt_file = try fs.cwd().createFile(fns_txt_path, .{});
        defer fns_txt_file.close();
        var buffered = std.io.bufferedWriter(fns_txt_file.writer());
        const fns_txt = buffered.writer();
        for (global_fn_list) |name, i| {
            const entry = global_fn_set.getEntry(name).?;
            entry.value.index = i;
            try fns_txt.print("{} {}\n", .{ name, entry.value.lib });
        }
        try buffered.flush();
    }

    // Now the mapping of version and function to integer index is complete.
    // Here we create a mapping of function name to list of versions.
    for (abi_lists) |*abi_list, abi_index| {
        const entry = target_functions.getEntry(@ptrToInt(abi_list)).?;
        const fn_vers_list = &entry.value.fn_vers_list;
        for (entry.value.list.items) |*ver_fn| {
            const gop = try fn_vers_list.getOrPut(ver_fn.name);
            if (!gop.found_existing) {
                gop.entry.value = std.ArrayList(usize).init(allocator);
            }
            const ver_index = global_ver_set.getEntry(ver_fn.ver).?.value;
            if (std.mem.indexOfScalar(usize, gop.entry.value.items, ver_index) == null) {
                try gop.entry.value.append(ver_index);
            }
        }
    }

    {
        const abilist_txt_path = try fs.path.join(allocator, &[_][]const u8{ glibc_out_dir, "abi.txt" });
        const abilist_txt_file = try fs.cwd().createFile(abilist_txt_path, .{});
        defer abilist_txt_file.close();
        var buffered = std.io.bufferedWriter(abilist_txt_file.writer());
        const abilist_txt = buffered.writer();

        // first iterate over the abi lists
        for (abi_lists) |*abi_list, abi_index| {
            const fn_vers_list = &target_functions.getEntry(@ptrToInt(abi_list)).?.value.fn_vers_list;
            for (abi_list.targets) |target, it_i| {
                if (it_i != 0) try abilist_txt.writeByte(' ');
                try abilist_txt.print("{}-linux-{}", .{ @tagName(target.arch), @tagName(target.abi) });
            }
            try abilist_txt.writeByte('\n');
            // next, each line implicitly corresponds to a function
            for (global_fn_list) |name| {
                const entry = fn_vers_list.getEntry(name) orelse {
                    try abilist_txt.writeByte('\n');
                    continue;
                };
                for (entry.value.items) |ver_index, it_i| {
                    if (it_i != 0) try abilist_txt.writeByte(' ');
                    try abilist_txt.print("{d}", .{ver_index});
                }
                try abilist_txt.writeByte('\n');
            }
        }

        try buffered.flush();
    }
}

pub fn strCmpLessThan(context: void, a: []const u8, b: []const u8) bool {
    return std.mem.order(u8, a, b) == .lt;
}

pub fn versionLessThan(context: void, a: []const u8, b: []const u8) bool {
    const sep_chars = "GLIBC_.";
    var a_tokens = std.mem.tokenize(a, sep_chars);
    var b_tokens = std.mem.tokenize(b, sep_chars);

    while (true) {
        const a_next = a_tokens.next();
        const b_next = b_tokens.next();
        if (a_next == null and b_next == null) {
            return false; // equal means not less than
        } else if (a_next == null) {
            return true;
        } else if (b_next == null) {
            return false;
        }
        const a_int = fmt.parseInt(u64, a_next.?, 10) catch unreachable;
        const b_int = fmt.parseInt(u64, b_next.?, 10) catch unreachable;
        if (a_int < b_int) {
            return true;
        } else if (a_int > b_int) {
            return false;
        }
    }
}