Commit ef7fa76001

Andrew Kelley <andrew@ziglang.org>
2021-09-24 09:54:44
stage2: enable building freestanding libc with LLVM backend
* LLVM backend: respect `sub_path` just like the other stage2 backends do. * Compilation has some new logic to only emit work queue jobs for building stuff when it believes itself to be capable. The linker backends no longer have duplicate logic; instead they respect the optional bit on the respective asset.
1 parent f215d98
src/codegen/llvm.zig
@@ -164,15 +164,17 @@ pub const Object = struct {
     /// * it works for functions not all globals.
     /// Therefore, this table keeps track of the mapping.
     decl_map: std.AutoHashMapUnmanaged(*const Module.Decl, *const llvm.Value),
+    /// Where to put the output object file, relative to bin_file.options.emit directory.
+    sub_path: []const u8,
 
-    pub fn create(gpa: *Allocator, options: link.Options) !*Object {
+    pub fn create(gpa: *Allocator, sub_path: []const u8, options: link.Options) !*Object {
         const obj = try gpa.create(Object);
         errdefer gpa.destroy(obj);
-        obj.* = try Object.init(gpa, options);
+        obj.* = try Object.init(gpa, sub_path, options);
         return obj;
     }
 
-    pub fn init(gpa: *Allocator, options: link.Options) !Object {
+    pub fn init(gpa: *Allocator, sub_path: []const u8, options: link.Options) !Object {
         const context = llvm.Context.create();
         errdefer context.dispose();
 
@@ -251,6 +253,7 @@ pub const Object = struct {
             .context = context,
             .target_machine = target_machine,
             .decl_map = .{},
+            .sub_path = sub_path,
         };
     }
 
@@ -301,23 +304,18 @@ pub const Object = struct {
         const mod = comp.bin_file.options.module.?;
         const cache_dir = mod.zig_cache_artifact_directory;
 
-        const emit_bin_path: ?[*:0]const u8 = if (comp.bin_file.options.emit != null) blk: {
-            const obj_basename = try std.zig.binNameAlloc(arena, .{
-                .root_name = comp.bin_file.options.root_name,
-                .target = comp.bin_file.options.target,
-                .output_mode = .Obj,
-            });
-            if (cache_dir.joinZ(arena, &[_][]const u8{obj_basename})) |p| {
-                break :blk p.ptr;
-            } else |err| {
-                return err;
-            }
-        } else null;
+        const emit_bin_path: ?[*:0]const u8 = if (comp.bin_file.options.emit) |emit|
+            try emit.directory.joinZ(arena, &[_][]const u8{self.sub_path})
+        else
+            null;
 
         const emit_asm_path = try locPath(arena, comp.emit_asm, cache_dir);
         const emit_llvm_ir_path = try locPath(arena, comp.emit_llvm_ir, cache_dir);
         const emit_llvm_bc_path = try locPath(arena, comp.emit_llvm_bc, cache_dir);
 
+        const debug_emit_path = emit_bin_path orelse "(none)";
+        log.debug("emit LLVM object to {s}", .{debug_emit_path});
+
         var error_message: [*:0]const u8 = undefined;
         if (self.target_machine.emitToFile(
             self.llvm_module,
src/link/Coff.zig
@@ -132,7 +132,7 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio
         const self = try createEmpty(allocator, options);
         errdefer self.base.destroy();
 
-        self.llvm_object = try LlvmObject.create(allocator, options);
+        self.llvm_object = try LlvmObject.create(allocator, sub_path, options);
         return self;
     }
 
@@ -884,11 +884,8 @@ fn linkWithLLD(self: *Coff, comp: *Compilation) !void {
     // If there is no Zig code to compile, then we should skip flushing the output file because it
     // will not be part of the linker line anyway.
     const module_obj_path: ?[]const u8 = if (self.base.options.module) |module| blk: {
-        // Both stage1 and stage2 LLVM backend put the object file in the cache directory.
-        if (self.base.options.use_llvm) {
-            // Stage2 has to call flushModule since that outputs the LLVM object file.
-            if (!build_options.is_stage1 or !self.base.options.use_stage1) try self.flushModule(comp);
-
+        const use_stage1 = build_options.is_stage1 and self.base.options.use_stage1;
+        if (use_stage1) {
             const obj_basename = try std.zig.binNameAlloc(arena, .{
                 .root_name = self.base.options.root_name,
                 .target = self.base.options.target,
@@ -1269,22 +1266,23 @@ fn linkWithLLD(self: *Coff, comp: *Compilation) !void {
             try argv.append(comp.libunwind_static_lib.?.full_object_path);
         }
 
-        // TODO: remove when stage2 can build compiler_rt.zig, c.zig and ssp.zig
-        // compiler-rt, libc and libssp
-        if (is_exe_or_dyn_lib and
-            !self.base.options.skip_linker_dependencies and
-            build_options.is_stage1 and self.base.options.use_stage1)
-        {
+        if (is_exe_or_dyn_lib and !self.base.options.skip_linker_dependencies) {
             if (!self.base.options.link_libc) {
-                try argv.append(comp.libc_static_lib.?.full_object_path);
+                if (comp.libc_static_lib) |lib| {
+                    try argv.append(lib.full_object_path);
+                }
             }
             // MinGW doesn't provide libssp symbols
             if (target.abi.isGnu()) {
-                try argv.append(comp.libssp_static_lib.?.full_object_path);
+                if (comp.libssp_static_lib) |lib| {
+                    try argv.append(lib.full_object_path);
+                }
             }
             // MSVC compiler_rt is missing some stuff, so we build it unconditionally but
             // and rely on weak linkage to allow MSVC compiler_rt functions to override ours.
-            try argv.append(comp.compiler_rt_static_lib.?.full_object_path);
+            if (comp.compiler_rt_static_lib) |lib| {
+                try argv.append(lib.full_object_path);
+            }
         }
 
         try argv.ensureUnusedCapacity(self.base.options.system_libs.count());
src/link/Elf.zig
@@ -235,7 +235,7 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio
         const self = try createEmpty(allocator, options);
         errdefer self.base.destroy();
 
-        self.llvm_object = try LlvmObject.create(allocator, options);
+        self.llvm_object = try LlvmObject.create(allocator, sub_path, options);
         return self;
     }
 
@@ -1254,11 +1254,8 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void {
     // If there is no Zig code to compile, then we should skip flushing the output file because it
     // will not be part of the linker line anyway.
     const module_obj_path: ?[]const u8 = if (self.base.options.module) |module| blk: {
-        // Both stage1 and stage2 LLVM backend put the object file in the cache directory.
-        if (self.base.options.use_llvm) {
-            // Stage2 has to call flushModule since that outputs the LLVM object file.
-            if (!build_options.is_stage1 or !self.base.options.use_stage1) try self.flushModule(comp);
-
+        // stage1 puts the object file in the cache directory.
+        if (self.base.options.use_stage1) {
             const obj_basename = try std.zig.binNameAlloc(arena, .{
                 .root_name = self.base.options.root_name,
                 .target = self.base.options.target,
@@ -1621,14 +1618,13 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void {
         }
 
         // libc
-        // TODO: enable when stage2 can build c.zig
         if (is_exe_or_dyn_lib and
             !self.base.options.skip_linker_dependencies and
-            !self.base.options.link_libc and
-            build_options.is_stage1 and
-            self.base.options.use_stage1)
+            !self.base.options.link_libc)
         {
-            try argv.append(comp.libc_static_lib.?.full_object_path);
+            if (comp.libc_static_lib) |lib| {
+                try argv.append(lib.full_object_path);
+            }
         }
 
         // compiler-rt
src/link/MachO.zig
@@ -290,7 +290,7 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio
         const self = try createEmpty(allocator, options);
         errdefer self.base.destroy();
 
-        self.llvm_object = try LlvmObject.create(allocator, options);
+        self.llvm_object = try LlvmObject.create(allocator, sub_path, options);
         return self;
     }
 
src/link/Wasm.zig
@@ -121,7 +121,7 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio
         const self = try createEmpty(allocator, options);
         errdefer self.base.destroy();
 
-        self.llvm_object = try LlvmObject.create(allocator, options);
+        self.llvm_object = try LlvmObject.create(allocator, sub_path, options);
         return self;
     }
 
src/Compilation.zig
@@ -1574,25 +1574,29 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
         // 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 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 = {} });
-                }
+        const capable_of_building_compiler_rt = comp.bin_file.options.use_stage1;
+        const capable_of_building_ssp = comp.bin_file.options.use_stage1;
+        const capable_of_building_zig_libc = comp.bin_file.options.use_stage1 or
+            comp.bin_file.options.use_llvm;
+
+        if (comp.bin_file.options.include_compiler_rt and capable_of_building_compiler_rt) {
+            if (is_exe_or_dyn_lib) {
+                try comp.work_queue.writeItem(.{ .compiler_rt_lib = {} });
+            } 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 (needs_c_symbols) {
-                // MinGW provides no libssp, use our own implementation.
-                if (comp.getTarget().isMinGW()) {
-                    try comp.work_queue.writeItem(.{ .libssp = {} });
-                }
-                if (!comp.bin_file.options.link_libc) {
-                    try comp.work_queue.writeItem(.{ .zig_libc = {} });
-                }
+        }
+        if (needs_c_symbols) {
+            // MinGW provides no libssp, use our own implementation.
+            if (comp.getTarget().isMinGW() and capable_of_building_ssp) {
+                try comp.work_queue.writeItem(.{ .libssp = {} });
+            }
+
+            if (!comp.bin_file.options.link_libc and capable_of_building_zig_libc) {
+                try comp.work_queue.writeItem(.{ .zig_libc = {} });
             }
         }
     }
src/link.zig
@@ -245,6 +245,9 @@ pub const File = struct {
         };
 
         if (use_lld) {
+            // TODO this intermediary_basename isn't enough; in the case of `zig build-exe`,
+            // we also want to put the intermediary object file in the cache while the
+            // main emit directory is the cwd.
             file.intermediary_basename = sub_path;
         }