From d65cd73a8bfe39349be09a5f76f8477f3e3210b9 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 5 Apr 2017 07:46:50 -0400 Subject: [PATCH] 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 --- src/all_types.hpp | 1 + src/codegen.cpp | 16 +++++ src/codegen.hpp | 1 + src/link.cpp | 36 +++------- src/main.cpp | 86 +++++++++++++++++------- src/os.hpp | 16 +++++ src/target.cpp | 9 +++ src/target.hpp | 2 + test/run_tests.cpp | 162 +++++++++++++++++++++++++++++++++------------ 9 files changed, 237 insertions(+), 92 deletions(-) diff --git a/src/all_types.hpp b/src/all_types.hpp index d3d1a907d..f375ec051 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -1462,6 +1462,7 @@ struct CodeGen { ConstExprValue panic_msg_vals[PanicMsgIdCount]; Buf global_asm; + ZigList link_objects; }; enum VarLinkage { diff --git a/src/codegen.cpp b/src/codegen.cpp index 28a8243ac..50eba6e2c 100644 --- a/src/codegen.cpp +++ b/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", diff --git a/src/codegen.hpp b/src/codegen.hpp index bf65a19dd..e40fee9b1 100644 --- a/src/codegen.hpp +++ b/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); diff --git a/src/link.cpp b/src/link.cpp index da6c1891a..d26c87e65 100644 --- a/src/link.cpp +++ b/src/link.cpp @@ -16,7 +16,6 @@ struct LinkJob { Buf out_file; ZigList args; bool link_in_crt; - Buf out_file_o; HashMap 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) { diff --git a/src/main.cpp b/src/main.cpp index afe05b5d6..e9ca5feb6 100644 --- a/src/main.cpp +++ b/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 rpath_list = {0}; bool each_lib_rpath = false; + ZigList 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, "-")) { + + 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, ""); + + } 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); - 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 (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); - } + 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); diff --git a/src/os.hpp b/src/os.hpp index c84720fc3..339f1c8ae 100644 --- a/src/os.hpp +++ b/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 diff --git a/src/target.cpp b/src/target.cpp index cca869e47..cef9d6437 100644 --- a/src/target.cpp +++ b/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"; + } +} + diff --git a/src/target.hpp b/src/target.hpp index d56b3b02c..eee0ecbf6 100644 --- a/src/target.hpp +++ b/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 diff --git a/test/run_tests.cpp b/test/run_tests.cpp index 921125865..2ea6d6e9f 100644 --- a/test/run_tests.cpp +++ b/test/run_tests.cpp @@ -18,6 +18,7 @@ enum TestSpecial { TestSpecialNone, TestSpecialSelfHosted, TestSpecialStd, + TestSpecialLinkStep, }; struct TestSourceFile { @@ -36,6 +37,7 @@ struct TestCase { ZigList source_files; ZigList compile_errors; ZigList compiler_args; + ZigList linker_args; ZigList 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(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(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; + TestCase *test_case = allocate(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("build_exe"); - test_case->compiler_args.append(tmp_source_path); + 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("--name"); + test_case->compiler_args.append("test"); - test_case->compiler_args.append("--output"); - test_case->compiler_args.append(tmp_exe_path); + test_case->compiler_args.append("--output"); + test_case->compiler_args.append(tmp_exe_path); - test_cases.append(test_case); - } - { - TestCase *test_case = allocate(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); - - 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_case->compiler_args.append("--release"); - - 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(); }