Commit 8cf40f3445

Andrew Kelley <andrew@ziglang.org>
2020-09-11 07:24:27
stage2: loading glibc metadata
1 parent 98583be
Changed files (4)
src-self-hosted/glibc.zig
@@ -0,0 +1,231 @@
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+const target_util = @import("target.zig");
+const mem = std.mem;
+
+pub const Lib = struct {
+    name: []const u8,
+    sover: u8,
+};
+
+pub const Fn = struct {
+    name: []const u8,
+    lib: *const Lib,
+};
+
+pub const VerList = struct {
+    /// 7 is just the max number, we know statically it's big enough.
+    versions: [7]u8,
+    len: u8,
+};
+
+pub const ABI = struct {
+    all_versions: []const std.builtin.Version,
+    all_functions: []const Fn,
+    /// The value is a pointer to all_functions.len items and each item is an index into all_functions.
+    version_table: std.AutoHashMapUnmanaged(target_util.ArchOsAbi, [*]VerList),
+    arena_state: std.heap.ArenaAllocator.State,
+
+    pub fn destroy(abi: *ABI, gpa: *Allocator) void {
+        abi.version_table.deinit(gpa);
+        abi.arena_state.promote(gpa).deinit(); // Frees the ABI memory too.
+    }
+};
+
+pub const libs = [_]Lib{
+    .{ .name = "c", .sover = 6 },
+    .{ .name = "m", .sover = 6 },
+    .{ .name = "pthread", .sover = 0 },
+    .{ .name = "dl", .sover = 2 },
+    .{ .name = "rt", .sover = 1 },
+    .{ .name = "ld", .sover = 2 },
+    .{ .name = "util", .sover = 1 },
+};
+
+pub const LoadMetaDataError = error{
+    /// The files that ship with the Zig compiler were unable to be read, or otherwise had malformed data.
+    ZigInstallationCorrupt,
+    OutOfMemory,
+};
+
+/// This function will emit a log error when there is a problem with the zig installation and then return
+/// `error.ZigInstallationCorrupt`.
+pub fn loadMetaData(gpa: *Allocator, zig_lib_dir: std.fs.Dir) LoadMetaDataError!*ABI {
+    var arena_allocator = std.heap.ArenaAllocator.init(gpa);
+    errdefer arena_allocator.deinit();
+    const arena = &arena_allocator.allocator;
+
+    var all_versions = std.ArrayListUnmanaged(std.builtin.Version){};
+    var all_functions = std.ArrayListUnmanaged(Fn){};
+    var version_table = std.AutoHashMapUnmanaged(target_util.ArchOsAbi, [*]VerList){};
+    errdefer version_table.deinit(gpa);
+
+    var glibc_dir = zig_lib_dir.openDir("libc" ++ std.fs.path.sep_str ++ "glibc", .{}) catch |err| {
+        std.log.err("unable to open glibc dir: {}", .{@errorName(err)});
+        return error.ZigInstallationCorrupt;
+    };
+    defer glibc_dir.close();
+
+    const max_txt_size = 500 * 1024; // Bigger than this and something is definitely borked.
+    const vers_txt_contents = glibc_dir.readFileAlloc(gpa, "vers.txt", max_txt_size) catch |err| switch (err) {
+        error.OutOfMemory => return error.OutOfMemory,
+        else => {
+            std.log.err("unable to read vers.txt: {}", .{@errorName(err)});
+            return error.ZigInstallationCorrupt;
+        },
+    };
+    defer gpa.free(vers_txt_contents);
+
+    const fns_txt_contents = glibc_dir.readFileAlloc(gpa, "fns.txt", max_txt_size) catch |err| switch (err) {
+        error.OutOfMemory => return error.OutOfMemory,
+        else => {
+            std.log.err("unable to read fns.txt: {}", .{@errorName(err)});
+            return error.ZigInstallationCorrupt;
+        },
+    };
+    defer gpa.free(fns_txt_contents);
+
+    const abi_txt_contents = glibc_dir.readFileAlloc(gpa, "abi.txt", max_txt_size) catch |err| switch (err) {
+        error.OutOfMemory => return error.OutOfMemory,
+        else => {
+            std.log.err("unable to read abi.txt: {}", .{@errorName(err)});
+            return error.ZigInstallationCorrupt;
+        },
+    };
+    defer gpa.free(abi_txt_contents);
+
+    {
+        var it = mem.tokenize(vers_txt_contents, "\r\n");
+        var line_i: usize = 1;
+        while (it.next()) |line| : (line_i += 1) {
+            const prefix = "GLIBC_";
+            if (!mem.startsWith(u8, line, prefix)) {
+                std.log.err("vers.txt:{}: expected 'GLIBC_' prefix", .{line_i});
+                return error.ZigInstallationCorrupt;
+            }
+            const adjusted_line = line[prefix.len..];
+            const ver = std.builtin.Version.parse(adjusted_line) catch |err| {
+                std.log.err("vers.txt:{}: unable to parse glibc version '{}': {}", .{ line_i, line, @errorName(err) });
+                return error.ZigInstallationCorrupt;
+            };
+            try all_versions.append(arena, ver);
+        }
+    }
+    {
+        var file_it = mem.tokenize(fns_txt_contents, "\r\n");
+        var line_i: usize = 1;
+        while (file_it.next()) |line| : (line_i += 1) {
+            var line_it = mem.tokenize(line, " ");
+            const fn_name = line_it.next() orelse {
+                std.log.err("fns.txt:{}: expected function name", .{line_i});
+                return error.ZigInstallationCorrupt;
+            };
+            const lib_name = line_it.next() orelse {
+                std.log.err("fns.txt:{}: expected library name", .{line_i});
+                return error.ZigInstallationCorrupt;
+            };
+            const lib = findLib(lib_name) orelse {
+                std.log.err("fns.txt:{}: unknown library name: {}", .{ line_i, lib_name });
+                return error.ZigInstallationCorrupt;
+            };
+            try all_functions.append(arena, .{
+                .name = fn_name,
+                .lib = lib,
+            });
+        }
+    }
+    {
+        var file_it = mem.split(abi_txt_contents, "\n");
+        var line_i: usize = 0;
+        while (true) {
+            const ver_list_base: []VerList = blk: {
+                const line = file_it.next() orelse break;
+                if (line.len == 0) break;
+                line_i += 1;
+                const ver_list_base = try arena.alloc(VerList, all_functions.items.len);
+                var line_it = mem.tokenize(line, " ");
+                while (line_it.next()) |target_string| {
+                    var component_it = mem.tokenize(target_string, "-");
+                    const arch_name = component_it.next() orelse {
+                        std.log.err("abi.txt:{}: expected arch name", .{line_i});
+                        return error.ZigInstallationCorrupt;
+                    };
+                    const os_name = component_it.next() orelse {
+                        std.log.err("abi.txt:{}: expected OS name", .{line_i});
+                        return error.ZigInstallationCorrupt;
+                    };
+                    const abi_name = component_it.next() orelse {
+                        std.log.err("abi.txt:{}: expected ABI name", .{line_i});
+                        return error.ZigInstallationCorrupt;
+                    };
+                    const arch_tag = std.meta.stringToEnum(std.Target.Cpu.Arch, arch_name) orelse {
+                        std.log.err("abi.txt:{}: unrecognized arch: '{}'", .{ line_i, arch_name });
+                        return error.ZigInstallationCorrupt;
+                    };
+                    if (!mem.eql(u8, os_name, "linux")) {
+                        std.log.err("abi.txt:{}: expected OS 'linux', found '{}'", .{ line_i, os_name });
+                        return error.ZigInstallationCorrupt;
+                    }
+                    const abi_tag = std.meta.stringToEnum(std.Target.Abi, abi_name) orelse {
+                        std.log.err("abi.txt:{}: unrecognized ABI: '{}'", .{ line_i, abi_name });
+                        return error.ZigInstallationCorrupt;
+                    };
+
+                    const triple = target_util.ArchOsAbi{
+                        .arch = arch_tag,
+                        .os = .linux,
+                        .abi = abi_tag,
+                    };
+                    try version_table.put(arena, triple, ver_list_base.ptr);
+                }
+                break :blk ver_list_base;
+            };
+            for (ver_list_base) |*ver_list| {
+                const line = file_it.next() orelse {
+                    std.log.err("abi.txt:{}: missing version number line", .{line_i});
+                    return error.ZigInstallationCorrupt;
+                };
+                line_i += 1;
+
+                ver_list.* = .{
+                    .versions = undefined,
+                    .len = 0,
+                };
+                var line_it = mem.tokenize(line, " ");
+                while (line_it.next()) |version_index_string| {
+                    if (ver_list.len >= ver_list.versions.len) {
+                        // If this happens with legit data, increase the array len in the type.
+                        std.log.err("abi.txt:{}: too many versions", .{line_i});
+                        return error.ZigInstallationCorrupt;
+                    }
+                    const version_index = std.fmt.parseInt(u8, version_index_string, 10) catch |err| {
+                        // If this happens with legit data, increase the size of the integer type in the struct.
+                        std.log.err("abi.txt:{}: unable to parse version: {}", .{ line_i, @errorName(err) });
+                        return error.ZigInstallationCorrupt;
+                    };
+
+                    ver_list.versions[ver_list.len] = version_index;
+                    ver_list.len += 1;
+                }
+            }
+        }
+    }
+
+    const abi = try arena.create(ABI);
+    abi.* = .{
+        .all_versions = all_versions.items,
+        .all_functions = all_functions.items,
+        .version_table = version_table,
+        .arena_state = arena_allocator.state,
+    };
+    return abi;
+}
+
+fn findLib(name: []const u8) ?*const Lib {
+    for (libs) |*lib| {
+        if (mem.eql(u8, lib.name, name)) {
+            return lib;
+        }
+    }
+    return null;
+}
src-self-hosted/main.zig
@@ -16,7 +16,7 @@ const warn = std.log.warn;
 const introspect = @import("introspect.zig");
 const LibCInstallation = @import("libc_installation.zig").LibCInstallation;
 
