Commit 7ccf7807b3

Andrew Kelley <andrew@ziglang.org>
2019-07-07 23:06:09
ability to target any glibc version
1 parent 3b97940
src/all_types.hpp
@@ -1921,6 +1921,7 @@ struct CodeGen {
     Buf *zig_lib_dir;
     Buf *zig_std_dir;
     Buf *dynamic_linker_path;
+    Buf *version_script_path; 
 
     const char **llvm_argv;
     size_t llvm_argv_len;
@@ -3788,6 +3789,9 @@ static const size_t stack_trace_ptr_count = 32;
 #define NAMESPACE_SEP_CHAR '.'
 #define NAMESPACE_SEP_STR "."
 
+#define CACHE_OUT_SUBDIR "o"
+#define CACHE_HASH_SUBDIR "h"
+
 enum FloatMode {
     FloatModeStrict,
     FloatModeOptimized,
src/codegen.cpp
@@ -24,9 +24,6 @@
 #include <stdio.h>
 #include <errno.h>
 
-#define CACHE_OUT_SUBDIR "o"
-#define CACHE_HASH_SUBDIR "h"
-
 static void init_darwin_native(CodeGen *g) {
     char *osx_target = getenv("MACOSX_DEPLOYMENT_TARGET");
     char *ios_target = getenv("IPHONEOS_DEPLOYMENT_TARGET");
@@ -9502,6 +9499,7 @@ static Error check_cache(CodeGen *g, Buf *manifest_dir, Buf *digest) {
         cache_buf(ch, &g->libc->kernel32_lib_dir);
     }
     cache_buf_opt(ch, g->dynamic_linker_path);
+    cache_buf_opt(ch, g->version_script_path);
 
     // gen_c_objects appends objects to g->link_objects which we want to include in the hash
     gen_c_objects(g);
@@ -9695,3 +9693,35 @@ ZigPackage *codegen_create_package(CodeGen *g, const char *root_src_dir, const c
     }
     return pkg;
 }
+
+CodeGen *create_child_codegen(CodeGen *parent_gen, Buf *root_src_path, OutType out_type,
+        ZigLibCInstallation *libc)
+{
+    CodeGen *child_gen = codegen_create(nullptr, root_src_path, parent_gen->zig_target, out_type,
+        parent_gen->build_mode, parent_gen->zig_lib_dir, parent_gen->zig_std_dir, libc, get_stage1_cache_path());
+    child_gen->disable_gen_h = true;
+    child_gen->want_stack_check = WantStackCheckDisabled;
+    child_gen->verbose_tokenize = parent_gen->verbose_tokenize;
+    child_gen->verbose_ast = parent_gen->verbose_ast;
+    child_gen->verbose_link = parent_gen->verbose_link;
+    child_gen->verbose_ir = parent_gen->verbose_ir;
+    child_gen->verbose_llvm_ir = parent_gen->verbose_llvm_ir;
+    child_gen->verbose_cimport = parent_gen->verbose_cimport;
+    child_gen->verbose_cc = parent_gen->verbose_cc;
+    child_gen->llvm_argv = parent_gen->llvm_argv;
+    child_gen->dynamic_linker_path = parent_gen->dynamic_linker_path;
+
+    codegen_set_strip(child_gen, parent_gen->strip_debug_symbols);
+    child_gen->want_pic = parent_gen->have_pic ? WantPICEnabled : WantPICDisabled;
+    child_gen->valgrind_support = ValgrindSupportDisabled;
+
+    codegen_set_errmsg_color(child_gen, parent_gen->err_color);
+
+    codegen_set_mmacosx_version_min(child_gen, parent_gen->mmacosx_version_min);
+    codegen_set_mios_version_min(child_gen, parent_gen->mios_version_min);
+
+    child_gen->enable_cache = true;
+
+    return child_gen;
+}
+
src/codegen.hpp
@@ -20,6 +20,9 @@ CodeGen *codegen_create(Buf *main_pkg_path, Buf *root_src_path, const ZigTarget
     OutType out_type, BuildMode build_mode, Buf *zig_lib_dir, Buf *override_std_dir,
     ZigLibCInstallation *libc, Buf *cache_dir);
 
+CodeGen *create_child_codegen(CodeGen *parent_gen, Buf *root_src_path, OutType out_type,
+        ZigLibCInstallation *libc);
+
 void codegen_set_clang_argv(CodeGen *codegen, const char **args, size_t len);
 void codegen_set_llvm_argv(CodeGen *codegen, const char **args, size_t len);
 void codegen_set_is_test(CodeGen *codegen, bool is_test);
src/compiler.cpp
@@ -9,9 +9,13 @@ static Buf saved_stage1_path = BUF_INIT;
 static Buf saved_lib_dir = BUF_INIT;
 static Buf saved_special_dir = BUF_INIT;
 static Buf saved_std_dir = BUF_INIT;
+
 static Buf saved_dynamic_linker_path = BUF_INIT;
 static bool searched_for_dyn_linker = false;
 
+static Buf saved_libc_path = BUF_INIT;
+static bool searched_for_libc = false;
+
 Buf *get_stage1_cache_path(void) {
     if (saved_stage1_path.list.length != 0) {
         return &saved_stage1_path;
@@ -36,6 +40,28 @@ static void detect_dynamic_linker(Buf *lib_path) {
 #endif
 }
 
+const Buf *get_self_libc_path(void) {
+    for (;;) {
+        if (saved_libc_path.list.length != 0) {
+            return &saved_libc_path;
+        }
+        if (searched_for_libc)
+            return nullptr;
+        ZigList<Buf *> lib_paths = {};
+        Error err;
+        if ((err = os_self_exe_shared_libs(lib_paths)))
+            return nullptr;
+        for (size_t i = 0; i < lib_paths.length; i += 1) {
+            Buf *lib_path = lib_paths.at(i);
+            if (buf_ends_with_str(lib_path, "libc.so.6")) {
+                buf_init_from_buf(&saved_libc_path, lib_path);
+                return &saved_libc_path;
+            }
+        }
+        searched_for_libc = true;
+    }
+}
+
 Buf *get_self_dynamic_linker_path(void) {
     for (;;) {
         if (saved_dynamic_linker_path.list.length != 0) {
src/compiler.hpp
@@ -14,6 +14,7 @@
 Buf *get_stage1_cache_path(void);
 Error get_compiler_id(Buf **result);
 Buf *get_self_dynamic_linker_path(void);
+Buf *get_self_libc_path(void);
 
 Buf *get_zig_lib_dir(void);
 Buf *get_zig_special_dir(Buf *zig_lib_dir);
src/glibc.cpp
@@ -0,0 +1,410 @@
+/*
+ * Copyright (c) 2019 Andrew Kelley
+ *
+ * This file is part of zig, which is MIT licensed.
+ * See http://opensource.org/licenses/MIT
+ */
+
+#include "glibc.hpp"
+#include "compiler.hpp"
+#include "cache_hash.hpp"
+#include "codegen.hpp"
+
+static const ZigGLibCLib glibc_libs[] = {
+    {"c", 6},
+    {"m", 6},
+    {"pthread", 0},
+    {"dl", 2},
+    {"rt", 1},
+};
+
+Error glibc_load_metadata(ZigGLibCAbi **out_result, Buf *zig_lib_dir, bool verbose) {
+    Error err;
+
+    ZigGLibCAbi *glibc_abi = allocate<ZigGLibCAbi>(1);
+    glibc_abi->vers_txt_path = buf_sprintf("%s" OS_SEP "libc" OS_SEP "glibc" OS_SEP "vers.txt", buf_ptr(zig_lib_dir));
+    glibc_abi->fns_txt_path = buf_sprintf("%s" OS_SEP "libc" OS_SEP "glibc" OS_SEP "fns.txt", buf_ptr(zig_lib_dir));
+    glibc_abi->abi_txt_path = buf_sprintf("%s" OS_SEP "libc" OS_SEP "glibc" OS_SEP "abi.txt", buf_ptr(zig_lib_dir));
+    glibc_abi->version_table.init(16);
+
+    Buf *vers_txt_contents = buf_alloc();
+    if ((err = os_fetch_file_path(glibc_abi->vers_txt_path, vers_txt_contents))) {
+        if (verbose) {
+            fprintf(stderr, "Unable to read %s: %s\n", buf_ptr(glibc_abi->vers_txt_path), err_str(err));
+        }
+        return err;
+    }
+    Buf *fns_txt_contents = buf_alloc();
+    if ((err = os_fetch_file_path(glibc_abi->fns_txt_path, fns_txt_contents))) {
+        if (verbose) {
+            fprintf(stderr, "Unable to read %s: %s\n", buf_ptr(glibc_abi->fns_txt_path), err_str(err));
+        }
+        return err;
+    }
+    Buf *abi_txt_contents = buf_alloc();
+    if ((err = os_fetch_file_path(glibc_abi->abi_txt_path, abi_txt_contents))) {
+        if (verbose) {
+            fprintf(stderr, "Unable to read %s: %s\n", buf_ptr(glibc_abi->abi_txt_path), err_str(err));
+        }
+        return err;
+    }
+
+    {
+        SplitIterator it = memSplit(buf_to_slice(vers_txt_contents), str("\n"));
+        for (;;) {
+            Optional<Slice<uint8_t>> opt_component = SplitIterator_next(&it);
+            if (!opt_component.is_some) break;
+            Buf *ver_buf = buf_create_from_slice(opt_component.value);
+            ZigGLibCVersion *this_ver = glibc_abi->all_versions.add_one();
+            if ((err = target_parse_glibc_version(this_ver, buf_ptr(ver_buf)))) {
+                if (verbose) {
+                    fprintf(stderr, "Unable to parse glibc version '%s': %s\n", buf_ptr(ver_buf), err_str(err));
+                }
+                return err;
+            }
+        }
+    }
+    {
+        SplitIterator it = memSplit(buf_to_slice(fns_txt_contents), str("\n"));
+        for (;;) {
+            Optional<Slice<uint8_t>> opt_component = SplitIterator_next(&it);
+            if (!opt_component.is_some) break;
+            SplitIterator line_it = memSplit(opt_component.value, str(" "));
+            Optional<Slice<uint8_t>> opt_fn_name = SplitIterator_next(&line_it);
+            if (!opt_fn_name.is_some) {
+                if (verbose) {
+                    fprintf(stderr, "%s: Expected function name\n", buf_ptr(glibc_abi->fns_txt_path));
+                }
+                return ErrorInvalidFormat;
+            }
+            Optional<Slice<uint8_t>> opt_lib_name = SplitIterator_next(&line_it);
+            if (!opt_lib_name.is_some) {
+                if (verbose) {
+                    fprintf(stderr, "%s: Expected lib name\n", buf_ptr(glibc_abi->fns_txt_path));
+                }
+                return ErrorInvalidFormat;
+            }
+
+            Buf *this_fn_name = buf_create_from_slice(opt_fn_name.value);
+            Buf *this_lib_name = buf_create_from_slice(opt_lib_name.value);
+            glibc_abi->all_functions.append({ this_fn_name, glibc_lib_find(buf_ptr(this_lib_name)) });
+        }
+    }
+    {
+        SplitIterator it = memSplit(buf_to_slice(abi_txt_contents), str("\n"));
+        ZigGLibCVerList *ver_list_base = nullptr;
+        for (;;) {
+            if (ver_list_base == nullptr) {
+                Optional<Slice<uint8_t>> opt_line = SplitIterator_next_separate(&it);
+                if (!opt_line.is_some) break;
+
+                ver_list_base = allocate<ZigGLibCVerList>(glibc_abi->all_functions.length);
+                ZigTarget *target = allocate<ZigTarget>(1);
+                SplitIterator line_it = memSplit(opt_line.value, str(" "));
+                for (;;) {
+                    Optional<Slice<uint8_t>> opt_target = SplitIterator_next(&line_it);
+                    if (!opt_target.is_some) break;
+
+                    SplitIterator component_it = memSplit(opt_target.value, str("-"));
+                    Optional<Slice<uint8_t>> opt_arch = SplitIterator_next(&component_it);
+                    assert(opt_arch.is_some);
+                    Optional<Slice<uint8_t>> opt_os = SplitIterator_next(&component_it);
+                    assert(opt_os.is_some); // it's always "linux" so we ignore it
+                    Optional<Slice<uint8_t>> opt_abi = SplitIterator_next(&component_it);
+                    assert(opt_abi.is_some);
+
+
+                    err = target_parse_archsub(&target->arch, &target->sub_arch,
+                            (char*)opt_arch.value.ptr, opt_arch.value.len);
+                    // there's no sub arch so we might get an error, but the arch is still populated
+                    assert(err == ErrorNone || err == ErrorUnknownArchitecture);
+
+                    target->os = OsLinux;
+
+                    err = target_parse_abi(&target->abi, (char*)opt_abi.value.ptr, opt_abi.value.len);
+                    assert(err == ErrorNone);
+
+                    glibc_abi->version_table.put(target, ver_list_base);
+                }
+                continue;
+            }
+            for (size_t fn_i = 0; fn_i < glibc_abi->all_functions.length; fn_i += 1) {
+                ZigGLibCVerList *ver_list = &ver_list_base[fn_i];
+                Optional<Slice<uint8_t>> opt_line = SplitIterator_next_separate(&it);
+                assert(opt_line.is_some);
+
+                SplitIterator line_it = memSplit(opt_line.value, str(" "));
+                for (;;) {
+                    Optional<Slice<uint8_t>> opt_ver = SplitIterator_next(&line_it);
+                    if (!opt_ver.is_some) break;
+                    assert(ver_list->len < 8); // increase the array len in the type
+
+                    unsigned long ver_index = strtoul(buf_ptr(buf_create_from_slice(opt_ver.value)), nullptr, 10);
+                    assert(ver_index < 255); // use a bigger integer in the type
+                    ver_list->versions[ver_list->len] = ver_index;
+                    ver_list->len += 1;
+                }
+            }
+            ver_list_base = nullptr;
+        }
+    }
+
+    *out_result = glibc_abi;
+    return ErrorNone;
+}
+
+Error glibc_build_dummies_and_maps(CodeGen *g, const ZigGLibCAbi *glibc_abi, const ZigTarget *target,
+        Buf **out_dir, bool verbose)
+{
+    Error err;
+
+    Buf *cache_dir = get_stage1_cache_path();
+    CacheHash *cache_hash = allocate<CacheHash>(1);
+    Buf *manifest_dir = buf_sprintf("%s" OS_SEP CACHE_HASH_SUBDIR, buf_ptr(cache_dir));
+    cache_init(cache_hash, manifest_dir);
+
+    Buf *compiler_id;
+    if ((err = get_compiler_id(&compiler_id))) {
+        if (verbose) {
+            fprintf(stderr, "unable to get compiler id: %s\n", err_str(err));
+        }
+        return err;
+    }
+    cache_buf(cache_hash, compiler_id);
+    cache_int(cache_hash, target->arch);
+    cache_int(cache_hash, target->abi);
+    cache_int(cache_hash, target->glibc_version->major);
+    cache_int(cache_hash, target->glibc_version->minor);
+    cache_int(cache_hash, target->glibc_version->patch);
+
+    Buf digest = BUF_INIT;
+    buf_resize(&digest, 0);
+    if ((err = cache_hit(cache_hash, &digest))) {
+        // Treat an invalid format error as a cache miss.
+        if (err != ErrorInvalidFormat)
+            return err;
+    }
+    // We should always get a cache hit because there are no
+    // files in the input hash.
+    assert(buf_len(&digest) != 0);
+
+    Buf *dummy_dir = buf_alloc();
+    os_path_join(manifest_dir, &digest, dummy_dir);
+
+    if ((err = os_make_path(dummy_dir)))
+        return err;
+
+    Buf *test_if_exists_path = buf_alloc();
+    os_path_join(dummy_dir, buf_create_from_str("ok"), test_if_exists_path);
+
+    bool hit;
+    if ((err = os_file_exists(test_if_exists_path, &hit)))
+        return err;
+
+    // TODO this is for debugging
+    fprintf(stderr, "dummy so dir: %s\n", buf_ptr(dummy_dir));
+
+    if (hit) {
+        *out_dir = dummy_dir;
+        return ErrorNone;
+    }
+
+
+    ZigGLibCVerList *ver_list_base = glibc_abi->version_table.get(target);
+
+    uint8_t target_ver_index = 0;
+    for (;target_ver_index < glibc_abi->all_versions.length; target_ver_index += 1) {
+        const ZigGLibCVersion *this_ver = &glibc_abi->all_versions.at(target_ver_index);
+        if (this_ver->major == target->glibc_version->major &&
+            this_ver->minor == target->glibc_version->minor &&
+            this_ver->patch == target->glibc_version->patch)
+        {
+            break;
+        }
+    }
+    if (target_ver_index == glibc_abi->all_versions.length) {
+        if (verbose) {
+            fprintf(stderr, "Unrecognized glibc version: %d.%d.%d\n",
+                   target->glibc_version->major,
+                   target->glibc_version->minor,
+                   target->glibc_version->patch);
+        }
+        return ErrorUnknownABI;
+    }
+
+    Buf *map_file_path = buf_sprintf("%s" OS_SEP "all.map", buf_ptr(dummy_dir));
+    Buf *map_contents = buf_alloc();
+
+    for (uint8_t ver_i = 0; ver_i < glibc_abi->all_versions.length; ver_i += 1) {
+        const ZigGLibCVersion *ver = &glibc_abi->all_versions.at(ver_i);
+        if (ver->patch == 0) {
+            buf_appendf(map_contents, "GLIBC_%d.%d { };\n", ver->major, ver->minor);
+        } else {
+            buf_appendf(map_contents, "GLIBC_%d.%d.%d { };\n", ver->major, ver->minor, ver->patch);
+        }
+    }
+
+    if ((err = os_write_file(map_file_path, map_contents))) {
+        if (verbose) {
+            fprintf(stderr, "unable to write %s: %s", buf_ptr(map_file_path), err_str(err));
+        }
+        return err;
+    }
+
+
+    for (size_t lib_i = 0; lib_i < array_length(glibc_libs); lib_i += 1) {
+        const ZigGLibCLib *lib = &glibc_libs[lib_i];
+        Buf *zig_file_path = buf_sprintf("%s" OS_SEP "%s.zig", buf_ptr(dummy_dir), lib->name);
+        Buf *zig_body = buf_alloc();
+        Buf *zig_footer = buf_alloc();
+
+        buf_appendf(zig_body, "comptime {\n");
+        buf_appendf(zig_body, "    asm (\n");
+
+        for (size_t fn_i = 0; fn_i < glibc_abi->all_functions.length; fn_i += 1) {
+            const ZigGLibCFn *libc_fn = &glibc_abi->all_functions.at(fn_i);
+            if (libc_fn->lib != lib) continue;
+            ZigGLibCVerList *ver_list = &ver_list_base[fn_i];
+            // Pick the default symbol version:
+            // - If there are no versions, don't emit it
+            // - Take the greatest one <= than the target one
+            // - If none of them is <= than the
+            //   specified one don't pick any default version 
+            if (ver_list->len == 0) continue;
+            uint8_t chosen_def_ver_index = 255;
+            for (uint8_t ver_i = 0; ver_i < ver_list->len; ver_i += 1) {
+                uint8_t ver_index = ver_list->versions[ver_i];
+                if ((chosen_def_ver_index == 255 || ver_index > chosen_def_ver_index) &&
+                    target_ver_index >= ver_index)
+                {
+                    chosen_def_ver_index = ver_index;
+                }
+            }
+            for (uint8_t ver_i = 0; ver_i < ver_list->len; ver_i += 1) {
+                uint8_t ver_index = ver_list->versions[ver_i];
+
+                Buf *stub_name;
+                const ZigGLibCVersion *ver = &glibc_abi->all_versions.at(ver_index);
+                const char *sym_name = buf_ptr(libc_fn->name);
+                if (ver->patch == 0) {
+                    stub_name = buf_sprintf("%s_%d_%d", sym_name, ver->major, ver->minor);
+                } else {
+                    stub_name = buf_sprintf("%s_%d_%d_%d", sym_name, ver->major, ver->minor, ver->patch);
+                }
+
+                buf_appendf(zig_footer, "export fn %s() void {}\n", buf_ptr(stub_name));
+
+                // Default symbol version definition vs normal symbol version definition
+                const char *at_sign_str = (chosen_def_ver_index != 255 &&
+                        ver_index == chosen_def_ver_index) ? "@@" : "@";
+                if (ver->patch == 0) {
+                    buf_appendf(zig_body, "        \\\\ .symver %s, %s%sGLIBC_%d.%d\n",
+                            buf_ptr(stub_name), sym_name, at_sign_str, ver->major, ver->minor);
+                } else {
+                    buf_appendf(zig_body, "        \\\\ .symver %s, %s%sGLIBC_%d.%d.%d\n",
+                            buf_ptr(stub_name), sym_name, at_sign_str, ver->major, ver->minor, ver->patch);
+                }
+                // Hide the stub to keep the symbol table clean
+                buf_appendf(zig_body, "        \\\\ .hidden %s\n", buf_ptr(stub_name));
+            }
+        }
+
+        buf_appendf(zig_body, "    );\n");
+        buf_appendf(zig_body, "}\n");
+        buf_append_buf(zig_body, zig_footer);
+
+        if ((err = os_write_file(zig_file_path, zig_body))) {
+            if (verbose) {
+                fprintf(stderr, "unable to write %s: %s", buf_ptr(zig_file_path), err_str(err));
+            }
+            return err;
+        }
+
+        CodeGen *child_gen = create_child_codegen(g, zig_file_path, OutTypeLib, nullptr);
+        codegen_set_out_name(child_gen, buf_create_from_str(lib->name));
+        codegen_set_lib_version(child_gen, lib->sover, 0, 0);
+        child_gen->is_dynamic = true;
+        child_gen->is_dummy_so = true;
+        child_gen->version_script_path = map_file_path; 
+        child_gen->enable_cache = false;
+        child_gen->output_dir = dummy_dir;
+        codegen_build_and_link(child_gen);
+    }
+
+    if ((err = os_write_file(test_if_exists_path, buf_alloc()))) {
+        if (verbose) {
+            fprintf(stderr, "unable to write %s: %s", buf_ptr(test_if_exists_path), err_str(err));
+        }
+        return err;
+    }
+    *out_dir = dummy_dir;
+    return ErrorNone;
+}
+
+uint32_t hash_glibc_target(const ZigTarget *x) {
+    return x->arch * 3250106448 +
+        x->os * 542534372 +
+        x->abi * 59162639;
+}
+
+bool eql_glibc_target(const ZigTarget *a, const ZigTarget *b) {
+    return a->arch == b->arch &&
+        a->os == b->os &&
+        a->abi == b->abi;
+}
+
+#ifdef ZIG_OS_LINUX
+#include <unistd.h>
+#include <linux/limits.h>
+Error glibc_detect_native_version(ZigGLibCVersion *glibc_ver) {
+    Buf *self_libc_path = get_self_libc_path();
+    if (self_libc_path == nullptr) {
+        // TODO There is still more we could do to detect the native glibc version. For example,
+        // we could look at the ELF file of `/usr/bin/env`, find `libc.so.6`, and then `readlink`
+        // to find out the glibc version. This is relevant for the static zig builds distributed
+        // on the download page, since the above detection based on zig's own dynamic linking
+        // will not work.
+
+        return ErrorUnknownABI;
+    }
+    Buf *link_name = buf_alloc();
+    buf_resize(link_name, PATH_MAX);
+    ssize_t amt = readlink(buf_ptr(self_libc_path), buf_ptr(link_name), buf_len(link_name));
+    if (amt == -1) {
+        return ErrorUnknownABI;
+    }
+    buf_resize(link_name, amt);
+    if (!buf_starts_with_str(link_name, "libc-") || !buf_ends_with_str(link_name, ".so")) {
+        return ErrorUnknownABI;
+    }
+    // example: "libc-2.3.4.so"
+    // example: "libc-2.27.so"
+    buf_resize(link_name, buf_len(link_name) - 3); // chop off ".so"
+    glibc_ver->major = 2;
+    glibc_ver->minor = 0;
+    glibc_ver->patch = 0;
+    return target_parse_glibc_version(glibc_ver, buf_ptr(link_name) + 5);
+}
+#else
+Error glibc_detect_native_version(ZigGLibCVersion *glibc_ver) {
+    return ErrorUnknownABI;
+}
+#endif
+
+size_t glibc_lib_count(void) {
+    return array_length(glibc_libs);
+}
+
+const ZigGLibCLib *glibc_lib_enum(size_t index) {
+    assert(index < array_length(glibc_libs));
+    return &glibc_libs[index];
+}
+
+const ZigGLibCLib *glibc_lib_find(const char *name) {
+    for (size_t i = 0; i < array_length(glibc_libs); i += 1) {
+        if (strcmp(glibc_libs[i].name, name) == 0) {
+            return &glibc_libs[i];
+        }
+    }
+    return nullptr;
+}
src/glibc.hpp
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2019 Andrew Kelley
+ *
+ * This file is part of zig, which is MIT licensed.
+ * See http://opensource.org/licenses/MIT
+ */
+
+#ifndef ZIG_GLIBC_HPP
+#define ZIG_GLIBC_HPP
+
+#include "all_types.hpp"
+
+struct ZigGLibCLib {
+    const char *name;
+    uint8_t sover;
+};
+
+struct ZigGLibCFn {
+    Buf *name;
+    const ZigGLibCLib *lib;
+};
+
+struct ZigGLibCVerList {
+    uint8_t versions[8]; // 8 is just the max number, we know statically it's big enough
+    uint8_t len;
+};
+
+uint32_t hash_glibc_target(const ZigTarget *x);
+bool eql_glibc_target(const ZigTarget *a, const ZigTarget *b);
+
+struct ZigGLibCAbi {
+    Buf *abi_txt_path;
+    Buf *vers_txt_path;
+    Buf *fns_txt_path;
+    ZigList<ZigGLibCVersion> all_versions;
+    ZigList<ZigGLibCFn> all_functions;
+    // The value is a pointer to all_functions.length items and each item is an index
+    // into all_functions.
+    HashMap<const ZigTarget *, ZigGLibCVerList *, hash_glibc_target, eql_glibc_target> version_table;
+};
+
+Error glibc_load_metadata(ZigGLibCAbi **out_result, Buf *zig_lib_dir, bool verbose);
+Error glibc_build_dummies_and_maps(CodeGen *codegen, const ZigGLibCAbi *glibc_abi, const ZigTarget *target,
+        Buf **out_dir, bool verbose);
+
+// returns ErrorUnknownABI when glibc is not the native libc
+Error glibc_detect_native_version(ZigGLibCVersion *glibc_ver);
+
+size_t glibc_lib_count(void);
+const ZigGLibCLib *glibc_lib_enum(size_t index);
+const ZigGLibCLib *glibc_lib_find(const char *name);
+
+#endif
src/link.cpp
@@ -11,6 +11,7 @@
 #include "analyze.hpp"
 #include "compiler.hpp"
 #include "install_files.h"
+#include "glibc.hpp"
 
 struct LinkJob {
     CodeGen *codegen;
@@ -19,37 +20,6 @@ struct LinkJob {
     HashMap<Buf *, bool, buf_hash, buf_eql_buf> rpath_table;
 };
 
-static CodeGen *create_child_codegen(CodeGen *parent_gen, Buf *root_src_path, OutType out_type,
-        ZigLibCInstallation *libc)
-{
-    CodeGen *child_gen = codegen_create(nullptr, root_src_path, parent_gen->zig_target, out_type,
-        parent_gen->build_mode, parent_gen->zig_lib_dir, parent_gen->zig_std_dir, libc, get_stage1_cache_path());
-    child_gen->disable_gen_h = true;
-    child_gen->want_stack_check = WantStackCheckDisabled;
-    child_gen->verbose_tokenize = parent_gen->verbose_tokenize;
-    child_gen->verbose_ast = parent_gen->verbose_ast;
-    child_gen->verbose_link = parent_gen->verbose_link;
-    child_gen->verbose_ir = parent_gen->verbose_ir;
-    child_gen->verbose_llvm_ir = parent_gen->verbose_llvm_ir;
-    child_gen->verbose_cimport = parent_gen->verbose_cimport;
-    child_gen->verbose_cc = parent_gen->verbose_cc;
-    child_gen->llvm_argv = parent_gen->llvm_argv;
-    child_gen->dynamic_linker_path = parent_gen->dynamic_linker_path;
-
-    codegen_set_strip(child_gen, parent_gen->strip_debug_symbols);
-    child_gen->want_pic = parent_gen->have_pic ? WantPICEnabled : WantPICDisabled;
-    child_gen->valgrind_support = ValgrindSupportDisabled;
-
-    codegen_set_errmsg_color(child_gen, parent_gen->err_color);
-
-    codegen_set_mmacosx_version_min(child_gen, parent_gen->mmacosx_version_min);
-    codegen_set_mios_version_min(child_gen, parent_gen->mios_version_min);
-
-    child_gen->enable_cache = true;
-
-    return child_gen;
-}
-
 static const char *build_libc_object(CodeGen *parent_gen, const char *name, CFile *c_file) {
     CodeGen *child_gen = create_child_codegen(parent_gen, nullptr, OutTypeObj, nullptr);
     codegen_set_out_name(child_gen, buf_create_from_str(name));
@@ -76,18 +46,6 @@ static const char *path_from_libunwind(CodeGen *g, const char *subpath) {
     return path_from_zig_lib(g, "libunwind", subpath);
 }
 
-static const char *build_dummy_so(CodeGen *parent, const char *name, size_t major_version) {
-    Buf *glibc_dummy_root_src = buf_sprintf("%s" OS_SEP "libc" OS_SEP "dummy" OS_SEP "%s.zig",
-            buf_ptr(parent->zig_lib_dir), name);
-    CodeGen *child_gen = create_child_codegen(parent, glibc_dummy_root_src, OutTypeLib, nullptr);
-    codegen_set_out_name(child_gen, buf_create_from_str(name));
-    codegen_set_lib_version(child_gen, major_version, 0, 0);
-    child_gen->is_dynamic = true;
-    child_gen->is_dummy_so = true;
-    codegen_build_and_link(child_gen);
-    return buf_ptr(&child_gen->output_file_path);
-}
-
 static const char *build_libunwind(CodeGen *parent) {
     CodeGen *child_gen = create_child_codegen(parent, nullptr, OutTypeLib, nullptr);
     codegen_set_out_name(child_gen, buf_create_from_str("unwind"));
@@ -892,6 +850,30 @@ static void add_rpath(LinkJob *lj, Buf *rpath) {
     lj->rpath_table.put(rpath, true);
 }
 
+static void add_glibc_libs(LinkJob *lj) {
+    Error err;
+    ZigGLibCAbi *glibc_abi;
+    if ((err = glibc_load_metadata(&glibc_abi, lj->codegen->zig_lib_dir, true))) {
+        fprintf(stderr, "%s\n", err_str(err));
+        exit(1);
+    }
+
+    Buf *artifact_dir;
+    if ((err = glibc_build_dummies_and_maps(lj->codegen, glibc_abi, lj->codegen->zig_target,
+                    &artifact_dir, true)))
+    {
+        fprintf(stderr, "%s\n", err_str(err));
+        exit(1);
+    }
+
+    size_t lib_count = glibc_lib_count();
+    for (size_t i = 0; i < lib_count; i += 1) {
+        const ZigGLibCLib *lib = glibc_lib_enum(i);
+        Buf *so_path = buf_sprintf("%s" OS_SEP "lib%s.so.%d.0.0", buf_ptr(artifact_dir), lib->name, lib->sover);
+        lj->args.append(buf_ptr(so_path));
+    }
+}
+
 static void construct_linker_job_elf(LinkJob *lj) {
     CodeGen *g = lj->codegen;
 
@@ -990,6 +972,11 @@ static void construct_linker_job_elf(LinkJob *lj) {
     if (is_dyn_lib) {
         lj->args.append("-soname");
         lj->args.append(buf_ptr(soname));
+
+        if (g->version_script_path != nullptr) {
+            lj->args.append("-version-script");
+            lj->args.append(buf_ptr(g->version_script_path));
+        }
     }
 
     // .o files
@@ -1053,11 +1040,7 @@ static void construct_linker_job_elf(LinkJob *lj) {
             }
         } else if (target_is_glibc(g->zig_target)) {
             lj->args.append(build_libunwind(g));
-            lj->args.append(build_dummy_so(g, "c", 6));
-            lj->args.append(build_dummy_so(g, "m", 6));
-            lj->args.append(build_dummy_so(g, "pthread", 0));
-            lj->args.append(build_dummy_so(g, "dl", 2));
-            lj->args.append(build_dummy_so(g, "rt", 1));
+            add_glibc_libs(lj);
             lj->args.append(get_libc_crt_file(g, "libc_nonshared.a"));
         } else if (target_is_musl(g->zig_target)) {
             lj->args.append(build_libunwind(g));
src/main.cpp
@@ -15,6 +15,7 @@
 #include "target.hpp"
 #include "libc_installation.hpp"
 #include "userland.h"
+#include "glibc.hpp"
 
 #include <stdio.h>
 
@@ -96,6 +97,7 @@ static int print_full_usage(const char *arg0, FILE *file, int return_code) {
         "  --forbid-library [lib]       make it an error to link against lib\n"
         "  --library-path [dir]         add a directory to the library search path\n"
         "  --linker-script [path]       use a custom linker script\n"
+        "  --version-script [path]      provide a version .map file\n"
         "  --object [obj]               add object file to build\n"
         "  -L[dir]                      alias for --library-path\n"
         "  -rdynamic                    add all symbols to the dynamic symbol table\n"
@@ -189,10 +191,33 @@ static int print_target_list(FILE *f) {
     for (size_t i = 0; i < libc_count; i += 1) {
         ZigTarget libc_target;
         target_libc_enum(i, &libc_target);
-        fprintf(f, "  %s-%s-%s\n", target_arch_name(libc_target.arch),
-                target_os_name(libc_target.os), target_abi_name(libc_target.abi));
+        bool is_native = native.arch == libc_target.arch &&
+            native.os == libc_target.os &&
+            native.abi == libc_target.abi;
+        const char *native_str = is_native ? " (native)" : "";
+        fprintf(f, "  %s-%s-%s%s\n", target_arch_name(libc_target.arch),
+                target_os_name(libc_target.os), target_abi_name(libc_target.abi), native_str);
     }
 
+    fprintf(f, "\nAvailable glibc versions:\n");
+    ZigGLibCAbi *glibc_abi;
+    Error err;
+    if ((err = glibc_load_metadata(&glibc_abi, get_zig_lib_dir(), true))) {
+        return EXIT_FAILURE;
+    }
+    for (size_t i = 0; i < glibc_abi->all_versions.length; i += 1) {
+        ZigGLibCVersion *this_ver = &glibc_abi->all_versions.at(i);
+        bool is_native = native.glibc_version != nullptr &&
+            native.glibc_version->major == this_ver->major &&
+            native.glibc_version->minor == this_ver->minor &&
+            native.glibc_version->patch == this_ver->patch;
+        const char *native_str = is_native ? " (native)" : "";
+        if (this_ver->patch == 0) {
+            fprintf(f, "  %d.%d%s\n", this_ver->major, this_ver->minor, native_str);
+        } else {
+            fprintf(f, "  %d.%d.%d%s\n", this_ver->major, this_ver->minor, this_ver->patch, native_str);
+        }
+    }
     return EXIT_SUCCESS;
 }
 
@@ -437,6 +462,8 @@ int main(int argc, char **argv) {
     const char *mmacosx_version_min = nullptr;
     const char *mios_version_min = nullptr;
     const char *linker_script = nullptr;
+    Buf *version_script = nullptr;
+    const char *target_glibc = nullptr;
     ZigList<const char *> rpath_list = {0};
     bool each_lib_rpath = false;
     ZigList<const char *> objects = {0};
@@ -783,6 +810,10 @@ int main(int argc, char **argv) {
                     frameworks.append(argv[i]);
                 } else if (strcmp(arg, "--linker-script") == 0) {
                     linker_script = argv[i];
+                } else if (strcmp(arg, "--version-script") == 0) {
+                    version_script = buf_create_from_str(argv[i]); 
+                } else if (strcmp(arg, "-target-glibc") == 0) {
+                    target_glibc = argv[i];
                 } else if (strcmp(arg, "-rpath") == 0) {
                     rpath_list.append(argv[i]);
                 } else if (strcmp(arg, "--test-filter") == 0) {
@@ -904,6 +935,10 @@ int main(int argc, char **argv) {
     ZigTarget target;
     if (target_string == nullptr) {
         get_native_target(&target);
+        if (target_glibc != nullptr) {
+            fprintf(stderr, "-target-glibc provided but no -target parameter\n");
+            return print_error_usage(arg0);
+        }
     } else {
         if ((err = target_parse_triple(&target, target_string))) {
             if (err == ErrorUnknownArchitecture && target.arch != ZigLLVM_UnknownArch) {
@@ -921,6 +956,22 @@ int main(int argc, char **argv) {
                 return print_error_usage(arg0);
             }
         }
+        if (target_is_glibc(&target)) {
+            target.glibc_version = allocate<ZigGLibCVersion>(1);
+
+            if (target_glibc != nullptr) {
+                if ((err = target_parse_glibc_version(target.glibc_version, target_glibc))) {
+                    fprintf(stderr, "invalid glibc version '%s': %s\n", target_glibc, err_str(err));
+                    return print_error_usage(arg0);
+                }
+            } else {
+                // Default cross-compiling glibc version
+                *target.glibc_version = {2, 17, 0};
+            }
+        } else if (target_glibc != nullptr) {
+            fprintf(stderr, "'%s' is not a glibc-compatible target", target_string);
+            return print_error_usage(arg0);
+        }
     }
 
     if (output_dir != nullptr && enable_cache == CacheOptOn) {
@@ -1074,6 +1125,7 @@ int main(int argc, char **argv) {
             codegen_set_is_test(g, cmd == CmdTest);
             g->want_single_threaded = want_single_threaded;
             codegen_set_linker_script(g, linker_script);
+            g->version_script_path = version_script; 
             if (each_lib_rpath)
                 codegen_set_each_lib_rpath(g, each_lib_rpath);
 
src/target.cpp
@@ -10,6 +10,8 @@
 #include "target.hpp"
 #include "util.hpp"
 #include "os.hpp"
+#include "compiler.hpp"
+#include "glibc.hpp"
 
 #include <stdio.h>
 
@@ -466,6 +468,29 @@ const char *target_abi_name(ZigLLVM_EnvironmentType abi) {
     return ZigLLVMGetEnvironmentTypeName(abi);
 }
 
+Error target_parse_glibc_version(ZigGLibCVersion *glibc_ver, const char *text) {
+    glibc_ver->major = 2;
+    glibc_ver->minor = 0;
+    glibc_ver->patch = 0;
+    SplitIterator it = memSplit(str(text), str("GLIBC_."));
+    {
+        Optional<Slice<uint8_t>> opt_component = SplitIterator_next(&it);
+        if (!opt_component.is_some) return ErrorUnknownABI;
+        glibc_ver->major = strtoul(buf_ptr(buf_create_from_slice(opt_component.value)), nullptr, 10);
+    }
+    {
+        Optional<Slice<uint8_t>> opt_component = SplitIterator_next(&it);
+        if (!opt_component.is_some) return ErrorNone;
+        glibc_ver->minor = strtoul(buf_ptr(buf_create_from_slice(opt_component.value)), nullptr, 10);
+    }
+    {
+        Optional<Slice<uint8_t>> opt_component = SplitIterator_next(&it);
+        if (!opt_component.is_some) return ErrorNone;
+        glibc_ver->patch = strtoul(buf_ptr(buf_create_from_slice(opt_component.value)), nullptr, 10);
+    }
+    return ErrorNone;
+}
+
 void get_native_target(ZigTarget *target) {
     ZigLLVM_OSType os_type;
     ZigLLVM_ObjectFormatType oformat; // ignored; based on arch/os
@@ -481,6 +506,17 @@ void get_native_target(ZigTarget *target) {
     if (target->abi == ZigLLVM_UnknownEnvironment) {
         target->abi = target_default_abi(target->arch, target->os);
     }
+    target->glibc_version = nullptr;
+#ifdef ZIG_OS_LINUX
+    if (target_is_glibc(target)) {
+        target->glibc_version = allocate<ZigGLibCVersion>(1);
+        Error err;
+        if ((err = glibc_detect_native_version(target->glibc_version))) {
+            // Use a default version.
+            *target->glibc_version = {2, 17, 0};
+        }
+    }
+#endif
 }
 
 Error target_parse_archsub(ZigLLVM_ArchType *out_arch, ZigLLVM_SubArchType *out_sub,
src/target.hpp
@@ -77,12 +77,19 @@ enum TargetSubsystem {
     TargetSubsystemAuto
 };
 
+struct ZigGLibCVersion {
+    uint32_t major; // always 2
+    uint32_t minor;
+    uint32_t patch;
+};
+
 struct ZigTarget {
     ZigLLVM_ArchType arch;
     ZigLLVM_SubArchType sub_arch;
     ZigLLVM_VendorType vendor;
     Os os;
     ZigLLVM_EnvironmentType abi;
+    ZigGLibCVersion *glibc_version; // null means default
     bool is_native;
 };
 
@@ -105,6 +112,8 @@ Error target_parse_archsub(ZigLLVM_ArchType *arch, ZigLLVM_SubArchType *sub,
 Error target_parse_os(Os *os, const char *os_ptr, size_t os_len);
 Error target_parse_abi(ZigLLVM_EnvironmentType *abi, const char *abi_ptr, size_t abi_len);
 
+Error target_parse_glibc_version(ZigGLibCVersion *out, const char *text);
+
 size_t target_arch_count(void);
 ZigLLVM_ArchType target_arch_enum(size_t index);
 const char *target_arch_name(ZigLLVM_ArchType arch);
src/util.cpp
@@ -86,6 +86,32 @@ Optional<Slice<uint8_t>> SplitIterator_next(SplitIterator *self) {
     return Optional<Slice<uint8_t>>::some(self->buffer.slice(start, end));
 }
 
+// Ported from std/mem.zig.
+// This one won't collapse multiple separators into one, so you could use it, for example,
+// to parse Comma Separated Value format.
+Optional<Slice<uint8_t>> SplitIterator_next_separate(SplitIterator *self) {
+    // move to beginning of token
+    if (self->index < self->buffer.len &&
+        SplitIterator_isSplitByte(self, self->buffer.ptr[self->index]))
+    {
+        self->index += 1;
+    }
+    size_t start = self->index;
+    if (start == self->buffer.len) {
+        return {};
+    }
+
+    // move to end of token
+    while (self->index < self->buffer.len &&
+        !SplitIterator_isSplitByte(self, self->buffer.ptr[self->index]))
+    {
+        self->index += 1;
+    }
+    size_t end = self->index;
+
+    return Optional<Slice<uint8_t>>::some(self->buffer.slice(start, end));
+}
+
 // Ported from std/mem.zig
 Slice<uint8_t> SplitIterator_rest(SplitIterator *self) {
     // move to beginning of token
src/util.hpp
@@ -314,6 +314,7 @@ struct SplitIterator {
 
 bool SplitIterator_isSplitByte(SplitIterator *self, uint8_t byte);
 Optional< Slice<uint8_t> > SplitIterator_next(SplitIterator *self);
+Optional< Slice<uint8_t> > SplitIterator_next_separate(SplitIterator *self);
 Slice<uint8_t> SplitIterator_rest(SplitIterator *self);
 SplitIterator memSplit(Slice<uint8_t> buffer, Slice<uint8_t> split_bytes);
 
CMakeLists.txt
@@ -421,6 +421,7 @@ set(ZIG_MAIN_SRC "${CMAKE_SOURCE_DIR}/src/main.cpp")
 set(ZIG0_SHIM_SRC "${CMAKE_SOURCE_DIR}/src/userland.cpp")
 
 set(ZIG_SOURCES
+    "${CMAKE_SOURCE_DIR}/src/glibc.cpp"
     "${CMAKE_SOURCE_DIR}/src/analyze.cpp"
     "${CMAKE_SOURCE_DIR}/src/ast_render.cpp"
     "${CMAKE_SOURCE_DIR}/src/bigfloat.cpp"
@@ -2667,6 +2668,9 @@ set(ZIG_MUSL_SRC_FILES
 )
 
 set(ZIG_LIBC_FILES
+    "glibc/abi.txt"
+    "glibc/fns.txt"
+    "glibc/vers.txt"
     "glibc/bits/byteswap.h"
     "glibc/bits/endian.h"
     "glibc/bits/floatn-common.h"