Commit 984e7d6cc7

Andrew Kelley <superjoe30@gmail.com>
2016-02-16 04:56:52
first pass at linking on macos
1 parent f580c7f
src/all_types.hpp
@@ -1130,6 +1130,10 @@ struct CodeGen {
     bool windows_subsystem_windows;
     bool windows_subsystem_console;
     bool windows_linker_unicode;
+    Buf *darwin_linker_version;
+    Buf *mmacosx_version_min;
+    Buf *mios_version_min;
+    bool linker_rdynamic;
 
     // The function definitions this module includes. There must be a corresponding
     // fn_protos entry.
src/codegen.cpp
@@ -22,6 +22,31 @@
 #include <errno.h>
 
 
+static void init_darwin_native(CodeGen *g) {
+    char *osx_target = getenv("MACOSX_DEPLOYMENT_TARGET");
+    char *ios_target = getenv("IPHONEOS_DEPLOYMENT_TARGET");
+
+    // Allow conflicts among OSX and iOS, but choose the default platform.
+    if (osx_target && ios_target) {
+        if (g->zig_target.arch.arch == ZigLLVM_arm ||
+            g->zig_target.arch.arch == ZigLLVM_aarch64 ||
+            g->zig_target.arch.arch == ZigLLVM_thumb)
+        {
+            osx_target = nullptr;
+        } else {
+            ios_target = nullptr;
+        }
+    }
+
+    if (osx_target) {
+        g->mmacosx_version_min = buf_create_from_str(osx_target);
+    } else if (ios_target) {
+        g->mios_version_min = buf_create_from_str(ios_target);
+    } else {
+        zig_panic("unable to determine -mmacosx-version-min or -mios-version-min");
+    }
+}
+
 CodeGen *codegen_create(Buf *root_source_dir, const ZigTarget *target) {
     CodeGen *g = allocate<CodeGen>(1);
     g->import_table.init(32);
@@ -46,6 +71,7 @@ CodeGen *codegen_create(Buf *root_source_dir, const ZigTarget *target) {
         g->libc_static_lib_dir = buf_create_from_str("");
         g->libc_include_dir = buf_create_from_str("");
         g->linker_path = buf_create_from_str("");
+        g->darwin_linker_version = buf_create_from_str("");
     } else {
         // native compilation, we can rely on the configuration stuff
         g->is_native_target = true;
@@ -56,6 +82,15 @@ CodeGen *codegen_create(Buf *root_source_dir, const ZigTarget *target) {
         g->libc_static_lib_dir = buf_create_from_str(ZIG_LIBC_STATIC_LIB_DIR);
         g->libc_include_dir = buf_create_from_str(ZIG_LIBC_INCLUDE_DIR);
         g->linker_path = buf_create_from_str(ZIG_LD_PATH);
+        g->darwin_linker_version = buf_create_from_str(ZIG_HOST_LINK_VERSION);
+
+        if (g->zig_target.os == ZigLLVM_Darwin ||
+            g->zig_target.os == ZigLLVM_MacOSX ||
+            g->zig_target.os == ZigLLVM_IOS)
+        {
+            init_darwin_native(g);
+        }
+
     }
 
     return g;
@@ -131,6 +166,22 @@ void codegen_set_windows_unicode(CodeGen *g, bool municode) {
     g->windows_linker_unicode = municode;
 }
 
+void codegen_set_mlinker_version(CodeGen *g, Buf *darwin_linker_version) {
+    g->darwin_linker_version = darwin_linker_version;
+}
+
+void codegen_set_mmacosx_version_min(CodeGen *g, Buf *mmacosx_version_min) {
+    g->mmacosx_version_min = mmacosx_version_min;
+}
+
+void codegen_set_mios_version_min(CodeGen *g, Buf *mios_version_min) {
+    g->mios_version_min = mios_version_min;
+}
+
+void codegen_set_rdynamic(CodeGen *g, bool rdynamic) {
+    g->linker_rdynamic = rdynamic;
+}
+
 static LLVMValueRef gen_expr(CodeGen *g, AstNode *expr_node);
 static LLVMValueRef gen_lvalue(CodeGen *g, AstNode *expr_node, AstNode *node, TypeTableEntry **out_type_entry);
 static LLVMValueRef gen_field_access_expr(CodeGen *g, AstNode *node, bool is_lvalue);
src/codegen.hpp
@@ -34,6 +34,10 @@ void codegen_set_linker_path(CodeGen *g, Buf *linker_path);
 void codegen_set_windows_subsystem(CodeGen *g, bool mwindows, bool mconsole);
 void codegen_set_windows_unicode(CodeGen *g, bool municode);
 void codegen_add_lib_dir(CodeGen *codegen, const char *dir);
+void codegen_set_mlinker_version(CodeGen *g, Buf *darwin_linker_version);
+void codegen_set_rdynamic(CodeGen *g, bool rdynamic);
+void codegen_set_mmacosx_version_min(CodeGen *g, Buf *mmacosx_version_min);
+void codegen_set_mios_version_min(CodeGen *g, Buf *mios_version_min);
 
 void codegen_add_root_code(CodeGen *g, Buf *source_dir, Buf *source_basename, Buf *source_code);
 
src/config.h.in
@@ -13,6 +13,7 @@
 #define ZIG_LIBC_STATIC_LIB_DIR "@ZIG_LIBC_STATIC_LIB_DIR@"
 #define ZIG_LD_PATH "@ZIG_LD_PATH@"
 #define ZIG_DYNAMIC_LINKER "@ZIG_DYNAMIC_LINKER@"
+#define ZIG_HOST_LINK_VERSION "@ZIG_HOST_LINK_VERSION@"
 
 #cmakedefine ZIG_LLVM_OLD_CXX_ABI
 
src/link.cpp
@@ -50,6 +50,9 @@ static Buf *build_o(CodeGen *parent_gen, const char *oname) {
     codegen_set_verbose(child_gen, parent_gen->verbose);
     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);
+
     Buf *full_path = buf_alloc();
     os_path_join(std_dir_path, source_basename, full_path);
     Buf source_code = BUF_INIT;
@@ -391,38 +394,251 @@ static void construct_linker_job_mingw(LinkJob *lj) {
     }
 }
 
+
+// Parse (([0-9]+)(.([0-9]+)(.([0-9]+)?))?)? and return the
+// grouped values as integers. Numbers which are not provided are set to 0.
+// return true if the entire string was parsed (9.2), or all groups were
+// parsed (10.3.5extrastuff).
+static bool darwin_get_release_version(const char *str, int *major, int *minor, int *micro, bool *had_extra) {
+    *had_extra = false;
+
+    *major = 0;
+    *minor = 0;
+    *micro = 0;
+
+    if (*str == '\0')
+        return false;
+
+    char *end;
+    *major = strtol(str, &end, 10);
+    if (*str != '\0' && *end == '\0')
+        return true;
+    if (*end != '.')
+        return false;
+
+    str = end + 1;
+    *minor = strtol(str, &end, 10);
+    if (*str != '\0' && *end == '\0')
+        return true;
+    if (*end != '.')
+        return false;
+
+    str = end + 1;
+    *micro = strtol(str, &end, 10);
+    if (*str != '\0' && *end == '\0')
+        return true;
+    if (str == end)
+        return false;
+    *had_extra = true;
+    return true;
+}
+
+enum DarwinPlatformKind {
+    MacOS,
+    IPhoneOS,
+    IPhoneOSSimulator,
+};
+
+struct DarwinPlatform {
+    DarwinPlatformKind kind;
+    int major;
+    int minor;
+    int micro;
+};
+
+static void get_darwin_platform(LinkJob *lj, DarwinPlatform *platform) {
+    CodeGen *g = lj->codegen;
+
+    if (g->mmacosx_version_min) {
+        platform->kind = MacOS;
+    } else if (g->mios_version_min) {
+        platform->kind = IPhoneOS;
+    } else {
+        zig_panic("unable to infer -macosx-version-min or -mios-version-min");
+    }
+
+    bool had_extra;
+    if (platform->kind == MacOS) {
+        if (!darwin_get_release_version(buf_ptr(g->mmacosx_version_min),
+                    &platform->major, &platform->minor, &platform->micro, &had_extra) ||
+                had_extra || platform->major != 10 || platform->minor >= 100 || platform->micro >= 100)
+        {
+            zig_panic("invalid -mmacosx-version-min");
+        }
+    } else if (platform->kind == IPhoneOS) {
+        if (!darwin_get_release_version(buf_ptr(g->mios_version_min),
+                    &platform->major, &platform->minor, &platform->micro, &had_extra) ||
+                had_extra || platform->major >= 10 || platform->minor >= 100 || platform->micro >= 100)
+        {
+            zig_panic("invalid -mios-version-min");
+        }
+    } else {
+        zig_unreachable();
+    }
+
+    if (platform->kind == IPhoneOS &&
+        (g->zig_target.arch.arch == ZigLLVM_x86 ||
+         g->zig_target.arch.arch == ZigLLVM_x86_64))
+    {
+        platform->kind = IPhoneOSSimulator;
+    }
+}
+
+static bool darwin_version_lt(DarwinPlatform *platform, int major, int minor) {
+    if (platform->major < major) {
+        return true;
+    } else if (platform->major > major) {
+        return false;
+    }
+    if (platform->minor < minor) {
+        return true;
+    }
+    return false;
+}
+
+static void construct_linker_job_darwin(LinkJob *lj) {
+    CodeGen *g = lj->codegen;
+
+    int ver_major;
+    int ver_minor;
+    int ver_micro;
+    bool had_extra;
+
+    if (!darwin_get_release_version(buf_ptr(g->darwin_linker_version), &ver_major, &ver_minor, &ver_micro,
+                &had_extra) || had_extra)
+    {
+        zig_panic("invalid linker version number");
+    }
+
+    // Newer linkers support -demangle. Pass it if supported and not disabled by
+    // the user.
+    if (ver_major >= 100) {
+        lj->args.append("-demangle");
+    }
+
+    if (g->linker_rdynamic && ver_major >= 137) {
+        lj->args.append("-export_dynamic");
+    }
+
+    bool is_lib = g->out_type == OutTypeLib;
+    bool shared = !g->is_static && is_lib;
+    if (g->is_static) {
+        lj->args.append("-static");
+    } else {
+        lj->args.append("-dynamic");
+    }
+
+    if (is_lib) {
+        zig_panic("TODO linker args on darwin for making a library");
+    }
+
+    DarwinPlatform platform;
+    get_darwin_platform(lj, &platform);
+    switch (platform.kind) {
+        case MacOS:
+            lj->args.append("-macosx_version_min");
+            break;
+        case IPhoneOS:
+            lj->args.append("-iphoneos_version_min");
+            break;
+        case IPhoneOSSimulator:
+            lj->args.append("-ios_simulator_version_min");
+            break;
+    }
+    lj->args.append(buf_ptr(buf_sprintf("%d.%d.%d", platform.major, platform.minor, platform.micro)));
+
+
+    if (g->out_type == OutTypeExe) {
+        if (g->is_static) {
+            lj->args.append("-no_pie");
+        } else {
+            lj->args.append("-pie");
+        }
+    }
+
+    lj->args.append("-o");
+    lj->args.append(buf_ptr(&lj->out_file));
+
+    if (lj->link_in_crt) {
+        if (shared) {
+            zig_panic("TODO");
+        } else if (g->is_static) {
+            lj->args.append("-lcrt0.o");
+        } else {
+            switch (platform.kind) {
+                case MacOS:
+                    if (darwin_version_lt(&platform, 10, 5)) {
+                        lj->args.append("-lcrt1.o");
+                    } else if (darwin_version_lt(&platform, 10, 6)) {
+                        lj->args.append("-lcrt1.10.5.o");
+                    } else if (darwin_version_lt(&platform, 10, 8)) {
+                        lj->args.append("-lcrt1.10.6.o");
+                    }
+                    break;
+                case IPhoneOS:
+                    if (g->zig_target.arch.arch == ZigLLVM_aarch64) {
+                        // iOS does not need any crt1 files for arm64
+                    } else if (darwin_version_lt(&platform, 3, 1)) {
+                        lj->args.append("-lcrt1.o");
+                    } else if (darwin_version_lt(&platform, 6, 0)) {
+                        lj->args.append("-lcrt1.3.1.o");
+                    }
+                    break;
+                case IPhoneOSSimulator:
+                    // no crt1.o needed
+                    break;
+            }
+        }
+    }
+
+    for (int i = 0; i < g->lib_dirs.length; i += 1) {
+        const char *lib_dir = g->lib_dirs.at(i);
+        lj->args.append("-L");
+        lj->args.append(lib_dir);
+    }
+
+    lj->args.append((const char *)buf_ptr(&lj->out_file_o));
+
+    for (int i = 0; i < g->link_libs.length; i += 1) {
+        Buf *link_lib = g->link_libs.at(i);
+        Buf *arg = buf_sprintf("-l%s", buf_ptr(link_lib));
+        lj->args.append(buf_ptr(arg));
+    }
+
+}
+
 static void construct_linker_job(LinkJob *lj) {
     switch (lj->codegen->zig_target.os) {
         case ZigLLVM_UnknownOS:
-             zig_unreachable();
+            zig_unreachable();
         case ZigLLVM_Linux:
-             if (lj->codegen->zig_target.arch.arch == ZigLLVM_hexagon) {
+            if (lj->codegen->zig_target.arch.arch == ZigLLVM_hexagon) {
                 zig_panic("TODO construct hexagon_TC linker job");
-             } else {
+            } else {
                 return construct_linker_job_linux(lj);
-             }
+            }
         case ZigLLVM_CloudABI:
-             zig_panic("TODO construct CloudABI linker job");
+            zig_panic("TODO construct CloudABI linker job");
         case ZigLLVM_Darwin:
         case ZigLLVM_MacOSX:
         case ZigLLVM_IOS:
-             zig_panic("TODO construct DarwinClang linker job");
+            return construct_linker_job_darwin(lj);
         case ZigLLVM_DragonFly:
-             zig_panic("TODO construct DragonFly linker job");
+            zig_panic("TODO construct DragonFly linker job");
         case ZigLLVM_OpenBSD:
-             zig_panic("TODO construct OpenBSD linker job");
+            zig_panic("TODO construct OpenBSD linker job");
         case ZigLLVM_Bitrig:
-             zig_panic("TODO construct Bitrig linker job");
+            zig_panic("TODO construct Bitrig linker job");
         case ZigLLVM_NetBSD:
-             zig_panic("TODO construct NetBSD linker job");
+            zig_panic("TODO construct NetBSD linker job");
         case ZigLLVM_FreeBSD:
-             zig_panic("TODO construct FreeBSD linker job");
+            zig_panic("TODO construct FreeBSD linker job");
         case ZigLLVM_Minix:
-             zig_panic("TODO construct Minix linker job");
+            zig_panic("TODO construct Minix linker job");
         case ZigLLVM_NaCl:
-             zig_panic("TODO construct NaCl_TC linker job");
+            zig_panic("TODO construct NaCl_TC linker job");
         case ZigLLVM_Solaris:
-             zig_panic("TODO construct Solaris linker job");
+            zig_panic("TODO construct Solaris linker job");
         case ZigLLVM_Win32:
             switch (lj->codegen->zig_target.environ) {
                 default:
@@ -440,27 +656,27 @@ static void construct_linker_job(LinkJob *lj) {
                 case ZigLLVM_MSVC:
                 case ZigLLVM_UnknownEnvironment:
                     zig_panic("TODO construct MSVC linker job");
-             }
+            }
         case ZigLLVM_CUDA:
-             zig_panic("TODO construct Cuda linker job");
+            zig_panic("TODO construct Cuda linker job");
         default:
-             // Of these targets, Hexagon is the only one that might have
-             // an OS of Linux, in which case it got handled above already.
-             if (lj->codegen->zig_target.arch.arch == ZigLLVM_tce) {
+            // Of these targets, Hexagon is the only one that might have
+            // an OS of Linux, in which case it got handled above already.
+            if (lj->codegen->zig_target.arch.arch == ZigLLVM_tce) {
                 zig_panic("TODO construct TCE linker job");
-             } else if (lj->codegen->zig_target.arch.arch == ZigLLVM_hexagon) {
+            } else if (lj->codegen->zig_target.arch.arch == ZigLLVM_hexagon) {
                 zig_panic("TODO construct Hexagon_TC linker job");
-             } else if (lj->codegen->zig_target.arch.arch == ZigLLVM_xcore) {
+            } else if (lj->codegen->zig_target.arch.arch == ZigLLVM_xcore) {
                 zig_panic("TODO construct XCore linker job");
-             } else if (lj->codegen->zig_target.arch.arch == ZigLLVM_shave) {
+            } else if (lj->codegen->zig_target.arch.arch == ZigLLVM_shave) {
                 zig_panic("TODO construct SHAVE linker job");
-             } else if (lj->codegen->zig_target.oformat == ZigLLVM_ELF) {
+            } else if (lj->codegen->zig_target.oformat == ZigLLVM_ELF) {
                 zig_panic("TODO construct Generic_ELF linker job");
-             } else if (lj->codegen->zig_target.oformat == ZigLLVM_MachO) {
+            } else if (lj->codegen->zig_target.oformat == ZigLLVM_MachO) {
                 zig_panic("TODO construct MachO linker job");
-             } else {
+            } else {
                 zig_panic("TODO construct Generic_GCC linker job");
-             }
+            }
 
     }
 }
@@ -535,7 +751,6 @@ void codegen_link(CodeGen *g, const char *out_file) {
     }
 
     lj.link_in_crt = (g->link_libc && g->out_type == OutTypeExe);
-    // invoke `ld`
     ensure_we_have_linker_path(g);
 
     construct_linker_job(&lj);
src/main.cpp
@@ -18,10 +18,10 @@
 static int usage(const char *arg0) {
     fprintf(stderr, "Usage: %s [command] [options]\n"
         "Commands:\n"
-        "  build                        create executable, object, or library from target\n"
-        "  test                         create and run a test build\n"
+        "  build [source]               create executable, object, or library from source\n"
+        "  test [source]                create and run a test build\n"
+        "  parseh [source]              convert a c header file to zig extern declarations\n"
         "  version                      print version number and exit\n"
-        "  parseh                       convert a c header file to zig extern declarations\n"
         "  targets                      list available compilation targets\n"
         "Options:\n"
         "  --release                    build with optimizations on and debug protection off\n"
@@ -33,7 +33,7 @@ static int usage(const char *arg0) {
         "  --verbose                    turn on compiler debug output\n"
         "  --color [auto|off|on]        enable or disable colored error messages\n"
         "  --libc-lib-dir [path]        directory where libc crt1.o resides\n"
-        "  --libc-static-lib-dir [path] directory where libc crtbeginT.o resides\n"
+        "  --libc-static-lib-dir [path] directory where libc crtbegin.o resides\n"
         "  --libc-include-dir [path]    directory where libc stdlib.h resides\n"
         "  --dynamic-linker [path]      set the path to ld.so\n"
         "  --ld-path [path]             set the path to the linker\n"
@@ -46,6 +46,10 @@ static int usage(const char *arg0) {
         "  -mwindows                    (windows only) --subsystem windows to the linker\n"
         "  -mconsole                    (windows only) --subsystem console to the linker\n"
         "  -municode                    (windows only) link with unicode\n"
+        "  -mlinker-version [ver]       (darwin only) override linker version\n"
+        "  -rdynamic                    add all symbols to the dynamic symbol table\n"
+        "  -mmacosx-version-min [ver]   (darwin only) set Mac OS X deployment target\n"
+        "  -mios-version-min [ver]      (darwin only) set iOS deployment target\n"
     , arg0);
     return EXIT_FAILURE;
 }
@@ -119,6 +123,10 @@ int main(int argc, char **argv) {
     bool mwindows = false;
     bool mconsole = false;
     bool municode = false;
+    const char *mlinker_version = nullptr;
+    bool rdynamic = false;
+    const char *mmacosx_version_min = nullptr;
+    const char *mios_version_min = nullptr;
 
     for (int i = 1; i < argc; i += 1) {
         char *arg = argv[i];
@@ -138,6 +146,8 @@ int main(int argc, char **argv) {
                 mconsole = true;
             } else if (strcmp(arg, "-municode") == 0) {
                 municode = true;
+            } else if (strcmp(arg, "-rdynamic") == 0) {
+                rdynamic = true;
             } else if (i + 1 >= argc) {
                 return usage(arg0);
             } else {
@@ -192,6 +202,12 @@ int main(int argc, char **argv) {
                     target_os = argv[i];
                 } else if (strcmp(arg, "--target-environ") == 0) {
                     target_environ = argv[i];
+                } else if (strcmp(arg, "-mlinker-version") == 0) {
+                    mlinker_version = argv[i];
+                } else if (strcmp(arg, "-mmacosx-version-min") == 0) {
+                    mmacosx_version_min = argv[i];
+                } else if (strcmp(arg, "-mios-version-min") == 0) {
+                    mios_version_min = argv[i];
                 } else {
                     fprintf(stderr, "Invalid argument: %s\n", arg);
                     return usage(arg0);
@@ -327,6 +343,20 @@ int main(int argc, char **argv) {
 
             codegen_set_windows_subsystem(g, mwindows, mconsole);
             codegen_set_windows_unicode(g, municode);
+            codegen_set_rdynamic(g, rdynamic);
+            if (mlinker_version) {
+                codegen_set_mlinker_version(g, buf_create_from_str(mlinker_version));
+            }
+            if (mmacosx_version_min && mios_version_min) {
+                fprintf(stderr, "-mmacosx-version-min and -mios-version-min options not allowed together\n");
+                return EXIT_FAILURE;
+            }
+            if (mmacosx_version_min) {
+                codegen_set_mmacosx_version_min(g, buf_create_from_str(mmacosx_version_min));
+            }
+            if (mios_version_min) {
+                codegen_set_mios_version_min(g, buf_create_from_str(mios_version_min));
+            }
 
             if (cmd == CmdBuild) {
                 codegen_add_root_code(g, &root_source_dir, &root_source_name, &root_source_code);
CMakeLists.txt
@@ -144,6 +144,27 @@ set(ZIG_STD_SRC
     "${CMAKE_SOURCE_DIR}/std/math.zig"
 )
 
+
+set(ZIG_HOST_LINK_VERSION)
+if (APPLE)
+    set(LD_V_OUTPUT)
+    execute_process(
+        COMMAND sh -c "${CMAKE_LINKER} -v 2>&1 | head -1"
+        RESULT_VARIABLE HAD_ERROR
+        OUTPUT_VARIABLE LD_V_OUTPUT
+    )
+    if (NOT HAD_ERROR)
+        if ("${LD_V_OUTPUT}" MATCHES ".*ld64-([0-9.]+).*")
+            string(REGEX REPLACE ".*ld64-([0-9.]+).*" "\\1" ZIG_HOST_LINK_VERSION ${LD_V_OUTPUT})
+        elseif ("${LD_V_OUTPUT}" MATCHES "[^0-9]*([0-9.]+).*")
+            string(REGEX REPLACE "[^0-9]*([0-9.]+).*" "\\1" ZIG_HOST_LINK_VERSION ${LD_V_OUTPUT})
+        endif()
+    else()
+        message(FATAL_ERROR "${CMAKE_LINKER} failed with status ${HAD_ERROR}")
+    endif()
+endif()
+
+
 set(C_HEADERS_DEST "lib/zig/include")
 set(ZIG_STD_DEST "lib/zig/std")
 set(CONFIGURE_OUT_FILE "${CMAKE_BINARY_DIR}/config.h")