Commit d65cd73a8b

Andrew Kelley <superjoe30@gmail.com>
2017-04-05 13:46:50
add support to use zig as a linker driver
closes #243 I also added --grep to ./run_tests if you want to single out some specific tests
1 parent 8c10b6d
src/all_types.hpp
@@ -1462,6 +1462,7 @@ struct CodeGen {
     ConstExprValue panic_msg_vals[PanicMsgIdCount];
 
     Buf global_asm;
+    ZigList<Buf *> link_objects;
 };
 
 enum VarLinkage {
src/codegen.cpp
@@ -3743,6 +3743,18 @@ static void do_code_gen(CodeGen *g) {
     char *error = nullptr;
     LLVMVerifyModule(g->module, LLVMAbortProcessAction, &error);
 #endif
+
+    char *err_msg = nullptr;
+    Buf *out_file_o = buf_create_from_buf(g->root_out_name);
+    const char *o_ext = target_o_file_ext(&g->zig_target);
+    buf_append_str(out_file_o, o_ext);
+    if (LLVMTargetMachineEmitToFile(g->target_machine, g->module, buf_ptr(out_file_o),
+                LLVMObjectFile, &err_msg))
+    {
+        zig_panic("unable to write object file: %s", err_msg);
+    }
+
+    g->link_objects.append(out_file_o);
 }
 
 static const size_t int_sizes_in_bits[] = {
@@ -4550,6 +4562,10 @@ void codegen_add_root_assembly(CodeGen *g, Buf *src_dir, Buf *src_basename, Buf
     do_code_gen(g);
 }
 
+void codegen_add_object(CodeGen *g, Buf *object_path) {
+    g->link_objects.append(object_path);
+}
+
 
 static const char *c_int_type_names[] = {
     [CIntTypeShort] = "short",
src/codegen.hpp
@@ -48,6 +48,7 @@ void codegen_set_omit_zigrt(CodeGen *g, bool omit_zigrt);
 PackageTableEntry *new_package(const char *root_src_dir, const char *root_src_path);
 void codegen_add_root_code(CodeGen *g, Buf *source_dir, Buf *source_basename, Buf *source_code);
 void codegen_add_root_assembly(CodeGen *g, Buf *source_dir, Buf *source_basename, Buf *source_code);
+void codegen_add_object(CodeGen *g, Buf *object_path);
 
 void codegen_parseh(CodeGen *g, Buf *src_dirname, Buf *src_basename, Buf *source_code);
 void codegen_render_ast(CodeGen *g, FILE *f, int indent_size);
src/link.cpp
@@ -16,7 +16,6 @@ struct LinkJob {
     Buf out_file;
     ZigList<const char *> args;
     bool link_in_crt;
-    Buf out_file_o;
     HashMap<Buf *, bool, buf_hash, buf_eql_buf> rpath_table;
 };
 
@@ -32,14 +31,6 @@ static const char *get_libc_static_file(CodeGen *g, const char *file) {
     return buf_ptr(out_buf);
 }
 
-static const char *get_o_file_extension(CodeGen *g) {
-    if (g->zig_target.env_type == ZigLLVM_MSVC) {
-        return ".obj";
-    } else {
-        return ".o";
-    }
-}
-
 static Buf *build_o(CodeGen *parent_gen, const char *oname) {
     Buf *source_basename = buf_sprintf("%s.zig", oname);
 
@@ -78,7 +69,7 @@ static Buf *build_o(CodeGen *parent_gen, const char *oname) {
     }
 
     codegen_add_root_code(child_gen, parent_gen->zig_std_special_dir, source_basename, &source_code);
-    const char *o_ext = get_o_file_extension(child_gen);
+    const char *o_ext = target_o_file_ext(&child_gen->zig_target);
     Buf *o_out = buf_sprintf("%s%s", oname, o_ext);
     codegen_link(child_gen, buf_ptr(o_out));
 
@@ -274,7 +265,9 @@ static void construct_linker_job_elf(LinkJob *lj) {
     }
 
     // .o files
-    lj->args.append((const char *)buf_ptr(&lj->out_file_o));
+    for (size_t i = 0; i < g->link_objects.length; i += 1) {
+        lj->args.append((const char *)buf_ptr(g->link_objects.at(i)));
+    }
 
     if (g->is_test_build) {
         Buf *test_runner_o_path = build_o(g, "test_runner");
@@ -417,7 +410,9 @@ static void construct_linker_job_coff(LinkJob *lj) {
         lj->args.append(buf_ptr(g->libc_static_lib_dir));
     }
 
-    lj->args.append((const char *)buf_ptr(&lj->out_file_o));
+    for (size_t i = 0; i < g->link_objects.length; i += 1) {
+        lj->args.append((const char *)buf_ptr(g->link_objects.at(i)));
+    }
 
     if (g->is_test_build) {
         Buf *test_runner_o_path = build_o(g, "test_runner");
@@ -681,7 +676,9 @@ static void construct_linker_job_macho(LinkJob *lj) {
         lj->args.append(lib_dir);
     }
 
-    lj->args.append((const char *)buf_ptr(&lj->out_file_o));
+    for (size_t i = 0; i < g->link_objects.length; i += 1) {
+        lj->args.append((const char *)buf_ptr(g->link_objects.at(i)));
+    }
 
     if (g->is_test_build) {
         Buf *test_runner_o_path = build_o(g, "test_runner");
@@ -776,19 +773,6 @@ void codegen_link(CodeGen *g, const char *out_file) {
             buf_append_str(&lj.out_file, get_exe_file_extension(g));
         }
     }
-    buf_init_from_buf(&lj.out_file_o, &lj.out_file);
-
-    if (g->out_type != OutTypeObj || !override_out_file) {
-        const char *o_ext = get_o_file_extension(g);
-        buf_append_str(&lj.out_file_o, o_ext);
-    }
-
-    char *err_msg = nullptr;
-    if (LLVMTargetMachineEmitToFile(g->target_machine, g->module, buf_ptr(&lj.out_file_o),
-                LLVMObjectFile, &err_msg))
-    {
-        zig_panic("unable to write object file: %s", err_msg);
-    }
 
     if (g->out_type == OutTypeObj) {
         if (g->want_h_file) {
src/main.cpp
@@ -24,6 +24,8 @@ static int usage(const char *arg0) {
         "  build_exe [source]           create executable from source\n"
         "  build_lib [source]           create library from source\n"
         "  build_obj [source]           create object from source\n"
+        "  link_exe [objects]           create executable from objects\n"
+        "  link_lib [objects]           create library from objects\n"
         "  parseh [source]              convert a c header file to zig extern declarations\n"
         "  targets                      list available compilation targets\n"
         "  test [source]                create and run a test build\n"
@@ -108,6 +110,7 @@ enum Cmd {
     CmdParseH,
     CmdTargets,
     CmdAsm,
+    CmdLink,
 };
 
 int main(int argc, char **argv) {
@@ -147,6 +150,7 @@ int main(int argc, char **argv) {
     const char *linker_script = nullptr;
     ZigList<const char *> rpath_list = {0};
     bool each_lib_rpath = false;
+    ZigList<const char *> objects = {0};
 
     if (argc >= 2 && strcmp(argv[1], "build") == 0) {
         const char *zig_exe_path = arg0;
@@ -304,6 +308,12 @@ int main(int argc, char **argv) {
             } else if (strcmp(arg, "build_lib") == 0) {
                 cmd = CmdBuild;
                 out_type = OutTypeLib;
+            } else if (strcmp(arg, "link_lib") == 0) {
+                cmd = CmdLink;
+                out_type = OutTypeLib;
+            } else if (strcmp(arg, "link_exe") == 0) {
+                cmd = CmdLink;
+                out_type = OutTypeExe;
             } else if (strcmp(arg, "version") == 0) {
                 cmd = CmdVersion;
             } else if (strcmp(arg, "parseh") == 0) {
@@ -330,6 +340,9 @@ int main(int argc, char **argv) {
                         return usage(arg0);
                     }
                     break;
+                case CmdLink:
+                    objects.append(arg);
+                    break;
                 case CmdVersion:
                 case CmdTargets:
                     return usage(arg0);
@@ -344,11 +357,24 @@ int main(int argc, char **argv) {
     case CmdParseH:
     case CmdTest:
     case CmdAsm:
+    case CmdLink:
         {
-            if (!in_file)
-                return usage(arg0);
+            bool one_source_input = (cmd == CmdBuild || cmd == CmdParseH || cmd == CmdTest || cmd == CmdAsm);
+            if (one_source_input) {
+                if (!in_file) {
+                    fprintf(stderr, "Expected source file argument.\n");
+                    return usage(arg0);
+                }
+            } else if (cmd == CmdLink) {
+                if (objects.length == 0) {
+                    fprintf(stderr, "Expected one or more object arguments.\n");
+                    return usage(arg0);
+                }
+            } else {
+                zig_unreachable();
+            }
 
-            assert(cmd != CmdBuild || out_type != OutTypeUnknown);
+            assert((cmd != CmdBuild && cmd != CmdLink) || out_type != OutTypeUnknown);
 
             init_all_targets();
 
@@ -379,10 +405,9 @@ int main(int argc, char **argv) {
                 }
             }
 
-            bool need_name = (cmd == CmdBuild || cmd == CmdAsm);
+            bool need_name = (cmd == CmdBuild || cmd == CmdAsm || cmd == CmdLink);
 
             Buf in_file_buf = BUF_INIT;
-            buf_init_from_str(&in_file_buf, in_file);
 
             Buf root_source_dir = BUF_INIT;
             Buf root_source_code = BUF_INIT;
@@ -390,26 +415,35 @@ int main(int argc, char **argv) {
 
             Buf *buf_out_name = (cmd == CmdTest) ? buf_create_from_str("test") :
                 (out_name == nullptr) ? nullptr : buf_create_from_str(out_name);
-            if (buf_eql_str(&in_file_buf, "-")) {
-                os_get_cwd(&root_source_dir);
-                if ((err = os_fetch_file(stdin, &root_source_code))) {
-                    fprintf(stderr, "unable to read stdin: %s\n", err_str(err));
-                    return 1;
-                }
-                buf_init_from_str(&root_source_name, "");
 
-            } else {
-                os_path_split(&in_file_buf, &root_source_dir, &root_source_name);
-                if ((err = os_fetch_file_path(buf_create_from_str(in_file), &root_source_code))) {
-                    fprintf(stderr, "unable to open '%s': %s\n", in_file, err_str(err));
-                    return 1;
-                }
+            if (one_source_input) {
+                buf_init_from_str(&in_file_buf, in_file);
+
+                if (buf_eql_str(&in_file_buf, "-")) {
+                    os_get_cwd(&root_source_dir);
+                    if ((err = os_fetch_file(stdin, &root_source_code))) {
+                        fprintf(stderr, "unable to read stdin: %s\n", err_str(err));
+                        return 1;
+                    }
+                    buf_init_from_str(&root_source_name, "");
 
-                if (need_name && buf_out_name == nullptr) {
-                    buf_out_name = buf_alloc();
-                    Buf ext_name = BUF_INIT;
-                    os_path_extname(&root_source_name, buf_out_name, &ext_name);
+                } else {
+                    os_path_split(&in_file_buf, &root_source_dir, &root_source_name);
+                    if ((err = os_fetch_file_path(buf_create_from_str(in_file), &root_source_code))) {
+                        fprintf(stderr, "unable to open '%s': %s\n", in_file, err_str(err));
+                        return 1;
+                    }
+
+                    if (need_name && buf_out_name == nullptr) {
+                        buf_out_name = buf_alloc();
+                        Buf ext_name = BUF_INIT;
+                        os_path_extname(&root_source_name, buf_out_name, &ext_name);
+                    }
                 }
+            } else if (cmd == CmdLink) {
+                os_get_cwd(&root_source_dir);
+            } else {
+                zig_unreachable();
             }
 
             if (need_name && buf_out_name == nullptr) {
@@ -484,6 +518,12 @@ int main(int argc, char **argv) {
                 codegen_add_root_code(g, &root_source_dir, &root_source_name, &root_source_code);
                 codegen_link(g, out_file);
                 return EXIT_SUCCESS;
+            } else if (cmd == CmdLink) {
+                for (size_t i = 0; i < objects.length; i += 1) {
+                    codegen_add_object(g, buf_create_from_str(objects.at(i)));
+                }
+                codegen_link(g, out_file);
+                return EXIT_SUCCESS;
             } else if (cmd == CmdAsm) {
                 codegen_add_root_assembly(g, &root_source_dir, &root_source_name, &root_source_code);
                 codegen_link(g, out_file);
src/os.hpp
@@ -54,4 +54,20 @@ int os_delete_file(Buf *path);
 
 int os_file_exists(Buf *full_path, bool *result);
 
+#if defined(__APPLE__)
+#define ZIG_OS_DARWIN
+#elif defined(_WIN32)
+#define ZIG_OS_WINDOWS
+#elif defined(__linux__)
+#define ZIG_OS_LINUX
+#else
+#define ZIG_OS_UNKNOWN
+#endif
+
+#if defined(__x86_64__)
+#define ZIG_ARCH_X86_64
+#else
+#define ZIG_ARCH_UNKNOWN
+#endif
+
 #endif
src/target.cpp
@@ -527,3 +527,12 @@ int get_c_type_size_in_bits(const ZigTarget *target, CIntType id) {
     }
     zig_unreachable();
 }
+
+const char *target_o_file_ext(ZigTarget *target) {
+    if (target->env_type == ZigLLVM_MSVC) {
+        return ".obj";
+    } else {
+        return ".o";
+    }
+}
+
src/target.hpp
@@ -72,4 +72,6 @@ void resolve_target_object_format(ZigTarget *target);
 
 int get_c_type_size_in_bits(const ZigTarget *target, CIntType id);
 
+const char *target_o_file_ext(ZigTarget *target);
+
 #endif
test/run_tests.cpp
@@ -18,6 +18,7 @@ enum TestSpecial {
     TestSpecialNone,
     TestSpecialSelfHosted,
     TestSpecialStd,
+    TestSpecialLinkStep,
 };
 
 struct TestSourceFile {
@@ -36,6 +37,7 @@ struct TestCase {
     ZigList<TestSourceFile> source_files;
     ZigList<const char *> compile_errors;
     ZigList<const char *> compiler_args;
+    ZigList<const char *> linker_args;
     ZigList<const char *> program_args;
     bool is_parseh;
     TestSpecial special;
@@ -89,6 +91,37 @@ static TestCase *add_simple_case(const char *case_name, const char *source, cons
     return test_case;
 }
 
+static TestCase *add_asm_case(const char *case_name, const char *source, const char *output) {
+    TestCase *test_case = allocate<TestCase>(1);
+    test_case->case_name = case_name;
+    test_case->output = output;
+    test_case->special = TestSpecialLinkStep;
+
+    test_case->source_files.resize(1);
+    test_case->source_files.at(0).relative_path = ".tmp_source.s";
+    test_case->source_files.at(0).source_code = source;
+
+    test_case->compiler_args.append("asm");
+    test_case->compiler_args.append(".tmp_source.s");
+    test_case->compiler_args.append("--name");
+    test_case->compiler_args.append("test");
+    test_case->compiler_args.append("--color");
+    test_case->compiler_args.append("on");
+
+    test_case->linker_args.append("link_exe");
+    test_case->linker_args.append("test.o");
+    test_case->linker_args.append("--name");
+    test_case->linker_args.append("test");
+    test_case->linker_args.append("--output");
+    test_case->linker_args.append(tmp_exe_path);
+    test_case->linker_args.append("--color");
+    test_case->linker_args.append("on");
+
+    test_cases.append(test_case);
+
+    return test_case;
+}
+
 static TestCase *add_simple_case_libc(const char *case_name, const char *source, const char *output) {
     TestCase *tc = add_simple_case(case_name, source, output);
     tc->compiler_args.append("--library");
@@ -129,46 +162,23 @@ static TestCase *add_compile_fail_case(const char *case_name, const char *source
 }
 
 static void add_debug_safety_case(const char *case_name, const char *source) {
-    {
-        TestCase *test_case = allocate<TestCase>(1);
-        test_case->is_debug_safety = true;
-        test_case->case_name = buf_ptr(buf_sprintf("%s (debug)", case_name));
-        test_case->source_files.resize(1);
-        test_case->source_files.at(0).relative_path = tmp_source_path;
-        test_case->source_files.at(0).source_code = source;
-
-        test_case->compiler_args.append("build_exe");
-        test_case->compiler_args.append(tmp_source_path);
-
-        test_case->compiler_args.append("--name");
-        test_case->compiler_args.append("test");
-
-        test_case->compiler_args.append("--output");
-        test_case->compiler_args.append(tmp_exe_path);
-
-        test_cases.append(test_case);
-    }
-    {
-        TestCase *test_case = allocate<TestCase>(1);
-        test_case->case_name = buf_ptr(buf_sprintf("%s (release)", case_name));
-        test_case->source_files.resize(1);
-        test_case->source_files.at(0).relative_path = tmp_source_path;
-        test_case->source_files.at(0).source_code = source;
-        test_case->output = "";
-
-        test_case->compiler_args.append("build_exe");
-        test_case->compiler_args.append(tmp_source_path);
+    TestCase *test_case = allocate<TestCase>(1);
+    test_case->is_debug_safety = true;
+    test_case->case_name = buf_ptr(buf_sprintf("%s", case_name));
+    test_case->source_files.resize(1);
+    test_case->source_files.at(0).relative_path = tmp_source_path;
+    test_case->source_files.at(0).source_code = source;
 
-        test_case->compiler_args.append("--name");
-        test_case->compiler_args.append("test");
+    test_case->compiler_args.append("build_exe");
+    test_case->compiler_args.append(tmp_source_path);
 
-        test_case->compiler_args.append("--output");
-        test_case->compiler_args.append(tmp_exe_path);
+    test_case->compiler_args.append("--name");
+    test_case->compiler_args.append("test");
 
-        test_case->compiler_args.append("--release");
+    test_case->compiler_args.append("--output");
+    test_case->compiler_args.append(tmp_exe_path);
 
-        test_cases.append(test_case);
-    }
+    test_cases.append(test_case);
 }
 
 static TestCase *add_parseh_case(const char *case_name, AllowWarnings allow_warnings,
@@ -1865,9 +1875,7 @@ pub fn panic(message: []const u8) -> noreturn {
     while (true) {}
 }
 pub fn main() -> %void {
-    if (!@compileVar("is_release")) {
-        @panic("oh no");
-    }
+    @panic("oh no");
 }
     )SOURCE");
 
@@ -2375,6 +2383,32 @@ static void add_std_lib_tests(void) {
     }
 }
 
+static void add_asm_tests(void) {
+#if defined(ZIG_OS_LINUX) && defined(ZIG_ARCH_X86_64)
+    add_asm_case("assemble and link hello world linux x86_64", R"SOURCE(
+.text
+.globl _start
+
+_start:
+    mov rax, 1
+    mov rdi, 1
+    lea rsi, msg
+    mov rdx, 14
+    syscall
+
+    mov rax, 60
+    mov rdi, 0
+    syscall
+
+.data
+
+msg:
+    .ascii "Hello, world!\n"
+    )SOURCE", "Hello, world!\n");
+
+#endif
+}
+
 
 static void print_compiler_invocation(TestCase *test_case) {
     printf("%s", zig_exe);
@@ -2384,6 +2418,15 @@ static void print_compiler_invocation(TestCase *test_case) {
     printf("\n");
 }
 
+static void print_linker_invocation(TestCase *test_case) {
+    printf("%s", zig_exe);
+    for (size_t i = 0; i < test_case->linker_args.length; i += 1) {
+        printf(" %s", test_case->linker_args.at(i));
+    }
+    printf("\n");
+}
+
+
 static void print_exe_invocation(TestCase *test_case) {
     printf("%s", tmp_exe_path);
     for (size_t i = 0; i < test_case->program_args.length; i += 1) {
@@ -2470,6 +2513,23 @@ static void run_test(TestCase *test_case) {
             }
         }
     } else {
+        if (test_case->special == TestSpecialLinkStep) {
+            Buf link_stderr = BUF_INIT;
+            Buf link_stdout = BUF_INIT;
+            int err;
+            Termination term;
+            if ((err = os_exec_process(zig_exe, test_case->linker_args, &term, &link_stderr, &link_stdout))) {
+                fprintf(stderr, "Unable to exec %s: %s\n", zig_exe, err_str(err));
+            }
+
+            if (term.how != TerminationIdClean || term.code != 0) {
+                printf("\nLink failed:\n");
+                print_linker_invocation(test_case);
+                printf("%s\n", buf_ptr(&zig_stderr));
+                exit(1);
+            }
+        }
+
         Buf program_stderr = BUF_INIT;
         Buf program_stdout = BUF_INIT;
         os_exec_process(tmp_exe_path, test_case->program_args, &term, &program_stderr, &program_stdout);
@@ -2520,9 +2580,13 @@ static void run_test(TestCase *test_case) {
     }
 }
 
-static void run_all_tests(void) {
+static void run_all_tests(const char *grep_text) {
     for (size_t i = 0; i < test_cases.length; i += 1) {
         TestCase *test_case = test_cases.at(i);
+        if (grep_text != nullptr && strstr(test_case->case_name, grep_text) == nullptr) {
+            continue;
+        }
+
         printf("Test %zu/%zu %s...", i + 1, test_cases.length, test_case->case_name);
         fflush(stdout);
         run_test(test_case);
@@ -2538,13 +2602,24 @@ static void cleanup(void) {
 }
 
 static int usage(const char *arg0) {
-    fprintf(stderr, "Usage: %s\n", arg0);
+    fprintf(stderr, "Usage: %s [--grep text]\n", arg0);
     return 1;
 }
 
 int main(int argc, char **argv) {
+    const char *grep_text = nullptr;
     for (int i = 1; i < argc; i += 1) {
-        return usage(argv[0]);
+        const char *arg = argv[i];
+        if (i + 1 >= argc) {
+            return usage(argv[0]);
+        } else {
+            i += 1;
+            if (strcmp(arg, "--grep") == 0) {
+                grep_text = argv[i];
+            } else {
+                return usage(argv[0]);
+            }
+        }
     }
     add_compiling_test_cases();
     add_debug_safety_test_cases();
@@ -2552,6 +2627,7 @@ int main(int argc, char **argv) {
     add_parseh_test_cases();
     add_self_hosted_tests();
     add_std_lib_tests();
-    run_all_tests();
+    add_asm_tests();
+    run_all_tests(grep_text);
     cleanup();
 }