Merge pull request #7856 from ziglang/lto

add LTO support
This commit is contained in:
Andrew Kelley 2021-01-24 11:09:48 -08:00 committed by GitHub
commit 15278b7f4b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 634 additions and 457 deletions

View File

@ -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,

View File

@ -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="),

View File

@ -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 {

View File

@ -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,

View File

@ -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}));

View File

@ -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");
}
try argv.append("-error-limit=0");
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 (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.linker_script) |linker_script| {
try argv.append("-T");
try argv.append(linker_script);
}
if (gc_sections) {
try argv.append("--gc-sections");
}
if (self.base.options.eh_frame_hdr) {
try argv.append("--eh-frame-hdr");
}
if (self.base.options.emit_relocs) {
try argv.append("--emit-relocs");
}
if (self.base.options.rdynamic) {
try argv.append("--export-dynamic");
}
try argv.appendSlice(self.base.options.extra_lld_args);
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 (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);
}
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 (self.base.options.pie and self.base.options.output_mode == .Exe) {
try argv.append("-pie");
}
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.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 (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 {
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 "Scrt1.o";
}
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;
};
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);
}
}
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| {
try argv.append("-L");
try argv.append(libc_installation.crt_dir.?);
}
if (have_dynamic_linker) {
if (self.base.options.dynamic_linker) |dynamic_linker| {
try argv.append("-dynamic-linker");
try argv.append(dynamic_linker);
}
}
}
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);
}
}
// 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 (module_obj_path) |p| {
try argv.append(p);
}
// TSAN
if (self.base.options.tsan) {
try argv.append(comp.tsan_static_lib.?.full_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);
}
// compiler-rt
if (compiler_rt_path) |p| {
try argv.append(p);
}
// 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++ 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.
}
}
}
// 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 (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..]);
}
// 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(),
// 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 {
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;
},
// 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 (stderr.len != 0) {
log.warn("unexpected LLD stderr:\n{s}", .{stderr});
try argv.append("-error-limit=0");
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.output_mode == .Exe) {
try argv.append("-z");
try argv.append(try std.fmt.allocPrint(arena, "stack-size={d}", .{stack_size}));
}
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.linker_script) |linker_script| {
try argv.append("-T");
try argv.append(linker_script);
}
if (gc_sections) {
try argv.append("--gc-sections");
}
if (self.base.options.eh_frame_hdr) {
try argv.append("--eh-frame-hdr");
}
if (self.base.options.emit_relocs) {
try argv.append("--emit-relocs");
}
if (self.base.options.rdynamic) {
try argv.append("--export-dynamic");
}
try argv.appendSlice(self.base.options.extra_lld_args);
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 (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);
}
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 (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.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 "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"));
}
}
// 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);
}
}
}
}
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| {
try argv.append("-L");
try argv.append(libc_installation.crt_dir.?);
}
if (have_dynamic_linker) {
if (self.base.options.dynamic_linker) |dynamic_linker| {
try argv.append("-dynamic-linker");
try argv.append(dynamic_linker);
}
}
}
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);
}
}
// 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 (module_obj_path) |p| {
try argv.append(p);
}
// TSAN
if (self.base.options.tsan) {
try argv.append(comp.tsan_static_lib.?.full_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);
}
// compiler-rt
if (compiler_rt_path) |p| {
try argv.append(p);
}
// 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++ 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.
}
}
}
// 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 (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..]);
}
// 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});
}
}
}

View File

@ -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) {

View File

@ -362,122 +362,157 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation) !void {
};
}
const is_obj = self.base.options.output_mode == .Obj;
const full_out_path = try directory.join(arena, &[_][]const u8{self.base.options.emit.?.sub_path});
// 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 (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];
try argv.append("-error-limit=0");
if (comp.c_object_table.count() != 0)
break :blk comp.c_object_table.items()[0].key.status.success.object_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);
if (module_obj_path) |p|
break :blk p;
// 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}),
});
// 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 (module_obj_path) |p| {
try argv.append(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);
}
if (compiler_rt_path) |p| {
try argv.append(p);
}
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();
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;
// TODO I think this is unreachable. Audit this situation when solving the above TODO
// regarding eliding redundant object -> object transformations.
return error.NoObjectsToLink;
};
switch (term) {
.Exited => |code| {
if (code != 0) {
// TODO https://github.com/ziglang/zig/issues/6342
std.process.exit(1);
}
},
else => std.process.abort(),
// 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 {
child.stdin_behavior = .Ignore;
child.stdout_behavior = .Ignore;
child.stderr_behavior = .Pipe;
const is_obj = self.base.options.output_mode == .Obj;
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;
},
// 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 (stderr.len != 0) {
log.warn("unexpected LLD stderr:\n{s}", .{stderr});
try argv.append("-error-limit=0");
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.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,
});
// 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 (module_obj_path) |p| {
try argv.append(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);
}
if (compiler_rt_path) |p| {
try argv.append(p);
}
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();
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});
}
}
}

View File

@ -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,

View File

@ -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",

View File

@ -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,

View File

@ -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;

View File

@ -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);

View File

@ -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;

View File

@ -178,6 +178,7 @@ struct ZigStage1 {
bool pic;
bool pie;
bool lto;
bool link_libc;
bool link_libcpp;
bool strip;

View File

@ -354,8 +354,6 @@ pub fn hasRedZone(target: std.Target) bool {
return switch (target.cpu.arch) {
.x86_64,
.i386,
.wasm32,
.wasm64,
.powerpc,
.powerpc64,
.powerpc64le,

View File

@ -22,6 +22,7 @@
#include <llvm/Analysis/TargetLibraryInfo.h>
#include <llvm/Analysis/TargetTransformInfo.h>
#include <llvm/Bitcode/BitcodeWriter.h>
#include <llvm/IR/DIBuilder.h>
#include <llvm/IR/DiagnosticInfo.h>
#include <llvm/IR/IRBuilder.h>
@ -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());

View File

@ -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);

View File

@ -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",