From a950cb42bdf7b3f339071868c6675e7dcc892bae Mon Sep 17 00:00:00 2001 From: Kurt Kartaltepe Date: Fri, 9 Jul 2021 20:41:35 -0700 Subject: [PATCH 1/5] Coff linker: Add IMPLIB support Allow --out-implib and -implib as passed by cmake and meson to be correctly passed through to the linker to generate import libraries. --- src/Compilation.zig | 6 +++++- src/link.zig | 1 + src/link/Coff.zig | 5 +++++ src/main.zig | 29 +++++++++++++++++++++++++++++ 4 files changed, 40 insertions(+), 1 deletion(-) diff --git a/src/Compilation.zig b/src/Compilation.zig index 48899f6f6e..61533eebb0 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -767,6 +767,8 @@ pub const InitOptions = struct { test_filter: ?[]const u8 = null, test_name_prefix: ?[]const u8 = null, subsystem: ?std.Target.SubSystem = null, + /// Windows/PE only. Where to output the import library, can contain directories. + out_implib: ?[]const u8 = null, /// WASI-only. Type of WASI execution model ("command" or "reactor"). wasi_exec_model: ?std.builtin.WasiExecModel = null, /// (Zig compiler development) Enable dumping linker's state as JSON. @@ -946,7 +948,8 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { options.output_mode == .Lib or options.lld_argv.len != 0 or options.image_base_override != null or - options.linker_script != null or options.version_script != null) + options.linker_script != null or options.version_script != null or + options.out_implib != null) { break :blk true; } @@ -1461,6 +1464,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { .each_lib_rpath = options.each_lib_rpath orelse options.is_native_os, .disable_lld_caching = options.disable_lld_caching, .subsystem = options.subsystem, + .out_implib = options.out_implib, .is_test = options.is_test, .wasi_exec_model = wasi_exec_model, .use_stage1 = use_stage1, diff --git a/src/link.zig b/src/link.zig index 4ad5952767..76f50a78fe 100644 --- a/src/link.zig +++ b/src/link.zig @@ -127,6 +127,7 @@ pub const Options = struct { gc_sections: ?bool = null, allow_shlib_undefined: ?bool, subsystem: ?std.Target.SubSystem, + out_implib: ?[]const u8, linker_script: ?[]const u8, version_script: ?[]const u8, soname: ?[]const u8, diff --git a/src/link/Coff.zig b/src/link/Coff.zig index b6f3279779..ee59f4f034 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -948,6 +948,7 @@ fn linkWithLLD(self: *Coff, comp: *Compilation) !void { man.hash.add(self.base.options.dynamicbase); man.hash.addOptional(self.base.options.major_subsystem_version); man.hash.addOptional(self.base.options.minor_subsystem_version); + man.hash.addOptionalBytes(self.base.options.out_implib); // We don't actually care whether it's a cache hit or miss; we just need the digest and the lock. _ = try man.hit(); @@ -1094,6 +1095,10 @@ fn linkWithLLD(self: *Coff, comp: *Compilation) !void { try argv.append(p); } + if (self.base.options.out_implib != null) { + try argv.append(try allocPrint(arena, "-IMPLIB:{s}.lib", .{full_out_path})); + } + const resolved_subsystem: ?std.Target.SubSystem = blk: { if (self.base.options.subsystem) |explicit| break :blk explicit; switch (target.os.tag) { diff --git a/src/main.zig b/src/main.zig index 0b274b3aee..4900743677 100644 --- a/src/main.zig +++ b/src/main.zig @@ -654,6 +654,7 @@ fn buildOutputType( var main_pkg_path: ?[]const u8 = null; var clang_preprocessor_mode: Compilation.ClangPreprocessorMode = .no; var subsystem: ?std.Target.SubSystem = null; + var out_implib: ?[]const u8 = null; var major_subsystem_version: ?u32 = null; var minor_subsystem_version: ?u32 = null; var wasi_exec_model: ?std.builtin.WasiExecModel = null; @@ -1637,6 +1638,14 @@ fn buildOutputType( fatal("unable to parse -current_version '{s}': {s}", .{ linker_args.items[i], @errorName(err) }); }; have_version = true; + } else if (mem.eql(u8, arg, "--out-implib") or + mem.eql(u8, arg, "-implib")) + { + i += 1; + if (i >= linker_args.items.len) { + fatal("expected linker arg after '{s}'", .{arg}); + } + out_implib = linker_args.items[i]; } else { warn("unsupported linker arg: {s}", .{arg}); } @@ -2154,6 +2163,16 @@ fn buildOutputType( else => false, }; + // Always output import libraries (.lib) when building for msvc to replicate + // `link` behavior. lld does not always output import libraries so on the + // gnu abi users must set out_implib. + if (output_mode == .Lib and emit_bin == .yes and target_info.target.abi == .msvc and out_implib == null) { + const emit_bin_ext = fs.path.extension(emit_bin.yes); + out_implib = try std.fmt.allocPrint(gpa, "{s}.lib", .{ + emit_bin.yes[0 .. emit_bin.yes.len - emit_bin_ext.len], + }); + } + gimmeMoreOfThoseSweetSweetFileDescriptors(); const comp = Compilation.create(gpa, .{ @@ -2263,6 +2282,7 @@ fn buildOutputType( .test_name_prefix = test_name_prefix, .disable_lld_caching = !have_enable_cache, .subsystem = subsystem, + .out_implib = out_implib, .wasi_exec_model = wasi_exec_model, .debug_compile_errors = debug_compile_errors, .enable_link_snapshots = enable_link_snapshots, @@ -2661,6 +2681,15 @@ fn updateModule(gpa: *Allocator, comp: *Compilation, hook: AfterUpdateHook) !voi _ = try cache_dir.updateFile(src_pdb_path, cwd, dst_pdb_path, .{}); } + + if (comp.bin_file.options.out_implib) |out_implib| { + const src_implib_path = try std.fmt.allocPrint(gpa, "{s}.lib", .{bin_sub_path}); + defer gpa.free(src_implib_path); + if (std.fs.path.dirname(out_implib)) |implib_dir| { + try cwd.makePath(implib_dir); + } + _ = try cache_dir.updateFile(src_implib_path, cwd, out_implib, .{}); + } }, } } From fd369bcb0b67bde0072f1549be2fd0b34014703f Mon Sep 17 00:00:00 2001 From: Kurt Kartaltepe Date: Fri, 9 Jul 2021 21:29:43 -0700 Subject: [PATCH 2/5] Coff Linker: Pass extra lld args Previously these were added to the hash but not actually appended to the linker arguments. --- src/link/Coff.zig | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index ee59f4f034..c9feebe7a2 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -1057,6 +1057,9 @@ fn linkWithLLD(self: *Coff, comp: *Compilation) !void { if (self.base.options.dynamicbase) { try argv.append("-dynamicbase"); } + + try argv.appendSlice(self.base.options.extra_lld_args); + const subsystem_suffix = ss: { if (self.base.options.major_subsystem_version) |major| { if (self.base.options.minor_subsystem_version) |minor| { From 7e23b3245a9bf6e002009e6c18c10a9995671afa Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 24 Nov 2021 17:11:37 -0700 Subject: [PATCH 3/5] stage2: remove extra_lld_args This mechanism for sending arbitrary linker args to LLD has no place in the Zig frontend, because our goal is for the frontend to understand all the arguments and not treat linker args like a black box. For example we have self-hosted linking in addition to LLD, so we want to have the options make sense to both linking codepaths, not just the LLD one. Passing -O linker args will now result in a warning that the arg does nothing. --- src/Compilation.zig | 3 --- src/link.zig | 2 -- src/link/Coff.zig | 3 --- src/link/Elf.zig | 3 --- src/link/Wasm.zig | 1 - src/main.zig | 14 +++++++++----- 6 files changed, 9 insertions(+), 17 deletions(-) diff --git a/src/Compilation.zig b/src/Compilation.zig index 61533eebb0..c84fd2c96d 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -667,7 +667,6 @@ pub const InitOptions = struct { optimize_mode: std.builtin.Mode = .Debug, keep_source_files_loaded: bool = false, clang_argv: []const []const u8 = &[0][]const u8{}, - lld_argv: []const []const u8 = &[0][]const u8{}, lib_dirs: []const []const u8 = &[0][]const u8{}, rpath_list: []const []const u8 = &[0][]const u8{}, c_source_files: []const CSourceFile = &[0]CSourceFile{}, @@ -946,7 +945,6 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { link_eh_frame_hdr or options.link_emit_relocs or options.output_mode == .Lib or - options.lld_argv.len != 0 or options.image_base_override != null or options.linker_script != null or options.version_script != null or options.out_implib != null) @@ -1440,7 +1438,6 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { .eh_frame_hdr = link_eh_frame_hdr, .emit_relocs = options.link_emit_relocs, .rdynamic = options.rdynamic, - .extra_lld_args = options.lld_argv, .soname = options.soname, .version = options.version, .compatibility_version = options.compatibility_version, diff --git a/src/link.zig b/src/link.zig index 76f50a78fe..f00d7809f4 100644 --- a/src/link.zig +++ b/src/link.zig @@ -132,8 +132,6 @@ pub const Options = struct { version_script: ?[]const u8, soname: ?[]const u8, llvm_cpu_features: ?[*:0]const u8, - /// Extra args passed directly to LLD. Ignored when not linking with LLD. - extra_lld_args: []const []const u8, objects: []const []const u8, framework_dirs: []const []const u8, diff --git a/src/link/Coff.zig b/src/link/Coff.zig index c9feebe7a2..19a99a2e32 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -927,7 +927,6 @@ fn linkWithLLD(self: *Coff, comp: *Compilation) !void { try man.addOptionalFile(module_obj_path); man.hash.addOptional(self.base.options.stack_size_override); man.hash.addOptional(self.base.options.image_base_override); - man.hash.addListOfBytes(self.base.options.extra_lld_args); man.hash.addListOfBytes(self.base.options.lib_dirs); man.hash.add(self.base.options.skip_linker_dependencies); if (self.base.options.link_libc) { @@ -1058,8 +1057,6 @@ fn linkWithLLD(self: *Coff, comp: *Compilation) !void { try argv.append("-dynamicbase"); } - try argv.appendSlice(self.base.options.extra_lld_args); - const subsystem_suffix = ss: { if (self.base.options.major_subsystem_version) |major| { if (self.base.options.minor_subsystem_version) |minor| { diff --git a/src/link/Elf.zig b/src/link/Elf.zig index cc7d5ba0e5..43447e0ec4 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -1322,7 +1322,6 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void { man.hash.add(self.base.options.eh_frame_hdr); man.hash.add(self.base.options.emit_relocs); man.hash.add(self.base.options.rdynamic); - man.hash.addListOfBytes(self.base.options.extra_lld_args); man.hash.addListOfBytes(self.base.options.lib_dirs); man.hash.addListOfBytes(self.base.options.rpath_list); man.hash.add(self.base.options.each_lib_rpath); @@ -1461,8 +1460,6 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void { 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"); diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index cf05fcd94a..fd6e042f9a 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -714,7 +714,6 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation) !void { try man.addOptionalFile(module_obj_path); try man.addOptionalFile(compiler_rt_path); man.hash.addOptional(self.base.options.stack_size_override); - man.hash.addListOfBytes(self.base.options.extra_lld_args); man.hash.add(self.base.options.import_memory); man.hash.addOptional(self.base.options.initial_memory); man.hash.addOptional(self.base.options.max_memory); diff --git a/src/main.zig b/src/main.zig index 4900743677..7d8b9b1265 100644 --- a/src/main.zig +++ b/src/main.zig @@ -672,9 +672,6 @@ fn buildOutputType( var extra_cflags = std.ArrayList([]const u8).init(gpa); defer extra_cflags.deinit(); - var lld_argv = std.ArrayList([]const u8).init(gpa); - defer lld_argv.deinit(); - var lib_dirs = std.ArrayList([]const u8).init(gpa); defer lib_dirs.deinit(); @@ -1474,8 +1471,16 @@ fn buildOutputType( fatal("expected linker arg after '{s}'", .{arg}); } version_script = linker_args.items[i]; + } else if (mem.eql(u8, arg, "-O")) { + i += 1; + if (i >= linker_args.items.len) { + fatal("expected linker arg after '{s}'", .{arg}); + } + warn("ignoring linker arg -O{s} because it does nothing", .{ + linker_args.items[i], + }); } else if (mem.startsWith(u8, arg, "-O")) { - try lld_argv.append(arg); + warn("ignoring linker arg {s} because it does nothing", .{arg}); } else if (mem.eql(u8, arg, "--gc-sections")) { linker_gc_sections = true; } else if (mem.eql(u8, arg, "--no-gc-sections")) { @@ -2200,7 +2205,6 @@ fn buildOutputType( .optimize_mode = optimize_mode, .keep_source_files_loaded = false, .clang_argv = clang_argv.items, - .lld_argv = lld_argv.items, .lib_dirs = lib_dirs.items, .rpath_list = rpath_list.items, .c_source_files = c_source_files.items, From 27c5c7fb23fceb0a333444408a1dea4188a14c32 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 24 Nov 2021 18:08:56 -0700 Subject: [PATCH 4/5] stage2: proper `-femit-implib` frontend support * Improve the logic for determining whether emitting an import lib is eligible, and improve the error message when the user provides contradictory arguments. * Integrate with the EmitLoc / Emit system that already exists, and use the `-femit-implib[=path]`/`-fno-emit-implib` convention that already exists. * Proper integration with the caching system. * CLI: fix bug in error reporting for resolving EmitLoc values for other parameters. --- src/Compilation.zig | 30 +++++++++-- src/link.zig | 3 +- src/link/Coff.zig | 11 ++-- src/main.zig | 123 ++++++++++++++++++++++++++++++-------------- 4 files changed, 117 insertions(+), 50 deletions(-) diff --git a/src/Compilation.zig b/src/Compilation.zig index c84fd2c96d..2ff390f196 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -654,6 +654,8 @@ pub const InitOptions = struct { emit_analysis: ?EmitLoc = null, /// `null` means to not emit docs. emit_docs: ?EmitLoc = null, + /// `null` means to not emit an import lib. + emit_implib: ?EmitLoc = null, link_mode: ?std.builtin.LinkMode = null, dll_export_fns: ?bool = false, /// Normally when using LLD to link, Zig uses a file named "lld.id" in the @@ -766,8 +768,6 @@ pub const InitOptions = struct { test_filter: ?[]const u8 = null, test_name_prefix: ?[]const u8 = null, subsystem: ?std.Target.SubSystem = null, - /// Windows/PE only. Where to output the import library, can contain directories. - out_implib: ?[]const u8 = null, /// WASI-only. Type of WASI execution model ("command" or "reactor"). wasi_exec_model: ?std.builtin.WasiExecModel = null, /// (Zig compiler development) Enable dumping linker's state as JSON. @@ -947,7 +947,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { options.output_mode == .Lib or options.image_base_override != null or options.linker_script != null or options.version_script != null or - options.out_implib != null) + options.emit_implib != null) { break :blk true; } @@ -1183,6 +1183,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { cache.hash.add(options.output_mode); cache.hash.add(options.machine_code_model); cache.hash.addOptionalEmitLoc(options.emit_bin); + cache.hash.addOptionalEmitLoc(options.emit_implib); cache.hash.addBytes(options.root_name); if (options.target.os.tag == .wasi) cache.hash.add(wasi_exec_model); // TODO audit this and make sure everything is in it @@ -1339,18 +1340,21 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { const bin_file_emit: ?link.Emit = blk: { const emit_bin = options.emit_bin orelse break :blk null; + if (emit_bin.directory) |directory| { break :blk link.Emit{ .directory = directory, .sub_path = emit_bin.basename, }; } + if (module) |zm| { break :blk link.Emit{ .directory = zm.zig_cache_artifact_directory, .sub_path = emit_bin.basename, }; } + // We could use the cache hash as is no problem, however, we increase // the likelihood of cache hits by adding the first C source file // path name (not contents) to the hash. This way if the user is compiling @@ -1377,6 +1381,24 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { }; }; + const implib_emit: ?link.Emit = blk: { + const emit_implib = options.emit_implib orelse break :blk null; + + if (emit_implib.directory) |directory| { + break :blk link.Emit{ + .directory = directory, + .sub_path = emit_implib.basename, + }; + } + + // Use the same directory as the bin. The CLI already emits an + // error if -fno-emit-bin is combined with -femit-implib. + break :blk link.Emit{ + .directory = bin_file_emit.?.directory, + .sub_path = emit_implib.basename, + }; + }; + var system_libs: std.StringArrayHashMapUnmanaged(SystemLib) = .{}; errdefer system_libs.deinit(gpa); try system_libs.ensureTotalCapacity(gpa, options.system_lib_names.len); @@ -1386,6 +1408,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { const bin_file = try link.File.openPath(gpa, .{ .emit = bin_file_emit, + .implib_emit = implib_emit, .root_name = root_name, .module = module, .target = options.target, @@ -1461,7 +1484,6 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { .each_lib_rpath = options.each_lib_rpath orelse options.is_native_os, .disable_lld_caching = options.disable_lld_caching, .subsystem = options.subsystem, - .out_implib = options.out_implib, .is_test = options.is_test, .wasi_exec_model = wasi_exec_model, .use_stage1 = use_stage1, diff --git a/src/link.zig b/src/link.zig index f00d7809f4..3657397500 100644 --- a/src/link.zig +++ b/src/link.zig @@ -47,6 +47,8 @@ pub const Options = struct { /// This is `null` when -fno-emit-bin is used. When `openPath` or `flush` is called, /// it will have already been null-checked. emit: ?Emit, + /// This is `null` not building a Windows DLL, or when -fno-emit-implib is used. + implib_emit: ?Emit, target: std.Target, output_mode: std.builtin.OutputMode, link_mode: std.builtin.LinkMode, @@ -127,7 +129,6 @@ pub const Options = struct { gc_sections: ?bool = null, allow_shlib_undefined: ?bool, subsystem: ?std.Target.SubSystem, - out_implib: ?[]const u8, linker_script: ?[]const u8, version_script: ?[]const u8, soname: ?[]const u8, diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 19a99a2e32..55bd912bef 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -947,7 +947,6 @@ fn linkWithLLD(self: *Coff, comp: *Compilation) !void { man.hash.add(self.base.options.dynamicbase); man.hash.addOptional(self.base.options.major_subsystem_version); man.hash.addOptional(self.base.options.minor_subsystem_version); - man.hash.addOptionalBytes(self.base.options.out_implib); // We don't actually care whether it's a cache hit or miss; we just need the digest and the lock. _ = try man.hit(); @@ -978,7 +977,6 @@ fn linkWithLLD(self: *Coff, comp: *Compilation) !void { } const full_out_path = try directory.join(arena, &[_][]const u8{self.base.options.emit.?.sub_path}); - if (self.base.options.output_mode == .Obj) { // LLD's COFF driver does not support the equivalent of `-r` so we do a simple file copy // here. TODO: think carefully about how we can avoid this redundant operation when doing @@ -1070,6 +1068,11 @@ fn linkWithLLD(self: *Coff, comp: *Compilation) !void { try argv.append(try allocPrint(arena, "-OUT:{s}", .{full_out_path})); + if (self.base.options.implib_emit) |emit| { + const implib_out_path = try emit.directory.join(arena, &[_][]const u8{emit.sub_path}); + try argv.append(try allocPrint(arena, "-IMPLIB:{s}", .{implib_out_path})); + } + if (self.base.options.link_libc) { if (self.base.options.libc_installation) |libc_installation| { try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{libc_installation.crt_dir.?})); @@ -1095,10 +1098,6 @@ fn linkWithLLD(self: *Coff, comp: *Compilation) !void { try argv.append(p); } - if (self.base.options.out_implib != null) { - try argv.append(try allocPrint(arena, "-IMPLIB:{s}.lib", .{full_out_path})); - } - const resolved_subsystem: ?std.Target.SubSystem = blk: { if (self.base.options.subsystem) |explicit| break :blk explicit; switch (target.os.tag) { diff --git a/src/main.zig b/src/main.zig index 7d8b9b1265..2c99f508d4 100644 --- a/src/main.zig +++ b/src/main.zig @@ -317,6 +317,8 @@ const usage_build_generic = \\ -fno-emit-docs (default) Do not produce docs/ dir with html documentation \\ -femit-analysis[=path] Write analysis JSON file with type information \\ -fno-emit-analysis (default) Do not write analysis JSON file with type information + \\ -femit-implib[=path] (default) Produce an import .lib when building a Windows DLL + \\ -fno-emit-implib Do not produce an import .lib when building a Windows DLL \\ --show-builtin Output the source of @import("builtin") then exit \\ --cache-dir [path] Override the local cache directory \\ --global-cache-dir [path] Override the global cache directory @@ -585,6 +587,8 @@ fn buildOutputType( var emit_llvm_bc: Emit = .no; var emit_docs: Emit = .no; var emit_analysis: Emit = .no; + var emit_implib: Emit = .yes_default_path; + var emit_implib_arg_provided = false; var target_arch_os_abi: []const u8 = "native"; var target_mcpu: ?[]const u8 = null; var target_dynamic_linker: ?[]const u8 = null; @@ -654,7 +658,6 @@ fn buildOutputType( var main_pkg_path: ?[]const u8 = null; var clang_preprocessor_mode: Compilation.ClangPreprocessorMode = .no; var subsystem: ?std.Target.SubSystem = null; - var out_implib: ?[]const u8 = null; var major_subsystem_version: ?u32 = null; var minor_subsystem_version: ?u32 = null; var wasi_exec_model: ?std.builtin.WasiExecModel = null; @@ -1091,6 +1094,15 @@ fn buildOutputType( emit_analysis = .{ .yes = arg["-femit-analysis=".len..] }; } else if (mem.eql(u8, arg, "-fno-emit-analysis")) { emit_analysis = .no; + } else if (mem.eql(u8, arg, "-femit-implib")) { + emit_implib = .yes_default_path; + emit_implib_arg_provided = true; + } else if (mem.startsWith(u8, arg, "-femit-implib=")) { + emit_implib = .{ .yes = arg["-femit-implib=".len..] }; + emit_implib_arg_provided = true; + } else if (mem.eql(u8, arg, "-fno-emit-implib")) { + emit_implib = .no; + emit_implib_arg_provided = true; } else if (mem.eql(u8, arg, "-dynamic")) { link_mode = .Dynamic; } else if (mem.eql(u8, arg, "-static")) { @@ -1650,7 +1662,8 @@ fn buildOutputType( if (i >= linker_args.items.len) { fatal("expected linker arg after '{s}'", .{arg}); } - out_implib = linker_args.items[i]; + emit_implib = .{ .yes = linker_args.items[i] }; + emit_implib_arg_provided = true; } else { warn("unsupported linker arg: {s}", .{arg}); } @@ -1998,11 +2011,15 @@ fn buildOutputType( const default_h_basename = try std.fmt.allocPrint(arena, "{s}.h", .{root_name}); var emit_h_resolved = emit_h.resolve(default_h_basename) catch |err| { switch (emit_h) { - .yes => { - fatal("unable to open directory from argument '-femit-h', '{s}': {s}", .{ emit_h.yes, @errorName(err) }); + .yes => |p| { + fatal("unable to open directory from argument '-femit-h', '{s}': {s}", .{ + p, @errorName(err), + }); }, .yes_default_path => { - fatal("unable to open directory from arguments '--name' or '-fsoname', '{s}': {s}", .{ default_h_basename, @errorName(err) }); + fatal("unable to open directory from arguments '--name' or '-fsoname', '{s}': {s}", .{ + default_h_basename, @errorName(err), + }); }, .no => unreachable, } @@ -2012,11 +2029,15 @@ fn buildOutputType( const default_asm_basename = try std.fmt.allocPrint(arena, "{s}.s", .{root_name}); var emit_asm_resolved = emit_asm.resolve(default_asm_basename) catch |err| { switch (emit_asm) { - .yes => { - fatal("unable to open directory from argument '-femit-asm', '{s}': {s}", .{ emit_asm.yes, @errorName(err) }); + .yes => |p| { + fatal("unable to open directory from argument '-femit-asm', '{s}': {s}", .{ + p, @errorName(err), + }); }, .yes_default_path => { - fatal("unable to open directory from arguments '--name' or '-fsoname', '{s}': {s}", .{ default_asm_basename, @errorName(err) }); + fatal("unable to open directory from arguments '--name' or '-fsoname', '{s}': {s}", .{ + default_asm_basename, @errorName(err), + }); }, .no => unreachable, } @@ -2026,11 +2047,15 @@ fn buildOutputType( const default_llvm_ir_basename = try std.fmt.allocPrint(arena, "{s}.ll", .{root_name}); var emit_llvm_ir_resolved = emit_llvm_ir.resolve(default_llvm_ir_basename) catch |err| { switch (emit_llvm_ir) { - .yes => { - fatal("unable to open directory from argument '-femit-llvm-ir', '{s}': {s}", .{ emit_llvm_ir.yes, @errorName(err) }); + .yes => |p| { + fatal("unable to open directory from argument '-femit-llvm-ir', '{s}': {s}", .{ + p, @errorName(err), + }); }, .yes_default_path => { - fatal("unable to open directory from arguments '--name' or '-fsoname', '{s}': {s}", .{ default_llvm_ir_basename, @errorName(err) }); + fatal("unable to open directory from arguments '--name' or '-fsoname', '{s}': {s}", .{ + default_llvm_ir_basename, @errorName(err), + }); }, .no => unreachable, } @@ -2040,11 +2065,15 @@ fn buildOutputType( const default_llvm_bc_basename = try std.fmt.allocPrint(arena, "{s}.bc", .{root_name}); var emit_llvm_bc_resolved = emit_llvm_bc.resolve(default_llvm_bc_basename) catch |err| { switch (emit_llvm_bc) { - .yes => { - fatal("unable to open directory from argument '-femit-llvm-bc', '{s}': {s}", .{ emit_llvm_bc.yes, @errorName(err) }); + .yes => |p| { + fatal("unable to open directory from argument '-femit-llvm-bc', '{s}': {s}", .{ + p, @errorName(err), + }); }, .yes_default_path => { - fatal("unable to open directory from arguments '--name' or '-fsoname', '{s}': {s}", .{ default_llvm_bc_basename, @errorName(err) }); + fatal("unable to open directory from arguments '--name' or '-fsoname', '{s}': {s}", .{ + default_llvm_bc_basename, @errorName(err), + }); }, .no => unreachable, } @@ -2054,11 +2083,15 @@ fn buildOutputType( const default_analysis_basename = try std.fmt.allocPrint(arena, "{s}-analysis.json", .{root_name}); var emit_analysis_resolved = emit_analysis.resolve(default_analysis_basename) catch |err| { switch (emit_analysis) { - .yes => { - fatal("unable to open directory from argument 'femit-analysis', '{s}': {s}", .{ emit_analysis.yes, @errorName(err) }); + .yes => |p| { + fatal("unable to open directory from argument '-femit-analysis', '{s}': {s}", .{ + p, @errorName(err), + }); }, .yes_default_path => { - fatal("unable to open directory from arguments 'name' or 'soname', '{s}': {s}", .{ default_analysis_basename, @errorName(err) }); + fatal("unable to open directory from arguments 'name' or 'soname', '{s}': {s}", .{ + default_analysis_basename, @errorName(err), + }); }, .no => unreachable, } @@ -2067,8 +2100,10 @@ fn buildOutputType( var emit_docs_resolved = emit_docs.resolve("docs") catch |err| { switch (emit_docs) { - .yes => { - fatal("unable to open directory from argument 'femit-docs', '{s}': {s}", .{ emit_h.yes, @errorName(err) }); + .yes => |p| { + fatal("unable to open directory from argument '-femit-docs', '{s}': {s}", .{ + p, @errorName(err), + }); }, .yes_default_path => { fatal("unable to open directory 'docs': {s}", .{@errorName(err)}); @@ -2078,6 +2113,35 @@ fn buildOutputType( }; defer emit_docs_resolved.deinit(); + const is_dyn_lib = switch (output_mode) { + .Obj, .Exe => false, + .Lib => (link_mode orelse .Static) == .Dynamic, + }; + const implib_eligible = is_dyn_lib and + emit_bin_loc != null and target_info.target.os.tag == .windows; + if (!implib_eligible) { + if (!emit_implib_arg_provided) { + emit_implib = .no; + } else if (emit_implib != .no) { + fatal("the argument -femit-implib is allowed only when building a Windows DLL", .{}); + } + } + const default_implib_basename = try std.fmt.allocPrint(arena, "{s}.lib", .{root_name}); + var emit_implib_resolved = emit_implib.resolve(default_implib_basename) catch |err| { + switch (emit_implib) { + .yes => |p| { + fatal("unable to open directory from argument '-femit-implib', '{s}': {s}", .{ + p, @errorName(err), + }); + }, + .yes_default_path => { + fatal("unable to open directory 'docs': {s}", .{@errorName(err)}); + }, + .no => unreachable, + } + }; + defer emit_implib_resolved.deinit(); + const main_pkg: ?*Package = if (root_src_file) |src_path| blk: { if (main_pkg_path) |p| { const rel_src_path = try fs.path.relative(gpa, p, src_path); @@ -2168,16 +2232,6 @@ fn buildOutputType( else => false, }; - // Always output import libraries (.lib) when building for msvc to replicate - // `link` behavior. lld does not always output import libraries so on the - // gnu abi users must set out_implib. - if (output_mode == .Lib and emit_bin == .yes and target_info.target.abi == .msvc and out_implib == null) { - const emit_bin_ext = fs.path.extension(emit_bin.yes); - out_implib = try std.fmt.allocPrint(gpa, "{s}.lib", .{ - emit_bin.yes[0 .. emit_bin.yes.len - emit_bin_ext.len], - }); - } - gimmeMoreOfThoseSweetSweetFileDescriptors(); const comp = Compilation.create(gpa, .{ @@ -2199,6 +2253,7 @@ fn buildOutputType( .emit_llvm_bc = emit_llvm_bc_resolved.data, .emit_docs = emit_docs_resolved.data, .emit_analysis = emit_analysis_resolved.data, + .emit_implib = emit_implib_resolved.data, .link_mode = link_mode, .dll_export_fns = dll_export_fns, .object_format = object_format, @@ -2286,7 +2341,6 @@ fn buildOutputType( .test_name_prefix = test_name_prefix, .disable_lld_caching = !have_enable_cache, .subsystem = subsystem, - .out_implib = out_implib, .wasi_exec_model = wasi_exec_model, .debug_compile_errors = debug_compile_errors, .enable_link_snapshots = enable_link_snapshots, @@ -2685,15 +2739,6 @@ fn updateModule(gpa: *Allocator, comp: *Compilation, hook: AfterUpdateHook) !voi _ = try cache_dir.updateFile(src_pdb_path, cwd, dst_pdb_path, .{}); } - - if (comp.bin_file.options.out_implib) |out_implib| { - const src_implib_path = try std.fmt.allocPrint(gpa, "{s}.lib", .{bin_sub_path}); - defer gpa.free(src_implib_path); - if (std.fs.path.dirname(out_implib)) |implib_dir| { - try cwd.makePath(implib_dir); - } - _ = try cache_dir.updateFile(src_implib_path, cwd, out_implib, .{}); - } }, } } From 20cc7af8e6e47ba209ab0d462826f40516c86b9d Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 24 Nov 2021 18:35:37 -0700 Subject: [PATCH 5/5] stage2: support LLD -O flags on ELF In 7e23b3245a9bf6e002009e6c18c10a9995671afa I made -O flags to the linker emit a warning that the argument does nothing. That was not correct however; LLD does have some logic that does different things depending on -O0, -O1, and -O2. It defaults to -O1, and it does less optimizations with -O0 and more with -O2. With this commit, e.g. `-Wl,-O1` is supported by the `zig cc` frontend, and by default we pass `-O0` to LLD in debug mode, and `-O3` in release modes. I also fixed a bug in the LLD ELF linker line which was incorrectly passing `-O` flags instead of `--lto-O` flags for LTO. --- src/Compilation.zig | 6 +++ src/link.zig | 1 + src/link/Elf.zig | 8 +++- src/main.zig | 12 ++++-- test/standalone/install_raw_hex/build.zig | 48 +++++++++++------------ 5 files changed, 45 insertions(+), 30 deletions(-) diff --git a/src/Compilation.zig b/src/Compilation.zig index 2ff390f196..4196eb634d 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -736,6 +736,7 @@ pub const InitOptions = struct { linker_tsaware: bool = false, linker_nxcompat: bool = false, linker_dynamicbase: bool = false, + linker_optimization: ?u8 = null, major_subsystem_version: ?u32 = null, minor_subsystem_version: ?u32 = null, clang_passthrough_mode: bool = false, @@ -1140,6 +1141,10 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { const strip = options.strip or !target_util.hasDebugInfo(options.target); const red_zone = options.want_red_zone orelse target_util.hasRedZone(options.target); const omit_frame_pointer = options.omit_frame_pointer orelse (options.optimize_mode != .Debug); + const linker_optimization: u8 = options.linker_optimization orelse switch (options.optimize_mode) { + .Debug => @as(u8, 0), + else => @as(u8, 3), + }; // We put everything into the cache hash that *cannot be modified during an incremental update*. // For example, one cannot change the target between updates, but one can change source files, @@ -1450,6 +1455,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { .tsaware = options.linker_tsaware, .nxcompat = options.linker_nxcompat, .dynamicbase = options.linker_dynamicbase, + .linker_optimization = linker_optimization, .major_subsystem_version = options.major_subsystem_version, .minor_subsystem_version = options.minor_subsystem_version, .stack_size_override = options.stack_size_override, diff --git a/src/link.zig b/src/link.zig index 3657397500..f4c51c735f 100644 --- a/src/link.zig +++ b/src/link.zig @@ -99,6 +99,7 @@ pub const Options = struct { tsaware: bool, nxcompat: bool, dynamicbase: bool, + linker_optimization: u8, bind_global_refs_locally: bool, import_memory: bool, initial_memory: ?u64, diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 43447e0ec4..646a568684 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -1349,6 +1349,7 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void { man.hash.add(self.base.options.bind_global_refs_locally); man.hash.add(self.base.options.tsan); man.hash.addOptionalBytes(self.base.options.sysroot); + man.hash.add(self.base.options.linker_optimization); // We don't actually care whether it's a cache hit or miss; we just need the digest and the lock. _ = try man.hit(); @@ -1425,10 +1426,13 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void { if (self.base.options.lto) { switch (self.base.options.optimize_mode) { .Debug => {}, - .ReleaseSmall => try argv.append("-O2"), - .ReleaseFast, .ReleaseSafe => try argv.append("-O3"), + .ReleaseSmall => try argv.append("--lto-O2"), + .ReleaseFast, .ReleaseSafe => try argv.append("--lto-O3"), } } + try argv.append(try std.fmt.allocPrint(arena, "-O{d}", .{ + self.base.options.linker_optimization, + })); if (self.base.options.output_mode == .Exe) { try argv.append("-z"); diff --git a/src/main.zig b/src/main.zig index 2c99f508d4..c2e7d293ec 100644 --- a/src/main.zig +++ b/src/main.zig @@ -635,6 +635,7 @@ fn buildOutputType( var linker_tsaware = false; var linker_nxcompat = false; var linker_dynamicbase = false; + var linker_optimization: ?u8 = null; var test_evented_io = false; var test_no_exec = false; var stack_size_override: ?u64 = null; @@ -1488,11 +1489,13 @@ fn buildOutputType( if (i >= linker_args.items.len) { fatal("expected linker arg after '{s}'", .{arg}); } - warn("ignoring linker arg -O{s} because it does nothing", .{ - linker_args.items[i], - }); + linker_optimization = std.fmt.parseUnsigned(u8, linker_args.items[i], 10) catch |err| { + fatal("unable to parse '{s}': {s}", .{ arg, @errorName(err) }); + }; } else if (mem.startsWith(u8, arg, "-O")) { - warn("ignoring linker arg {s} because it does nothing", .{arg}); + linker_optimization = std.fmt.parseUnsigned(u8, arg["-O".len..], 10) catch |err| { + fatal("unable to parse '{s}': {s}", .{ arg, @errorName(err) }); + }; } else if (mem.eql(u8, arg, "--gc-sections")) { linker_gc_sections = true; } else if (mem.eql(u8, arg, "--no-gc-sections")) { @@ -2309,6 +2312,7 @@ fn buildOutputType( .linker_tsaware = linker_tsaware, .linker_nxcompat = linker_nxcompat, .linker_dynamicbase = linker_dynamicbase, + .linker_optimization = linker_optimization, .major_subsystem_version = major_subsystem_version, .minor_subsystem_version = minor_subsystem_version, .link_eh_frame_hdr = link_eh_frame_hdr, diff --git a/test/standalone/install_raw_hex/build.zig b/test/standalone/install_raw_hex/build.zig index 086fafebbc..789197b627 100644 --- a/test/standalone/install_raw_hex/build.zig +++ b/test/standalone/install_raw_hex/build.zig @@ -35,7 +35,7 @@ pub fn build(b: *Builder) void { ":1001140000000000000000000000000000000000DB", ":1001240000000000000000000000000000000000CB", ":1001340000000000000000000000000000000000BB", - ":10014400830202006C020100090000002102010088", + ":1001440083020200F401010009000000FE01010025", ":100154000900000001000000000000000000000091", ":1001640000080002008001010004000010000000EB", ":100174000000000000000000000000001F0000005C", @@ -44,17 +44,17 @@ pub fn build(b: *Builder) void { ":1001A40000000000000000001F000000000000002C", ":1001B400000000000000000000000000000000003B", ":1001C400000000000000000000000000000000002B", - ":1001D4005B02010010000000F40101002C0000008B", - ":1001E4003F0201001B0000002B020100130000006D", - ":1001F40072656D61696E646572206469766973699C", - ":100204006F6E206279207A65726F206F72206E653E", - ":100214006761746976652076616C756500636F72D9", - ":100224007465782D6D3400696E646578206F75741B", - ":10023400206F6620626F756E647300696E74656703", - ":1002440065722063617374207472756E6361746582", - ":10025400642062697473006469766973696F6E20DF", - ":100264006279207A65726F00636F727465785F6D6E", - ":100274003400000081B00091FFE700BEFDE7D0B577", + ":1001D4000802010010000000190201002C000000B8", + ":1001E400460201001B00000062020100130000002F", + ":1001F400636F727465785F6D3400636F72746578D1", + ":100204002D6D34006469766973696F6E206279209C", + ":100214007A65726F0072656D61696E6465722064DF", + ":1002240069766973696F6E206279207A65726F20CE", + ":100234006F72206E656761746976652076616C758E", + ":100244006500696E746567657220636173742074F8", + ":1002540072756E6361746564206269747300696E9B", + ":10026400646578206F7574206F6620626F756E64A4", + ":100274007300000081B00091FFE700BEFDE7D0B538", ":1002840002AF90B00391029007A800F029F80399F7", ":100294000020069048680490FFE7049906980190AE", ":1002A40088420FD2FFE7019903980068405C07F881", @@ -89,7 +89,7 @@ pub fn build(b: *Builder) void { ":1001140000000000000000000000000000000000DB", ":1001240000000000000000000000000000000000CB", ":1001340000000000000000000000000000000000BB", - ":10014400830202006C020100090000002102010088", + ":1001440083020200F401010009000000FE01010025", ":100154000900000001000000000000000000000091", ":1001640000080002008001010004000010000000EB", ":100174000000000000000000000000001F0000005C", @@ -98,17 +98,17 @@ pub fn build(b: *Builder) void { ":1001A40000000000000000001F000000000000002C", ":1001B400000000000000000000000000000000003B", ":1001C400000000000000000000000000000000002B", - ":1001D4005B02010010000000F40101002C0000008B", - ":1001E4003F0201001B0000002B020100130000006D", - ":1001F40072656D61696E646572206469766973699C", - ":100204006F6E206279207A65726F206F72206E653E", - ":100214006761746976652076616C756500636F72D9", - ":100224007465782D6D3400696E646578206F75741B", - ":10023400206F6620626F756E647300696E74656703", - ":1002440065722063617374207472756E6361746582", - ":10025400642062697473006469766973696F6E20DF", - ":100264006279207A65726F00636F727465785F6D6E", - ":100274003400000081B00091FFE700BEFDE7D0B577", + ":1001D4000802010010000000190201002C000000B8", + ":1001E400460201001B00000062020100130000002F", + ":1001F400636F727465785F6D3400636F72746578D1", + ":100204002D6D34006469766973696F6E206279209C", + ":100214007A65726F0072656D61696E6465722064DF", + ":1002240069766973696F6E206279207A65726F20CE", + ":100234006F72206E656761746976652076616C758E", + ":100244006500696E746567657220636173742074F8", + ":1002540072756E6361746564206269747300696E9B", + ":10026400646578206F7574206F6620626F756E64A4", + ":100274007300000081B00091FFE700BEFDE7D0B538", ":1002840002AF90B00391029007A800F029F80399F7", ":100294000020069048680490FFE7049906980190AE", ":1002A40088420FD2FFE7019903980068405C07F881",