zig/tools/update_glibc.zig
Andrew Kelley 3b97940fb3
add an update_glibc tool, delete dummy libc files
This is the beginning of supporting minimum GLIBC version as part of the
target. See #2509 for the motivation.

The dummy libc zig files are removed. A future commit will build them
on-the-fly, using the generated text files generated by the new tool,
which are checked into source control and distributed along with zig.

These generated text files are, together, 142KB (20KB gzipped).
Compare that to a naive bundling of the .abilist files, which would be
2.2MiB (375KB gzipped).

This is based on glibc 2.29.
2019-07-07 17:56:07 -04:00

334 lines
13 KiB
Zig

const std = @import("std");
const builtin = @import("builtin");
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: @TagType(builtin.Arch),
abi: builtin.Abi,
};
const lib_names = [_][]const u8{
"c",
"dl",
"m",
"pthread",
"rt",
};
// 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.AutoHashMap([]const u8, 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.direct_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
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.AutoHashMap([]const u8, Function).init(allocator);
var global_ver_set = std.AutoHashMap([]const u8, 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.kv.value = FunctionSet{
.list = std.ArrayList(VersionedFn).init(allocator),
.fn_vers_list = FnVersionList.init(allocator),
};
}
const fn_set = &target_funcs_gop.kv.value.list;
for (lib_names) |lib_name, lib_name_index| {
const basename = try fmt.allocPrint(allocator, "lib{}.abilist", lib_name);
const abi_list_filename = blk: {
if (abi_list.targets[0].abi == .gnuabi64 and std.mem.eql(u8, lib_name, "c")) {
break :blk try fs.path.join(allocator, [_][]const u8{ prefix, abi_list.path, "n64", basename });
} else if (abi_list.targets[0].abi == .gnuabin32 and std.mem.eql(u8, lib_name, "c")) {
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
(std.mem.eql(u8, lib_name, "c") or
(std.mem.eql(u8, lib_name, "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
(std.mem.eql(u8, lib_name, "c") or
(std.mem.eql(u8, lib_name, "m") and abi_list.targets[0].arch == .powerpc)))
{
break :blk try fs.path.join(allocator, [_][]const u8{ prefix, abi_list.path, "nofpu", basename });
}
break :blk try fs.path.join(allocator, [_][]const u8{ prefix, abi_list.path, basename });
};
const contents = std.io.readFileAlloc(allocator, abi_list_filename) 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.kv.value.lib, "c")) {
gop.kv.value.lib = lib_name;
}
} else {
gop.kv.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()) |kv| try list.append(kv.key);
std.sort.sort([]const u8, list.toSlice(), strCmpLessThan);
break :blk list.toSliceConst();
};
const global_ver_list = blk: {
var list = std.ArrayList([]const u8).init(allocator);
var it = global_ver_set.iterator();
while (it.next()) |kv| try list.append(kv.key);
std.sort.sort([]const u8, list.toSlice(), versionLessThan);
break :blk list.toSliceConst();
};
{
const vers_txt_path = try fs.path.join(allocator, [_][]const u8{ glibc_out_dir, "vers.txt" });
const vers_txt_file = try fs.File.openWrite(vers_txt_path);
defer vers_txt_file.close();
var buffered = std.io.BufferedOutStream(fs.File.WriteError).init(&vers_txt_file.outStream().stream);
const vers_txt = &buffered.stream;
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.File.openWrite(fns_txt_path);
defer fns_txt_file.close();
var buffered = std.io.BufferedOutStream(fs.File.WriteError).init(&fns_txt_file.outStream().stream);
const fns_txt = &buffered.stream;
for (global_fn_list) |name, i| {
const kv = global_fn_set.get(name).?;
kv.value.index = i;
try fns_txt.print("{} {}\n", name, kv.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 kv = target_functions.get(@ptrToInt(abi_list)).?;
const fn_vers_list = &kv.value.fn_vers_list;
for (kv.value.list.toSliceConst()) |*ver_fn| {
const gop = try fn_vers_list.getOrPut(ver_fn.name);
if (!gop.found_existing) {
gop.kv.value = std.ArrayList(usize).init(allocator);
}
const ver_index = global_ver_set.get(ver_fn.ver).?.value;
if (std.mem.indexOfScalar(usize, gop.kv.value.toSliceConst(), ver_index) == null) {
try gop.kv.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.File.openWrite(abilist_txt_path);
defer abilist_txt_file.close();
var buffered = std.io.BufferedOutStream(fs.File.WriteError).init(&abilist_txt_file.outStream().stream);
const abilist_txt = &buffered.stream;
// first iterate over the abi lists
for (abi_lists) |*abi_list, abi_index| {
const fn_vers_list = &target_functions.get(@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 kv = fn_vers_list.get(name) orelse {
try abilist_txt.writeByte('\n');
continue;
};
for (kv.value.toSliceConst()) |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(a: []const u8, b: []const u8) bool {
return std.mem.compare(u8, a, b) == .LessThan;
}
pub fn versionLessThan(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;
}
}
}