-fn fatal(comptime format: []const u8, args: anytype) noreturn {
+pub fn fatal(comptime format: []const u8, args: anytype) noreturn {
     std.log.emerg(format, args);
     process.exit(1);
 }
src-self-hosted/Module.zig
@@ -1256,6 +1256,14 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Module {
         mod.c_object_table.putAssumeCapacityNoClobber(c_object, {});
     }
 
+    // If we need to build glibc for the target, add work items for it.
+    if (mod.bin_file.options.link_libc and
+        mod.bin_file.options.libc_installation == null and
+        mod.bin_file.options.target.isGnuLibC())
+    {
+        try mod.addBuildingGLibCWorkItems();
+    }
+
     return mod;
 }
 
@@ -4495,3 +4503,8 @@ pub fn get_libc_crt_file(mod: *Module, arena: *Allocator, basename: []const u8)
     const full_path = try std.fs.path.join(arena, &[_][]const u8{ crt_dir_path, basename });
     return full_path;
 }
+
+fn addBuildingGLibCWorkItems(mod: *Module) !void {
+    // crti.o, crtn.o, start.os, abi-note.o, Scrt1.o, libc_nonshared.a
+    try mod.work_queue.ensureUnusedCapacity(6);
+}
src-self-hosted/print_targets.zig
@@ -6,8 +6,9 @@ const Allocator = mem.Allocator;
 const Target = std.Target;
 const target = @import("target.zig");
 const assert = std.debug.assert;
