Commit 0d4b6ac741
Changed files (17)
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 {
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}));
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});
+ }
}
}
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) {
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});
+ }
}
}
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;
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);
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;
src/stage1/stage1.h
@@ -178,6 +178,7 @@ struct ZigStage1 {
bool pic;
bool pie;
+ bool lto;
bool link_libc;
bool link_libcpp;
bool strip;
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="),
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,
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,
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,
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,
src/zig_llvm.cpp
@@ -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());
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);
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",