Commit 7c25390c95

Andrew Kelley <andrew@ziglang.org>
2021-07-23 01:37:38
support -fcompiler-rt in conjunction with build-obj
When using `build-exe` or `build-lib -dynamic`, `-fcompiler-rt` means building compiler-rt into a static library and then linking it into the executable. When using `build-lib`, `-fcompiler-rt` means building compiler-rt into an object file and then adding it into the static archive. Before this commit, when using `build-obj`, zig would build compiler-rt into an object file, and then on ELF, use `lld -r` to merge it into the main object file. Other linker backends of LLD do not support `-r` to merge objects, so this failed with error messages for those targets. Now, `-fcompiler-rt` when used with `build-obj` acts as if the user puts `_ = @import("compiler_rt");` inside their root source file. The symbols of compiler-rt go into the same compilation unit as the root source file. This is hooked up for stage1 only for now. Once stage2 is capable of building compiler-rt, it should be hooked up there as well.
1 parent a5fb280
src/link/Elf.zig
@@ -1289,6 +1289,10 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void {
         // TODO: remove when stage2 can build compiler_rt.zig
         if (!build_options.is_stage1) break :blk null;
 
+        // In the case of build-obj we include the compiler-rt symbols directly alongside
+        // the symbols of the root source file, in the same compilation unit.
+        if (is_obj) break :blk null;
+
         if (is_exe_or_dyn_lib) {
             break :blk comp.compiler_rt_static_lib.?.full_object_path;
         } else {
src/link/Wasm.zig
@@ -645,7 +645,9 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation) !void {
         break :blk full_obj_path;
     } else null;
 
-    const compiler_rt_path: ?[]const u8 = if (self.base.options.include_compiler_rt)
+    const is_obj = self.base.options.output_mode == .Obj;
+
+    const compiler_rt_path: ?[]const u8 = if (self.base.options.include_compiler_rt and !is_obj)
         comp.compiler_rt_static_lib.?.full_object_path
     else
         null;
src/stage1/all_types.hpp
@@ -2150,6 +2150,7 @@ struct CodeGen {
     bool have_stack_probing;
     bool red_zone;
     bool function_sections;
+    bool include_compiler_rt;
     bool test_is_evented;
     bool valgrind_enabled;
     bool tsan_enabled;
src/stage1/codegen.cpp
@@ -9542,6 +9542,22 @@ static void gen_root_source(CodeGen *g) {
     g->panic_fn = panic_fn_val->data.x_ptr.data.fn.fn_entry;
     assert(g->panic_fn != nullptr);
 
+    if (g->include_compiler_rt) {
+        Buf *import_target_path;
+        Buf full_path = BUF_INIT;
+        ZigType *compiler_rt_import;
+        if ((err = analyze_import(g, std_import, buf_create_from_str("./special/compiler_rt.zig"),
+            &compiler_rt_import, &import_target_path, &full_path)))
+        {
+            if (err == ErrorFileNotFound) {
+                fprintf(stderr, "unable to find '%s'", buf_ptr(import_target_path));
+            } else {
+                fprintf(stderr, "unable to open '%s': %s\n", buf_ptr(&full_path), err_str(err));
+            }
+            exit(1);
+        }
+    }
+
     if (!g->error_during_imports) {
         semantic_analyze(g);
     }
src/stage1/stage1.cpp
@@ -101,6 +101,7 @@ void zig_stage1_build_object(struct ZigStage1 *stage1) {
     g->link_libc = stage1->link_libc;
     g->link_libcpp = stage1->link_libcpp;
     g->function_sections = stage1->function_sections;
+    g->include_compiler_rt = stage1->include_compiler_rt;
 
     g->subsystem = stage1->subsystem;
 
src/stage1/stage1.h
@@ -196,6 +196,7 @@ struct ZigStage1 {
     bool valgrind_enabled;
     bool tsan_enabled;
     bool function_sections;
+    bool include_compiler_rt;
     bool enable_stack_probing;
     bool red_zone;
     bool enable_time_report;
src/stage1/zig0.cpp
@@ -39,6 +39,7 @@ static int print_full_usage(const char *arg0, FILE *file, int return_code) {
         "  --color [auto|off|on]        enable or disable colored error messages\n"
         "  --name [name]                override output name\n"
         "  -femit-bin=[path]            Output machine code\n"
+        "  -fcompiler-rt                Always include compiler-rt symbols in output\n"
         "  --pkg-begin [name] [path]    make pkg available to import and push current pkg\n"
         "  --pkg-end                    pop current pkg\n"
         "  -ODebug                      build with optimizations off and safety on\n"
@@ -266,6 +267,7 @@ int main(int argc, char **argv) {
     const char *mcpu = nullptr;
     bool single_threaded = false;
     bool is_test_build = false;
+    bool include_compiler_rt = false;
 
     for (int i = 1; i < argc; i += 1) {
         char *arg = argv[i];
@@ -334,6 +336,8 @@ int main(int argc, char **argv) {
                 mcpu = arg + strlen("-mcpu=");
             } else if (str_starts_with(arg, "-femit-bin=")) {
                 emit_bin_path = arg + strlen("-femit-bin=");
+            } else if (strcmp(arg, "-fcompiler-rt") == 0) {
+                include_compiler_rt = true;
             } else if (i + 1 >= argc) {
                 fprintf(stderr, "Expected another argument after %s\n", arg);
                 return print_error_usage(arg0);
@@ -468,6 +472,7 @@ int main(int argc, char **argv) {
     stage1->subsystem = subsystem;
     stage1->pic = true;
     stage1->is_single_threaded = single_threaded;
+    stage1->include_compiler_rt = include_compiler_rt;
 
     zig_stage1_build_object(stage1);
 
src/Compilation.zig
@@ -826,6 +826,9 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
         const ofmt = options.object_format orelse options.target.getObjectFormat();
 
         const use_stage1 = options.use_stage1 orelse blk: {
+            // Even though we may have no Zig code to compile (depending on `options.root_pkg`),
+            // we may need to use stage1 for building compiler-rt and other dependencies.
+
             if (build_options.omit_stage2)
                 break :blk true;
             if (options.use_llvm) |use_llvm| {
@@ -833,9 +836,6 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
                     break :blk false;
                 }
             }
-            // If we have no zig code to compile, no need for stage1 backend.
-            if (options.root_pkg == null)
-                break :blk false;
 
             break :blk build_options.is_stage1;
         };
@@ -878,9 +878,6 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
             if (options.emit_llvm_ir != null or options.emit_llvm_bc != null) {
                 return error.EmittingLlvmModuleRequiresUsingLlvmBackend;
             }
-            if (use_stage1) {
-                return error.@"stage1 only supports LLVM backend";
-            }
         }
 
         const tsan = options.want_tsan orelse false;
@@ -1542,24 +1539,19 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
         }
 
         // The `use_stage1` condition is here only because stage2 cannot yet build compiler-rt.
-        // Once it is capable this condition should be removed.
+        // Once it is capable this condition should be removed. When removing this condition,
+        // also test the use case of `build-obj -fcompiler-rt` with the self-hosted compiler
+        // and make sure the compiler-rt symbols are emitted. Currently this is hooked up for
+        // stage1 but not stage2.
         if (comp.bin_file.options.use_stage1) {
             if (comp.bin_file.options.include_compiler_rt) {
                 if (is_exe_or_dyn_lib) {
                     try comp.work_queue.writeItem(.{ .compiler_rt_lib = {} });
-                } else {
+                } else if (options.output_mode != .Obj) {
+                    // If build-obj with -fcompiler-rt is requested, that is handled specially
+                    // elsewhere. In this case we are making a static library, so we ask
+                    // for a compiler-rt object to put in it.
                     try comp.work_queue.writeItem(.{ .compiler_rt_obj = {} });
-                    if (comp.bin_file.options.object_format != .elf and
-                        comp.bin_file.options.output_mode == .Obj)
-                    {
-                        // For ELF we can rely on using -r to link multiple objects together into one,
-                        // but to truly support `build-obj -fcompiler-rt` will require virtually
-                        // injecting `_ = @import("compiler_rt.zig")` into the root source file of
-                        // the compilation.
-                        fatal("Embedding compiler-rt into {s} objects is not yet implemented.", .{
-                            @tagName(comp.bin_file.options.object_format),
-                        });
-                    }
                 }
             }
             if (needs_c_symbols) {
@@ -4002,6 +3994,7 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node
     man.hash.add(target.os.getVersionRange());
     man.hash.add(comp.bin_file.options.dll_export_fns);
     man.hash.add(comp.bin_file.options.function_sections);
+    man.hash.add(comp.bin_file.options.include_compiler_rt);
     man.hash.add(comp.bin_file.options.is_test);
     man.hash.add(comp.bin_file.options.emit != null);
     man.hash.add(mod.emit_h != null);
@@ -4182,6 +4175,7 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node
         .valgrind_enabled = comp.bin_file.options.valgrind,
         .tsan_enabled = comp.bin_file.options.tsan,
         .function_sections = comp.bin_file.options.function_sections,
+        .include_compiler_rt = comp.bin_file.options.include_compiler_rt,
         .enable_stack_probing = comp.bin_file.options.stack_check,
         .red_zone = comp.bin_file.options.red_zone,
         .enable_time_report = comp.time_report,
src/main.zig
@@ -385,8 +385,8 @@ const usage_build_generic =
     \\  --dynamic-linker [path]        Set the dynamic interpreter path (usually ld.so)
     \\  --sysroot [path]               Set the system root directory (usually /)
     \\  --version [ver]                Dynamic library semver
-    \\  -fsoname[=name]                (Linux) Override the default SONAME value
-    \\  -fno-soname                    (Linux) Disable emitting a SONAME
+    \\  -fsoname[=name]                Override the default SONAME value
+    \\  -fno-soname                    Disable emitting a SONAME
     \\  -fLLD                          Force using LLD as the linker
     \\  -fno-LLD                       Prevent using LLD as the linker
     \\  -fcompiler-rt                  Always include compiler-rt symbols in output
src/stage1.zig
@@ -21,7 +21,6 @@ comptime {
     assert(build_options.is_stage1);
     assert(build_options.have_llvm);
     if (!builtin.is_test) {
-        _ = @import("compiler_rt");
         @export(main, .{ .name = "main" });
     }
 }
@@ -126,6 +125,7 @@ pub const Module = extern struct {
     valgrind_enabled: bool,
     tsan_enabled: bool,
     function_sections: bool,
+    include_compiler_rt: bool,
     enable_stack_probing: bool,
     red_zone: bool,
     enable_time_report: bool,
CMakeLists.txt
@@ -796,6 +796,7 @@ set(BUILD_ZIG1_ARGS
     --name zig1
     --zig-lib-dir "${CMAKE_SOURCE_DIR}/lib"
     "-femit-bin=${ZIG1_OBJECT}"
+    -fcompiler-rt
     "${ZIG1_RELEASE_ARG}"
     "${ZIG1_SINGLE_THREADED_ARG}"
     -lc