-
+const glibc = @import("glibc.zig");
 const introspect = @import("introspect.zig");
+const fatal = @import("main.zig").fatal;
 
 pub fn cmdTargets(
     allocator: *Allocator,
@@ -16,33 +17,16 @@ pub fn cmdTargets(
     stdout: anytype,
     native_target: Target,
 ) !void {
-    const available_glibcs = blk: {
-        const zig_lib_dir = introspect.resolveZigLibDir(allocator) catch |err| {
-            std.debug.print("unable to find zig installation directory: {}\n", .{@errorName(err)});
-            std.process.exit(1);
-        };
-        defer allocator.free(zig_lib_dir);
-
-        var dir = try std.fs.cwd().openDir(zig_lib_dir, .{});
-        defer dir.close();
-
-        const vers_txt = try dir.readFileAlloc(allocator, "libc" ++ std.fs.path.sep_str ++ "glibc" ++ std.fs.path.sep_str ++ "vers.txt", 10 * 1024);
-        defer allocator.free(vers_txt);
-
-        var list = std.ArrayList(std.builtin.Version).init(allocator);
-        defer list.deinit();
-
-        var it = mem.tokenize(vers_txt, "\r\n");
-        while (it.next()) |line| {
-            const prefix = "GLIBC_";
-            assert(mem.startsWith(u8, line, prefix));
-            const adjusted_line = line[prefix.len..];
-            const ver = try std.builtin.Version.parse(adjusted_line);
-            try list.append(ver);
-        }
-        break :blk list.toOwnedSlice();
+    const zig_lib_dir_path = introspect.resolveZigLibDir(allocator) catch |err| {
+        fatal("unable to find zig installation directory: {}\n", .{@errorName(err)});
     };
-    defer allocator.free(available_glibcs);
+    defer allocator.free(zig_lib_dir_path);
+
+    var zig_lib_dir = try fs.cwd().openDir(zig_lib_dir_path, .{});
+    defer zig_lib_dir.close();
+
+    const glibc_abi = try glibc.loadMetaData(allocator, zig_lib_dir);
+    errdefer glibc_abi.destroy(allocator);
 
     var bos = io.bufferedOutStream(stdout);
     const bos_stream = bos.outStream();
@@ -90,10 +74,10 @@ pub fn cmdTargets(
 
     try jws.objectField("glibc");
     try jws.beginArray();
-    for (available_glibcs) |glibc| {
+    for (glibc_abi.all_versions) |ver| {
         try jws.arrayElem();
 
-        const tmp = try std.fmt.allocPrint(allocator, "{}", .{glibc});
+        const tmp = try std.fmt.allocPrint(allocator, "{}", .{ver});
         defer allocator.free(tmp);
         try jws.emitString(tmp);
     }
@@ -170,7 +154,6 @@ pub fn cmdTargets(
     try jws.emitString(@tagName(native_target.os.tag));
     try jws.objectField("abi");
     try jws.emitString(@tagName(native_target.abi));
-    // TODO implement native glibc version detection in self-hosted
     try jws.endObject();
 
     try jws.endObject();