From 3647784d05ad6535875435c3268abe2316aa4f79 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 22 Jan 2021 23:34:53 -0700 Subject: [PATCH 1/3] stage2: add missing frexpl.c to mingw c source file list --- src/mingw.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mingw.zig b/src/mingw.zig index 0a7669f99516..ca887dd94036 100644 --- a/src/mingw.zig +++ b/src/mingw.zig @@ -707,6 +707,7 @@ const mingwex_generic_src = [_][]const u8{ "math" ++ path.sep_str ++ "fpclassifyf.c", "math" ++ path.sep_str ++ "fpclassifyl.c", "math" ++ path.sep_str ++ "frexpf.c", + "math" ++ path.sep_str ++ "frexpl.c", "math" ++ path.sep_str ++ "hypot.c", "math" ++ path.sep_str ++ "hypotf.c", "math" ++ path.sep_str ++ "hypotl.c", From ab4f3aee3da8f2658c019c05de2cce991d010f01 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 22 Jan 2021 23:35:32 -0700 Subject: [PATCH 2/3] stage2: wasm arch does not support -mred-zone flags --- src/target.zig | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/target.zig b/src/target.zig index c3df682ce0a2..e95149d7e067 100644 --- a/src/target.zig +++ b/src/target.zig @@ -354,8 +354,6 @@ pub fn hasRedZone(target: std.Target) bool { return switch (target.cpu.arch) { .x86_64, .i386, - .wasm32, - .wasm64, .powerpc, .powerpc64, .powerpc64le, From 0d4b6ac7417d1094ac972981b0241444ce2380ba Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 22 Jan 2021 23:36:30 -0700 Subject: [PATCH 3/3] add LTO support The CLI gains -flto and -fno-lto options to override the default. However, the cool thing about this is that the defaults are great! In general when you use build-exe in release mode, Zig will enable LTO if it would work and it would help. zig cc supports detecting and honoring the -flto and -fno-lto flags as well. The linkWithLld functions are improved to all be the same with regards to copying the artifact instead of trying to pass single objects through LLD with -r. There is possibly a future improvement here as well; see the respective TODOs. stage1 is updated to support outputting LLVM bitcode instead of machine code when lto is enabled. This allows LLVM to optimize across the Zig and C/C++ code boundary. closes #2845 --- src/Compilation.zig | 37 ++ src/clang_options_data.zig | 27 +- src/codegen/llvm/bindings.zig | 8 +- src/link.zig | 1 + src/link/Coff.zig | 7 + src/link/Elf.zig | 604 +++++++++++++++++---------------- src/link/MachO.zig | 7 + src/link/Wasm.zig | 237 +++++++------ src/main.zig | 12 + src/stage1.zig | 1 + src/stage1/all_types.hpp | 1 + src/stage1/codegen.cpp | 10 +- src/stage1/stage1.cpp | 1 + src/stage1/stage1.h | 1 + src/zig_llvm.cpp | 10 +- src/zig_llvm.h | 2 +- tools/update_clang_options.zig | 8 + 17 files changed, 576 insertions(+), 398 deletions(-) diff --git a/src/Compilation.zig b/src/Compilation.zig index fae725a1ac2b..b6c82c2dbb21 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -444,6 +444,7 @@ pub const InitOptions = struct { want_valgrind: ?bool = null, want_tsan: ?bool = null, want_compiler_rt: ?bool = null, + want_lto: ?bool = null, use_llvm: ?bool = null, use_lld: ?bool = null, use_clang: ?bool = null, @@ -602,6 +603,12 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { if (ofmt == .c) break :blk false; + if (options.want_lto) |lto| { + if (lto) { + break :blk true; + } + } + // Our linker can't handle objects or most advanced options yet. if (options.link_objects.len != 0 or options.c_source_files.len != 0 or @@ -647,6 +654,26 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { break :outer opts; } else .{}; + const lto = blk: { + if (options.want_lto) |explicit| { + if (!use_lld) + return error.LtoUnavailableWithoutLld; + break :blk explicit; + } else if (!use_lld) { + break :blk false; + } else if (options.c_source_files.len == 0) { + break :blk false; + } else if (darwin_options.system_linker_hack) { + break :blk false; + } else switch (options.output_mode) { + .Lib, .Obj => break :blk false, + .Exe => switch (options.optimize_mode) { + .Debug => break :blk false, + .ReleaseSafe, .ReleaseFast, .ReleaseSmall => break :blk true, + }, + } + }; + const tsan = options.want_tsan orelse false; const link_libc = options.link_libc or target_util.osRequiresLibC(options.target) or tsan; @@ -821,6 +848,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { cache.hash.add(ofmt); cache.hash.add(pic); cache.hash.add(pie); + cache.hash.add(lto); cache.hash.add(tsan); cache.hash.add(stack_check); cache.hash.add(red_zone); @@ -1022,6 +1050,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { .libc_installation = libc_dirs.libc_installation, .pic = pic, .pie = pie, + .lto = lto, .valgrind = valgrind, .tsan = tsan, .stack_check = stack_check, @@ -2233,6 +2262,9 @@ pub fn addCCArgs( "-nostdinc", "-fno-spell-checking", }); + if (comp.bin_file.options.lto) { + try argv.append("-flto"); + } // According to Rich Felker libc headers are supposed to go before C language headers. // However as noted by @dimenus, appending libc headers before c_headers breaks intrinsics @@ -3255,6 +3287,7 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node .err_color = @enumToInt(comp.color), .pic = comp.bin_file.options.pic, .pie = comp.bin_file.options.pie, + .lto = comp.bin_file.options.lto, .link_libc = comp.bin_file.options.link_libc, .link_libcpp = comp.bin_file.options.link_libcpp, .strip = comp.bin_file.options.strip, @@ -3415,6 +3448,10 @@ pub fn build_crt_file( .want_tsan = false, .want_pic = comp.bin_file.options.pic, .want_pie = comp.bin_file.options.pie, + .want_lto = switch (output_mode) { + .Lib => comp.bin_file.options.lto, + .Obj, .Exe => false, + }, .emit_h = null, .strip = comp.compilerRtStrip(), .is_native_os = comp.bin_file.options.is_native_os, diff --git a/src/clang_options_data.zig b/src/clang_options_data.zig index c901c6045fa4..4d89308545dc 100644 --- a/src/clang_options_data.zig +++ b/src/clang_options_data.zig @@ -2732,7 +2732,14 @@ flagpd1("fkeep-static-consts"), flagpd1("flat_namespace"), flagpd1("flax-vector-conversions"), flagpd1("flimit-debug-info"), -flagpd1("flto"), +.{ + .name = "flto", + .syntax = .flag, + .zig_equivalent = .lto, + .pd1 = true, + .pd2 = false, + .psl = false, +}, flagpd1("flto-unit"), flagpd1("flto-visibility-public-std"), sepd1("fmacro-backtrace-limit"), @@ -2942,7 +2949,14 @@ flagpd1("fno-jump-tables"), flagpd1("fno-keep-static-consts"), flagpd1("fno-lax-vector-conversions"), flagpd1("fno-limit-debug-info"), -flagpd1("fno-lto"), +.{ + .name = "fno-lto", + .syntax = .flag, + .zig_equivalent = .no_lto, + .pd1 = true, + .pd2 = false, + .psl = false, +}, flagpd1("fno-lto-unit"), flagpd1("fno-math-builtin"), flagpd1("fno-math-errno"), @@ -5638,7 +5652,14 @@ jspd1("Ttext"), .pd2 = true, .psl = false, }, -joinpd1("flto="), +.{ + .name = "flto=", + .syntax = .joined, + .zig_equivalent = .lto, + .pd1 = true, + .pd2 = false, + .psl = false, +}, joinpd1("gcoff"), joinpd1("mabi="), joinpd1("mabs="), diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig index 8774260a0819..6474957b1ce7 100644 --- a/src/codegen/llvm/bindings.zig +++ b/src/codegen/llvm/bindings.zig @@ -243,7 +243,13 @@ pub const TargetMachine = opaque { extern fn LLVMDisposeTargetMachine(T: *const TargetMachine) void; pub const emitToFile = LLVMTargetMachineEmitToFile; - extern fn LLVMTargetMachineEmitToFile(*const TargetMachine, M: *const Module, Filename: [*:0]const u8, codegen: CodeGenFileType, ErrorMessage: *[*:0]const u8) LLVMBool; + extern fn LLVMTargetMachineEmitToFile( + *const TargetMachine, + M: *const Module, + Filename: [*:0]const u8, + codegen: CodeGenFileType, + ErrorMessage: *[*:0]const u8, + ) LLVMBool; }; pub const CodeMode = extern enum { diff --git a/src/link.zig b/src/link.zig index 6914131beaee..16222b5a79b6 100644 --- a/src/link.zig +++ b/src/link.zig @@ -74,6 +74,7 @@ pub const Options = struct { is_native_abi: bool, pic: bool, pie: bool, + lto: bool, valgrind: bool, tsan: bool, stack_check: bool, diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 981d4ec3a340..1acf09a1dce0 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -945,6 +945,13 @@ fn linkWithLLD(self: *Coff, comp: *Compilation) !void { if (!self.base.options.strip) { try argv.append("-DEBUG"); } + if (self.base.options.lto) { + switch (self.base.options.optimize_mode) { + .Debug => {}, + .ReleaseSmall => try argv.append("-OPT:lldlto=2"), + .ReleaseFast, .ReleaseSafe => try argv.append("-OPT:lldlto=3"), + } + } if (self.base.options.output_mode == .Exe) { const stack_size = self.base.options.stack_size_override orelse 16777216; try argv.append(try allocPrint(arena, "-STACK:{d}", .{stack_size})); diff --git a/src/link/Elf.zig b/src/link/Elf.zig index fffe5628fef3..18f3f5771251 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -1384,351 +1384,385 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void { }; } - // Create an LLD command line and invoke it. - var argv = std.ArrayList([]const u8).init(self.base.allocator); - defer argv.deinit(); - // We will invoke ourselves as a child process to gain access to LLD. - // This is necessary because LLD does not behave properly as a library - - // it calls exit() and does not reset all global data between invocations. - try argv.appendSlice(&[_][]const u8{ comp.self_exe_path.?, "ld.lld" }); - if (is_obj) { - try argv.append("-r"); - } + const full_out_path = try directory.join(arena, &[_][]const u8{self.base.options.emit.?.sub_path}); + if (self.base.options.output_mode == .Obj and self.base.options.lto) { + // In this case we must do a simple file copy + // here. TODO: think carefully about how we can avoid this redundant operation when doing + // build-obj. See also the corresponding TODO in linkAsArchive. + const the_object_path = blk: { + if (self.base.options.objects.len != 0) + break :blk self.base.options.objects[0]; + + if (comp.c_object_table.count() != 0) + break :blk comp.c_object_table.items()[0].key.status.success.object_path; + + if (module_obj_path) |p| + break :blk p; + + // TODO I think this is unreachable. Audit this situation when solving the above TODO + // regarding eliding redundant object -> object transformations. + return error.NoObjectsToLink; + }; + // This can happen when using --enable-cache and using the stage1 backend. In this case + // we can skip the file copy. + if (!mem.eql(u8, the_object_path, full_out_path)) { + try fs.cwd().copyFile(the_object_path, fs.cwd(), full_out_path, .{}); + } + } else { - try argv.append("-error-limit=0"); + // Create an LLD command line and invoke it. + var argv = std.ArrayList([]const u8).init(self.base.allocator); + defer argv.deinit(); + // We will invoke ourselves as a child process to gain access to LLD. + // This is necessary because LLD does not behave properly as a library - + // it calls exit() and does not reset all global data between invocations. + try argv.appendSlice(&[_][]const u8{ comp.self_exe_path.?, "ld.lld" }); + if (is_obj) { + try argv.append("-r"); + } - if (self.base.options.output_mode == .Exe) { - try argv.append("-z"); - try argv.append(try std.fmt.allocPrint(arena, "stack-size={d}", .{stack_size})); - } + try argv.append("-error-limit=0"); - if (self.base.options.image_base_override) |image_base| { - try argv.append(try std.fmt.allocPrint(arena, "--image-base={d}", .{image_base})); - } + if (self.base.options.lto) { + switch (self.base.options.optimize_mode) { + .Debug => {}, + .ReleaseSmall => try argv.append("-O2"), + .ReleaseFast, .ReleaseSafe => try argv.append("-O3"), + } + } - if (self.base.options.linker_script) |linker_script| { - try argv.append("-T"); - try argv.append(linker_script); - } + if (self.base.options.output_mode == .Exe) { + try argv.append("-z"); + try argv.append(try std.fmt.allocPrint(arena, "stack-size={d}", .{stack_size})); + } - if (gc_sections) { - try argv.append("--gc-sections"); - } + if (self.base.options.image_base_override) |image_base| { + try argv.append(try std.fmt.allocPrint(arena, "--image-base={d}", .{image_base})); + } - if (self.base.options.eh_frame_hdr) { - try argv.append("--eh-frame-hdr"); - } + if (self.base.options.linker_script) |linker_script| { + try argv.append("-T"); + try argv.append(linker_script); + } - if (self.base.options.emit_relocs) { - try argv.append("--emit-relocs"); - } + if (gc_sections) { + try argv.append("--gc-sections"); + } - if (self.base.options.rdynamic) { - try argv.append("--export-dynamic"); - } + if (self.base.options.eh_frame_hdr) { + try argv.append("--eh-frame-hdr"); + } - try argv.appendSlice(self.base.options.extra_lld_args); + if (self.base.options.emit_relocs) { + try argv.append("--emit-relocs"); + } - if (self.base.options.z_nodelete) { - try argv.append("-z"); - try argv.append("nodelete"); - } - if (self.base.options.z_defs) { - try argv.append("-z"); - try argv.append("defs"); - } + if (self.base.options.rdynamic) { + try argv.append("--export-dynamic"); + } - if (getLDMOption(target)) |ldm| { - // Any target ELF will use the freebsd osabi if suffixed with "_fbsd". - const arg = if (target.os.tag == .freebsd) - try std.fmt.allocPrint(arena, "{s}_fbsd", .{ldm}) - else - ldm; - try argv.append("-m"); - try argv.append(arg); - } + try argv.appendSlice(self.base.options.extra_lld_args); - if (self.base.options.link_mode == .Static) { - if (target.cpu.arch.isARM() or target.cpu.arch.isThumb()) { - try argv.append("-Bstatic"); - } else { - try argv.append("-static"); + if (self.base.options.z_nodelete) { + try argv.append("-z"); + try argv.append("nodelete"); + } + if (self.base.options.z_defs) { + try argv.append("-z"); + try argv.append("defs"); } - } else if (is_dyn_lib) { - try argv.append("-shared"); - } - if (self.base.options.pie and self.base.options.output_mode == .Exe) { - try argv.append("-pie"); - } + if (getLDMOption(target)) |ldm| { + // Any target ELF will use the freebsd osabi if suffixed with "_fbsd". + const arg = if (target.os.tag == .freebsd) + try std.fmt.allocPrint(arena, "{s}_fbsd", .{ldm}) + else + ldm; + try argv.append("-m"); + try argv.append(arg); + } - const full_out_path = try directory.join(arena, &[_][]const u8{self.base.options.emit.?.sub_path}); - try argv.append("-o"); - try argv.append(full_out_path); + if (self.base.options.link_mode == .Static) { + if (target.cpu.arch.isARM() or target.cpu.arch.isThumb()) { + try argv.append("-Bstatic"); + } else { + try argv.append("-static"); + } + } else if (is_dyn_lib) { + try argv.append("-shared"); + } - if (link_in_crt) { - const crt1o: []const u8 = o: { - if (target.os.tag == .netbsd) { - break :o "crt0.o"; - } else if (target.os.tag == .openbsd) { - if (self.base.options.link_mode == .Static) { - break :o "rcrt0.o"; - } else { + if (self.base.options.pie and self.base.options.output_mode == .Exe) { + try argv.append("-pie"); + } + + try argv.append("-o"); + try argv.append(full_out_path); + + if (link_in_crt) { + const crt1o: []const u8 = o: { + if (target.os.tag == .netbsd) { break :o "crt0.o"; - } - } else if (target.isAndroid()) { - if (self.base.options.link_mode == .Dynamic) { - break :o "crtbegin_dynamic.o"; - } else { - break :o "crtbegin_static.o"; - } - } else if (self.base.options.link_mode == .Static) { - if (self.base.options.pie) { - break :o "rcrt1.o"; + } else if (target.os.tag == .openbsd) { + if (self.base.options.link_mode == .Static) { + break :o "rcrt0.o"; + } else { + break :o "crt0.o"; + } + } else if (target.isAndroid()) { + if (self.base.options.link_mode == .Dynamic) { + break :o "crtbegin_dynamic.o"; + } else { + break :o "crtbegin_static.o"; + } + } else if (self.base.options.link_mode == .Static) { + if (self.base.options.pie) { + break :o "rcrt1.o"; + } else { + break :o "crt1.o"; + } } else { - break :o "crt1.o"; + break :o "Scrt1.o"; } - } else { - break :o "Scrt1.o"; + }; + try argv.append(try comp.get_libc_crt_file(arena, crt1o)); + if (target_util.libc_needs_crti_crtn(target)) { + try argv.append(try comp.get_libc_crt_file(arena, "crti.o")); + } + if (target.os.tag == .openbsd) { + try argv.append(try comp.get_libc_crt_file(arena, "crtbegin.o")); } - }; - try argv.append(try comp.get_libc_crt_file(arena, crt1o)); - if (target_util.libc_needs_crti_crtn(target)) { - try argv.append(try comp.get_libc_crt_file(arena, "crti.o")); - } - if (target.os.tag == .openbsd) { - try argv.append(try comp.get_libc_crt_file(arena, "crtbegin.o")); } - } - // rpaths - var rpath_table = std.StringHashMap(void).init(self.base.allocator); - defer rpath_table.deinit(); - for (self.base.options.rpath_list) |rpath| { - if ((try rpath_table.fetchPut(rpath, {})) == null) { - try argv.append("-rpath"); - try argv.append(rpath); + // rpaths + var rpath_table = std.StringHashMap(void).init(self.base.allocator); + defer rpath_table.deinit(); + for (self.base.options.rpath_list) |rpath| { + if ((try rpath_table.fetchPut(rpath, {})) == null) { + try argv.append("-rpath"); + try argv.append(rpath); + } } - } - if (self.base.options.each_lib_rpath) { - var test_path = std.ArrayList(u8).init(self.base.allocator); - defer test_path.deinit(); - for (self.base.options.lib_dirs) |lib_dir_path| { - for (self.base.options.system_libs.items()) |entry| { - const link_lib = entry.key; - test_path.shrinkRetainingCapacity(0); - const sep = fs.path.sep_str; - try test_path.writer().print("{s}" ++ sep ++ "lib{s}.so", .{ lib_dir_path, link_lib }); - fs.cwd().access(test_path.items, .{}) catch |err| switch (err) { - error.FileNotFound => continue, - else => |e| return e, - }; - if ((try rpath_table.fetchPut(lib_dir_path, {})) == null) { - try argv.append("-rpath"); - try argv.append(lib_dir_path); + if (self.base.options.each_lib_rpath) { + var test_path = std.ArrayList(u8).init(self.base.allocator); + defer test_path.deinit(); + for (self.base.options.lib_dirs) |lib_dir_path| { + for (self.base.options.system_libs.items()) |entry| { + const link_lib = entry.key; + test_path.shrinkRetainingCapacity(0); + const sep = fs.path.sep_str; + try test_path.writer().print("{s}" ++ sep ++ "lib{s}.so", .{ lib_dir_path, link_lib }); + fs.cwd().access(test_path.items, .{}) catch |err| switch (err) { + error.FileNotFound => continue, + else => |e| return e, + }; + if ((try rpath_table.fetchPut(lib_dir_path, {})) == null) { + try argv.append("-rpath"); + try argv.append(lib_dir_path); + } } } } - } - for (self.base.options.lib_dirs) |lib_dir| { - try argv.append("-L"); - try argv.append(lib_dir); - } - - if (self.base.options.link_libc) { - if (self.base.options.libc_installation) |libc_installation| { + for (self.base.options.lib_dirs) |lib_dir| { try argv.append("-L"); - try argv.append(libc_installation.crt_dir.?); + try argv.append(lib_dir); } - if (have_dynamic_linker) { - if (self.base.options.dynamic_linker) |dynamic_linker| { - try argv.append("-dynamic-linker"); - try argv.append(dynamic_linker); + if (self.base.options.link_libc) { + if (self.base.options.libc_installation) |libc_installation| { + try argv.append("-L"); + try argv.append(libc_installation.crt_dir.?); } - } - } - if (is_dyn_lib) { - if (self.base.options.soname) |soname| { - try argv.append("-soname"); - try argv.append(soname); - } - if (self.base.options.version_script) |version_script| { - try argv.append("-version-script"); - try argv.append(version_script); + if (have_dynamic_linker) { + if (self.base.options.dynamic_linker) |dynamic_linker| { + try argv.append("-dynamic-linker"); + try argv.append(dynamic_linker); + } + } } - } - - // Positional arguments to the linker such as object files. - try argv.appendSlice(self.base.options.objects); - for (comp.c_object_table.items()) |entry| { - try argv.append(entry.key.status.success.object_path); - } + if (is_dyn_lib) { + if (self.base.options.soname) |soname| { + try argv.append("-soname"); + try argv.append(soname); + } + if (self.base.options.version_script) |version_script| { + try argv.append("-version-script"); + try argv.append(version_script); + } + } - if (module_obj_path) |p| { - try argv.append(p); - } + // Positional arguments to the linker such as object files. + try argv.appendSlice(self.base.options.objects); - // TSAN - if (self.base.options.tsan) { - try argv.append(comp.tsan_static_lib.?.full_object_path); - } + for (comp.c_object_table.items()) |entry| { + try argv.append(entry.key.status.success.object_path); + } - // 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) - { - try argv.append(comp.libc_static_lib.?.full_object_path); - } + if (module_obj_path) |p| { + try argv.append(p); + } - // compiler-rt - if (compiler_rt_path) |p| { - try argv.append(p); - } + // TSAN + if (self.base.options.tsan) { + try argv.append(comp.tsan_static_lib.?.full_object_path); + } - // Shared libraries. - if (is_exe_or_dyn_lib) { - const system_libs = self.base.options.system_libs.items(); - try argv.ensureCapacity(argv.items.len + system_libs.len); - for (system_libs) |entry| { - const link_lib = entry.key; - // By this time, we depend on these libs being dynamically linked libraries and not static libraries - // (the check for that needs to be earlier), but they could be full paths to .so files, in which - // case we want to avoid prepending "-l". - const ext = Compilation.classifyFileExt(link_lib); - const arg = if (ext == .shared_library) link_lib else try std.fmt.allocPrint(arena, "-l{s}", .{link_lib}); - argv.appendAssumeCapacity(arg); + // 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) + { + try argv.append(comp.libc_static_lib.?.full_object_path); } - // libc++ dep - if (self.base.options.link_libcpp) { - try argv.append(comp.libcxxabi_static_lib.?.full_object_path); - try argv.append(comp.libcxx_static_lib.?.full_object_path); + // compiler-rt + if (compiler_rt_path) |p| { + try argv.append(p); } - // libc dep - if (self.base.options.link_libc) { - if (self.base.options.libc_installation != null) { - if (self.base.options.link_mode == .Static) { - try argv.append("--start-group"); - try argv.append("-lc"); - try argv.append("-lm"); - try argv.append("--end-group"); - } else { - try argv.append("-lc"); - try argv.append("-lm"); - } + // Shared libraries. + if (is_exe_or_dyn_lib) { + const system_libs = self.base.options.system_libs.items(); + try argv.ensureCapacity(argv.items.len + system_libs.len); + for (system_libs) |entry| { + const link_lib = entry.key; + // By this time, we depend on these libs being dynamically linked libraries and not static libraries + // (the check for that needs to be earlier), but they could be full paths to .so files, in which + // case we want to avoid prepending "-l". + const ext = Compilation.classifyFileExt(link_lib); + const arg = if (ext == .shared_library) link_lib else try std.fmt.allocPrint(arena, "-l{s}", .{link_lib}); + argv.appendAssumeCapacity(arg); + } - if (target.os.tag == .freebsd or target.os.tag == .netbsd or target.os.tag == .openbsd) { - try argv.append("-lpthread"); - } - } else if (target.isGnuLibC()) { - try argv.append(comp.libunwind_static_lib.?.full_object_path); - for (glibc.libs) |lib| { - const lib_path = try std.fmt.allocPrint(arena, "{s}{c}lib{s}.so.{d}", .{ - comp.glibc_so_files.?.dir_path, fs.path.sep, lib.name, lib.sover, - }); - try argv.append(lib_path); + // libc++ dep + if (self.base.options.link_libcpp) { + try argv.append(comp.libcxxabi_static_lib.?.full_object_path); + try argv.append(comp.libcxx_static_lib.?.full_object_path); + } + + // libc dep + if (self.base.options.link_libc) { + if (self.base.options.libc_installation != null) { + if (self.base.options.link_mode == .Static) { + try argv.append("--start-group"); + try argv.append("-lc"); + try argv.append("-lm"); + try argv.append("--end-group"); + } else { + try argv.append("-lc"); + try argv.append("-lm"); + } + + if (target.os.tag == .freebsd or target.os.tag == .netbsd or target.os.tag == .openbsd) { + try argv.append("-lpthread"); + } + } else if (target.isGnuLibC()) { + try argv.append(comp.libunwind_static_lib.?.full_object_path); + for (glibc.libs) |lib| { + const lib_path = try std.fmt.allocPrint(arena, "{s}{c}lib{s}.so.{d}", .{ + comp.glibc_so_files.?.dir_path, fs.path.sep, lib.name, lib.sover, + }); + try argv.append(lib_path); + } + try argv.append(try comp.get_libc_crt_file(arena, "libc_nonshared.a")); + } else if (target.isMusl()) { + try argv.append(comp.libunwind_static_lib.?.full_object_path); + try argv.append(try comp.get_libc_crt_file(arena, switch (self.base.options.link_mode) { + .Static => "libc.a", + .Dynamic => "libc.so", + })); + } else if (self.base.options.link_libcpp) { + try argv.append(comp.libunwind_static_lib.?.full_object_path); + } else { + unreachable; // Compiler was supposed to emit an error for not being able to provide libc. } - try argv.append(try comp.get_libc_crt_file(arena, "libc_nonshared.a")); - } else if (target.isMusl()) { - try argv.append(comp.libunwind_static_lib.?.full_object_path); - try argv.append(try comp.get_libc_crt_file(arena, switch (self.base.options.link_mode) { - .Static => "libc.a", - .Dynamic => "libc.so", - })); - } else if (self.base.options.link_libcpp) { - try argv.append(comp.libunwind_static_lib.?.full_object_path); - } else { - unreachable; // Compiler was supposed to emit an error for not being able to provide libc. } } - } - // crt end - if (link_in_crt) { - if (target.isAndroid()) { - try argv.append(try comp.get_libc_crt_file(arena, "crtend_android.o")); - } else if (target.os.tag == .openbsd) { - try argv.append(try comp.get_libc_crt_file(arena, "crtend.o")); - } else if (target_util.libc_needs_crti_crtn(target)) { - try argv.append(try comp.get_libc_crt_file(arena, "crtn.o")); + // crt end + if (link_in_crt) { + if (target.isAndroid()) { + try argv.append(try comp.get_libc_crt_file(arena, "crtend_android.o")); + } else if (target.os.tag == .openbsd) { + try argv.append(try comp.get_libc_crt_file(arena, "crtend.o")); + } else if (target_util.libc_needs_crti_crtn(target)) { + try argv.append(try comp.get_libc_crt_file(arena, "crtn.o")); + } } - } - if (allow_shlib_undefined) { - try argv.append("--allow-shlib-undefined"); - } + if (allow_shlib_undefined) { + try argv.append("--allow-shlib-undefined"); + } - if (self.base.options.bind_global_refs_locally) { - try argv.append("-Bsymbolic"); - } + if (self.base.options.bind_global_refs_locally) { + try argv.append("-Bsymbolic"); + } - if (self.base.options.verbose_link) { - // Skip over our own name so that the LLD linker name is the first argv item. - Compilation.dump_argv(argv.items[1..]); - } + if (self.base.options.verbose_link) { + // Skip over our own name so that the LLD linker name is the first argv item. + Compilation.dump_argv(argv.items[1..]); + } - // Sadly, we must run LLD as a child process because it does not behave - // properly as a library. - const child = try std.ChildProcess.init(argv.items, arena); - defer child.deinit(); + // Sadly, we must run LLD as a child process because it does not behave + // properly as a library. + const child = try std.ChildProcess.init(argv.items, arena); + defer child.deinit(); - if (comp.clang_passthrough_mode) { - child.stdin_behavior = .Inherit; - child.stdout_behavior = .Inherit; - child.stderr_behavior = .Inherit; + if (comp.clang_passthrough_mode) { + child.stdin_behavior = .Inherit; + child.stdout_behavior = .Inherit; + child.stderr_behavior = .Inherit; - const term = child.spawnAndWait() catch |err| { - log.err("unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) }); - return error.UnableToSpawnSelf; - }; - switch (term) { - .Exited => |code| { - if (code != 0) { - // TODO https://github.com/ziglang/zig/issues/6342 - std.process.exit(1); - } - }, - else => std.process.abort(), - } - } else { - child.stdin_behavior = .Ignore; - child.stdout_behavior = .Ignore; - child.stderr_behavior = .Pipe; + const term = child.spawnAndWait() catch |err| { + log.err("unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) }); + return error.UnableToSpawnSelf; + }; + switch (term) { + .Exited => |code| { + if (code != 0) { + // TODO https://github.com/ziglang/zig/issues/6342 + std.process.exit(1); + } + }, + else => std.process.abort(), + } + } else { + child.stdin_behavior = .Ignore; + child.stdout_behavior = .Ignore; + child.stderr_behavior = .Pipe; - try child.spawn(); + try child.spawn(); - const stderr = try child.stderr.?.reader().readAllAlloc(arena, 10 * 1024 * 1024); + const stderr = try child.stderr.?.reader().readAllAlloc(arena, 10 * 1024 * 1024); - const term = child.wait() catch |err| { - log.err("unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) }); - return error.UnableToSpawnSelf; - }; + const term = child.wait() catch |err| { + log.err("unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) }); + return error.UnableToSpawnSelf; + }; - switch (term) { - .Exited => |code| { - if (code != 0) { - // TODO parse this output and surface with the Compilation API rather than - // directly outputting to stderr here. - std.debug.print("{s}", .{stderr}); - return error.LLDReportedFailure; - } - }, - else => { - log.err("{s} terminated with stderr:\n{s}", .{ argv.items[0], stderr }); - return error.LLDCrashed; - }, - } + switch (term) { + .Exited => |code| { + if (code != 0) { + // TODO parse this output and surface with the Compilation API rather than + // directly outputting to stderr here. + std.debug.print("{s}", .{stderr}); + return error.LLDReportedFailure; + } + }, + else => { + log.err("{s} terminated with stderr:\n{s}", .{ argv.items[0], stderr }); + return error.LLDCrashed; + }, + } - if (stderr.len != 0) { - log.warn("unexpected LLD stderr:\n{s}", .{stderr}); + if (stderr.len != 0) { + log.warn("unexpected LLD stderr:\n{s}", .{stderr}); + } } } diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 6c2059fcdc15..eee5841903a0 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -620,6 +620,13 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void { try argv.append("0"); } + if (self.base.options.lto) { + switch (self.base.options.optimize_mode) { + .Debug => {}, + .ReleaseSmall => try argv.append("-O2"), + .ReleaseFast, .ReleaseSafe => try argv.append("-O3"), + } + } try argv.append("-demangle"); if (self.base.options.rdynamic and !self.base.options.system_linker_hack) { diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index 4640c9f1afa7..fb356ad1aaad 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -362,122 +362,157 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation) !void { }; } - const is_obj = self.base.options.output_mode == .Obj; - - // Create an LLD command line and invoke it. - var argv = std.ArrayList([]const u8).init(self.base.allocator); - defer argv.deinit(); - // We will invoke ourselves as a child process to gain access to LLD. - // This is necessary because LLD does not behave properly as a library - - // it calls exit() and does not reset all global data between invocations. - try argv.appendSlice(&[_][]const u8{ comp.self_exe_path.?, "wasm-ld" }); - if (is_obj) { - try argv.append("-r"); - } - - try argv.append("-error-limit=0"); + const full_out_path = try directory.join(arena, &[_][]const u8{self.base.options.emit.?.sub_path}); - if (self.base.options.output_mode == .Exe) { - // Increase the default stack size to a more reasonable value of 1MB instead of - // the default of 1 Wasm page being 64KB, unless overriden by the user. - try argv.append("-z"); - const stack_size = self.base.options.stack_size_override orelse 1048576; - const arg = try std.fmt.allocPrint(arena, "stack-size={d}", .{stack_size}); - try argv.append(arg); - - // Put stack before globals so that stack overflow results in segfault immediately - // before corrupting globals. See https://github.com/ziglang/zig/issues/4496 - try argv.append("--stack-first"); - } else { - try argv.append("--no-entry"); // So lld doesn't look for _start. - try argv.append("--export-all"); - } - try argv.appendSlice(&[_][]const u8{ - "--allow-undefined", - "-o", - try directory.join(arena, &[_][]const u8{self.base.options.emit.?.sub_path}), - }); + if (self.base.options.output_mode == .Obj) { + // LLD's WASM driver does not support the equvialent of `-r` so we do a simple file copy + // here. TODO: think carefully about how we can avoid this redundant operation when doing + // build-obj. See also the corresponding TODO in linkAsArchive. + const the_object_path = blk: { + if (self.base.options.objects.len != 0) + break :blk self.base.options.objects[0]; - // Positional arguments to the linker such as object files. - try argv.appendSlice(self.base.options.objects); + if (comp.c_object_table.count() != 0) + break :blk comp.c_object_table.items()[0].key.status.success.object_path; - for (comp.c_object_table.items()) |entry| { - try argv.append(entry.key.status.success.object_path); - } - if (module_obj_path) |p| { - try argv.append(p); - } + if (module_obj_path) |p| + break :blk p; - if (self.base.options.output_mode != .Obj and - !self.base.options.skip_linker_dependencies and - !self.base.options.link_libc) - { - try argv.append(comp.libc_static_lib.?.full_object_path); - } + // TODO I think this is unreachable. Audit this situation when solving the above TODO + // regarding eliding redundant object -> object transformations. + return error.NoObjectsToLink; + }; + // This can happen when using --enable-cache and using the stage1 backend. In this case + // we can skip the file copy. + if (!mem.eql(u8, the_object_path, full_out_path)) { + try fs.cwd().copyFile(the_object_path, fs.cwd(), full_out_path, .{}); + } + } else { + const is_obj = self.base.options.output_mode == .Obj; + + // Create an LLD command line and invoke it. + var argv = std.ArrayList([]const u8).init(self.base.allocator); + defer argv.deinit(); + // We will invoke ourselves as a child process to gain access to LLD. + // This is necessary because LLD does not behave properly as a library - + // it calls exit() and does not reset all global data between invocations. + try argv.appendSlice(&[_][]const u8{ comp.self_exe_path.?, "wasm-ld" }); + if (is_obj) { + try argv.append("-r"); + } - if (compiler_rt_path) |p| { - try argv.append(p); - } + try argv.append("-error-limit=0"); - if (self.base.options.verbose_link) { - // Skip over our own name so that the LLD linker name is the first argv item. - Compilation.dump_argv(argv.items[1..]); - } + if (self.base.options.lto) { + switch (self.base.options.optimize_mode) { + .Debug => {}, + .ReleaseSmall => try argv.append("-O2"), + .ReleaseFast, .ReleaseSafe => try argv.append("-O3"), + } + } - // Sadly, we must run LLD as a child process because it does not behave - // properly as a library. - const child = try std.ChildProcess.init(argv.items, arena); - defer child.deinit(); + if (self.base.options.output_mode == .Exe) { + // Increase the default stack size to a more reasonable value of 1MB instead of + // the default of 1 Wasm page being 64KB, unless overriden by the user. + try argv.append("-z"); + const stack_size = self.base.options.stack_size_override orelse 1048576; + const arg = try std.fmt.allocPrint(arena, "stack-size={d}", .{stack_size}); + try argv.append(arg); + + // Put stack before globals so that stack overflow results in segfault immediately + // before corrupting globals. See https://github.com/ziglang/zig/issues/4496 + try argv.append("--stack-first"); + } else { + try argv.append("--no-entry"); // So lld doesn't look for _start. + try argv.append("--export-all"); + } + try argv.appendSlice(&[_][]const u8{ + "--allow-undefined", + "-o", + full_out_path, + }); - if (comp.clang_passthrough_mode) { - child.stdin_behavior = .Inherit; - child.stdout_behavior = .Inherit; - child.stderr_behavior = .Inherit; + // Positional arguments to the linker such as object files. + try argv.appendSlice(self.base.options.objects); - const term = child.spawnAndWait() catch |err| { - log.err("unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) }); - return error.UnableToSpawnSelf; - }; - switch (term) { - .Exited => |code| { - if (code != 0) { - // TODO https://github.com/ziglang/zig/issues/6342 - std.process.exit(1); - } - }, - else => std.process.abort(), + for (comp.c_object_table.items()) |entry| { + try argv.append(entry.key.status.success.object_path); + } + if (module_obj_path) |p| { + try argv.append(p); } - } else { - child.stdin_behavior = .Ignore; - child.stdout_behavior = .Ignore; - child.stderr_behavior = .Pipe; - - try child.spawn(); - const stderr = try child.stderr.?.reader().readAllAlloc(arena, 10 * 1024 * 1024); + if (self.base.options.output_mode != .Obj and + !self.base.options.skip_linker_dependencies and + !self.base.options.link_libc) + { + try argv.append(comp.libc_static_lib.?.full_object_path); + } - const term = child.wait() catch |err| { - log.err("unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) }); - return error.UnableToSpawnSelf; - }; + if (compiler_rt_path) |p| { + try argv.append(p); + } - switch (term) { - .Exited => |code| { - if (code != 0) { - // TODO parse this output and surface with the Compilation API rather than - // directly outputting to stderr here. - std.debug.print("{s}", .{stderr}); - return error.LLDReportedFailure; - } - }, - else => { - log.err("{s} terminated with stderr:\n{s}", .{ argv.items[0], stderr }); - return error.LLDCrashed; - }, + if (self.base.options.verbose_link) { + // Skip over our own name so that the LLD linker name is the first argv item. + Compilation.dump_argv(argv.items[1..]); } - if (stderr.len != 0) { - log.warn("unexpected LLD stderr:\n{s}", .{stderr}); + // Sadly, we must run LLD as a child process because it does not behave + // properly as a library. + const child = try std.ChildProcess.init(argv.items, arena); + defer child.deinit(); + + if (comp.clang_passthrough_mode) { + child.stdin_behavior = .Inherit; + child.stdout_behavior = .Inherit; + child.stderr_behavior = .Inherit; + + const term = child.spawnAndWait() catch |err| { + log.err("unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) }); + return error.UnableToSpawnSelf; + }; + switch (term) { + .Exited => |code| { + if (code != 0) { + // TODO https://github.com/ziglang/zig/issues/6342 + std.process.exit(1); + } + }, + else => std.process.abort(), + } + } else { + child.stdin_behavior = .Ignore; + child.stdout_behavior = .Ignore; + child.stderr_behavior = .Pipe; + + try child.spawn(); + + const stderr = try child.stderr.?.reader().readAllAlloc(arena, 10 * 1024 * 1024); + + const term = child.wait() catch |err| { + log.err("unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) }); + return error.UnableToSpawnSelf; + }; + + switch (term) { + .Exited => |code| { + if (code != 0) { + // TODO parse this output and surface with the Compilation API rather than + // directly outputting to stderr here. + std.debug.print("{s}", .{stderr}); + return error.LLDReportedFailure; + } + }, + else => { + log.err("{s} terminated with stderr:\n{s}", .{ argv.items[0], stderr }); + return error.LLDCrashed; + }, + } + + if (stderr.len != 0) { + log.warn("unexpected LLD stderr:\n{s}", .{stderr}); + } } } diff --git a/src/main.zig b/src/main.zig index ceada7e5d238..32c35739cd05 100644 --- a/src/main.zig +++ b/src/main.zig @@ -287,6 +287,8 @@ const usage_build_generic = \\ -fno-PIC Force-disable Position Independent Code \\ -fPIE Force-enable Position Independent Executable \\ -fno-PIE Force-disable Position Independent Executable + \\ -flto Force-enable Link Time Optimization (requires LLVM extensions) + \\ -fno-lto Force-disable Link Time Optimization \\ -fstack-check Enable stack probing in unsafe builds \\ -fno-stack-check Disable stack probing in safe builds \\ -fsanitize-c Enable C undefined behavior detection in unsafe builds @@ -511,6 +513,7 @@ fn buildOutputType( var enable_cache: ?bool = null; var want_pic: ?bool = null; var want_pie: ?bool = null; + var want_lto: ?bool = null; var want_sanitize_c: ?bool = null; var want_stack_check: ?bool = null; var want_red_zone: ?bool = null; @@ -852,6 +855,10 @@ fn buildOutputType( want_pie = true; } else if (mem.eql(u8, arg, "-fno-PIE")) { want_pie = false; + } else if (mem.eql(u8, arg, "-flto")) { + want_lto = true; + } else if (mem.eql(u8, arg, "-fno-lto")) { + want_lto = false; } else if (mem.eql(u8, arg, "-fstack-check")) { want_stack_check = true; } else if (mem.eql(u8, arg, "-fno-stack-check")) { @@ -1085,6 +1092,8 @@ fn buildOutputType( .no_pic => want_pic = false, .pie => want_pie = true, .no_pie => want_pie = false, + .lto => want_lto = true, + .no_lto => want_lto = false, .red_zone => want_red_zone = true, .no_red_zone => want_red_zone = false, .nostdlib => ensure_libc_on_non_freestanding = false, @@ -1771,6 +1780,7 @@ fn buildOutputType( .link_libcpp = link_libcpp, .want_pic = want_pic, .want_pie = want_pie, + .want_lto = want_lto, .want_sanitize_c = want_sanitize_c, .want_stack_check = want_stack_check, .want_red_zone = want_red_zone, @@ -2952,6 +2962,8 @@ pub const ClangArgIterator = struct { no_pic, pie, no_pie, + lto, + no_lto, nostdlib, nostdlib_cpp, shared, diff --git a/src/stage1.zig b/src/stage1.zig index 44fd8e109e60..8ab3b1d94d91 100644 --- a/src/stage1.zig +++ b/src/stage1.zig @@ -109,6 +109,7 @@ pub const Module = extern struct { err_color: ErrColor, pic: bool, pie: bool, + lto: bool, link_libc: bool, link_libcpp: bool, strip: bool, diff --git a/src/stage1/all_types.hpp b/src/stage1/all_types.hpp index c235bb362d23..726d1a3aafd6 100644 --- a/src/stage1/all_types.hpp +++ b/src/stage1/all_types.hpp @@ -2192,6 +2192,7 @@ struct CodeGen { bool is_single_threaded; bool have_pic; bool have_pie; + bool have_lto; bool link_mode_dynamic; bool dll_export_fns; bool have_stack_probing; diff --git a/src/stage1/codegen.cpp b/src/stage1/codegen.cpp index 7a973285dfd9..d850b3ee3169 100644 --- a/src/stage1/codegen.cpp +++ b/src/stage1/codegen.cpp @@ -8449,8 +8449,9 @@ static void zig_llvm_emit_output(CodeGen *g) { // Unfortunately, LLVM shits the bed when we ask for both binary and assembly. So we call the entire // pipeline multiple times if this is requested. if (asm_filename != nullptr && bin_filename != nullptr) { - if (ZigLLVMTargetMachineEmitToFile(g->target_machine, g->module, &err_msg, g->build_mode == BuildModeDebug, - is_small, g->enable_time_report, g->tsan_enabled, nullptr, bin_filename, llvm_ir_filename)) + if (ZigLLVMTargetMachineEmitToFile(g->target_machine, g->module, &err_msg, + g->build_mode == BuildModeDebug, is_small, g->enable_time_report, g->tsan_enabled, + g->have_lto, nullptr, bin_filename, llvm_ir_filename)) { fprintf(stderr, "LLVM failed to emit file: %s\n", err_msg); exit(1); @@ -8459,8 +8460,9 @@ static void zig_llvm_emit_output(CodeGen *g) { llvm_ir_filename = nullptr; } - if (ZigLLVMTargetMachineEmitToFile(g->target_machine, g->module, &err_msg, g->build_mode == BuildModeDebug, - is_small, g->enable_time_report, g->tsan_enabled, asm_filename, bin_filename, llvm_ir_filename)) + if (ZigLLVMTargetMachineEmitToFile(g->target_machine, g->module, &err_msg, + g->build_mode == BuildModeDebug, is_small, g->enable_time_report, g->tsan_enabled, + g->have_lto, asm_filename, bin_filename, llvm_ir_filename)) { fprintf(stderr, "LLVM failed to emit file: %s\n", err_msg); exit(1); diff --git a/src/stage1/stage1.cpp b/src/stage1/stage1.cpp index d68012157768..39e3bad1a582 100644 --- a/src/stage1/stage1.cpp +++ b/src/stage1/stage1.cpp @@ -90,6 +90,7 @@ void zig_stage1_build_object(struct ZigStage1 *stage1) { g->dll_export_fns = stage1->dll_export_fns; g->have_pic = stage1->pic; g->have_pie = stage1->pie; + g->have_lto = stage1->lto; g->have_stack_probing = stage1->enable_stack_probing; g->red_zone = stage1->red_zone; g->is_single_threaded = stage1->is_single_threaded; diff --git a/src/stage1/stage1.h b/src/stage1/stage1.h index 6db3621cf5f2..7fa1576d6462 100644 --- a/src/stage1/stage1.h +++ b/src/stage1/stage1.h @@ -178,6 +178,7 @@ struct ZigStage1 { bool pic; bool pie; + bool lto; bool link_libc; bool link_libcpp; bool strip; diff --git a/src/zig_llvm.cpp b/src/zig_llvm.cpp index 51af5e06d17f..280920ac7426 100644 --- a/src/zig_llvm.cpp +++ b/src/zig_llvm.cpp @@ -22,6 +22,7 @@ #include #include +#include #include #include #include @@ -184,7 +185,7 @@ unsigned ZigLLVMDataLayoutGetProgramAddressSpace(LLVMTargetDataRef TD) { bool ZigLLVMTargetMachineEmitToFile(LLVMTargetMachineRef targ_machine_ref, LLVMModuleRef module_ref, char **error_message, bool is_debug, - bool is_small, bool time_report, bool tsan, + bool is_small, bool time_report, bool tsan, bool lto, const char *asm_filename, const char *bin_filename, const char *llvm_ir_filename) { TimePassesIsEnabled = time_report; @@ -234,7 +235,7 @@ bool ZigLLVMTargetMachineEmitToFile(LLVMTargetMachineRef targ_machine_ref, LLVMM PMBuilder->VerifyInput = assertions_on; PMBuilder->VerifyOutput = assertions_on; PMBuilder->MergeFunctions = !is_debug; - PMBuilder->PrepareForLTO = false; + PMBuilder->PrepareForLTO = lto; PMBuilder->PrepareForThinLTO = false; PMBuilder->PerformThinLTO = false; @@ -272,7 +273,7 @@ bool ZigLLVMTargetMachineEmitToFile(LLVMTargetMachineRef targ_machine_ref, LLVMM PMBuilder->populateModulePassManager(MPM); // Set output passes. - if (dest_bin) { + if (dest_bin && !lto) { if (target_machine->addPassesToEmitFile(MPM, *dest_bin, nullptr, CGFT_ObjectFile)) { *error_message = strdup("TargetMachine can't emit an object file"); return true; @@ -299,6 +300,9 @@ bool ZigLLVMTargetMachineEmitToFile(LLVMTargetMachineRef targ_machine_ref, LLVMM return true; } } + if (dest_bin && lto) { + WriteBitcodeToFile(*module, *dest_bin); + } if (time_report) { TimerGroup::printAll(errs()); diff --git a/src/zig_llvm.h b/src/zig_llvm.h index 9a31092265a1..504ea4ec0148 100644 --- a/src/zig_llvm.h +++ b/src/zig_llvm.h @@ -48,7 +48,7 @@ ZIG_EXTERN_C char *ZigLLVMGetNativeFeatures(void); ZIG_EXTERN_C bool ZigLLVMTargetMachineEmitToFile(LLVMTargetMachineRef targ_machine_ref, LLVMModuleRef module_ref, char **error_message, bool is_debug, - bool is_small, bool time_report, bool tsan, + bool is_small, bool time_report, bool tsan, bool lto, const char *asm_filename, const char *bin_filename, const char *llvm_ir_filename); diff --git a/tools/update_clang_options.zig b/tools/update_clang_options.zig index 189a2ed7faad..2aeee6845b59 100644 --- a/tools/update_clang_options.zig +++ b/tools/update_clang_options.zig @@ -62,6 +62,14 @@ const known_options = [_]KnownOpt{ .name = "fno-PIE", .ident = "no_pie", }, + .{ + .name = "flto", + .ident = "lto", + }, + .{ + .name = "fno-lto", + .ident = "no_lto", + }, .{ .name = "nolibc", .ident = "nostdlib",